[giv] Added cfitsio to source tree.



commit b16c3d0a6572289d18c6e1167ff1b2e4653844c2
Author: Dov Grobgeld <dov grobgeld gmail com>
Date:   Sun Nov 18 23:24:58 2012 +0200

    Added cfitsio to source tree.

 README                                             |   22 +-
 SConstruct                                         |   51 +
 configure.in                                       |    2 +-
 giv.wine.nsi.in                                    |   74 +-
 notes.org                                          |   23 +
 src/SConscript                                     |    5 +-
 src/giv-win.gob                                    |    5 +-
 src/giv.nsi.in                                     |   72 -
 src/gtkimageviewer/gtk-image-viewer.gob            |   10 +-
 src/plugins/SConscript                             |   17 +-
 src/plugins/cfitsio/README                         |    2 +
 src/plugins/cfitsio/adler32.c                      |  167 +
 src/plugins/cfitsio/buffers.c                      | 1371 +++
 src/plugins/cfitsio/cfileio.c                      | 6859 +++++++++++++++
 src/plugins/cfitsio/cfortran.h                     | 2515 ++++++
 src/plugins/cfitsio/checksum.c                     |  508 ++
 src/plugins/cfitsio/crc32.c                        |  440 +
 src/plugins/cfitsio/crc32.h                        |  441 +
 src/plugins/cfitsio/deflate.c                      | 1832 ++++
 src/plugins/cfitsio/deflate.h                      |  340 +
 src/plugins/cfitsio/drvrfile.c                     |  900 ++
 src/plugins/cfitsio/drvrgsiftp.c                   |  522 ++
 src/plugins/cfitsio/drvrgsiftp.h                   |   21 +
 src/plugins/cfitsio/drvrmem.c                      | 1184 +++
 src/plugins/cfitsio/drvrnet.c                      | 2741 ++++++
 src/plugins/cfitsio/drvrsmem.c                     |  973 ++
 src/plugins/cfitsio/drvrsmem.h                     |  179 +
 src/plugins/cfitsio/editcol.c                      | 2474 ++++++
 src/plugins/cfitsio/edithdu.c                      |  883 ++
 src/plugins/cfitsio/eval_defs.h                    |  163 +
 src/plugins/cfitsio/eval_f.c                       | 2823 ++++++
 src/plugins/cfitsio/eval_l.c                       | 2252 +++++
 src/plugins/cfitsio/eval_tab.h                     |   42 +
 src/plugins/cfitsio/eval_y.c                       | 7333 ++++++++++++++++
 src/plugins/cfitsio/f77_wrap.h                     |  288 +
 src/plugins/cfitsio/f77_wrap1.c                    |  345 +
 src/plugins/cfitsio/f77_wrap2.c                    |  711 ++
 src/plugins/cfitsio/f77_wrap3.c                    |  853 ++
 src/plugins/cfitsio/f77_wrap4.c                    |  572 ++
 src/plugins/cfitsio/fits_hcompress.c               | 1858 ++++
 src/plugins/cfitsio/fits_hdecompress.c             | 2618 ++++++
 src/plugins/cfitsio/fitscore.c                     | 9242 +++++++++++++++++++
 src/plugins/cfitsio/fitsio.h                       | 1930 ++++
 src/plugins/cfitsio/fitsio2.h                      | 1205 +++
 src/plugins/cfitsio/fpack.h                        |  164 +
 src/plugins/cfitsio/fpackutil.c                    | 2381 +++++
 src/plugins/cfitsio/getcol.c                       | 1055 +++
 src/plugins/cfitsio/getcolb.c                      | 2001 +++++
 src/plugins/cfitsio/getcold.c                      | 1676 ++++
 src/plugins/cfitsio/getcole.c                      | 1679 ++++
 src/plugins/cfitsio/getcoli.c                      | 1901 ++++
 src/plugins/cfitsio/getcolj.c                      | 3726 ++++++++
 src/plugins/cfitsio/getcolk.c                      | 1894 ++++
 src/plugins/cfitsio/getcoll.c                      |  614 ++
 src/plugins/cfitsio/getcols.c                      |  835 ++
 src/plugins/cfitsio/getcolsb.c                     | 1991 +++++
 src/plugins/cfitsio/getcolui.c                     | 1907 ++++
 src/plugins/cfitsio/getcoluj.c                     | 1901 ++++
 src/plugins/cfitsio/getcoluk.c                     | 1916 ++++
 src/plugins/cfitsio/getkey.c                       | 3241 +++++++
 src/plugins/cfitsio/group.c                        | 6463 ++++++++++++++
 src/plugins/cfitsio/group.h                        |   65 +
 src/plugins/cfitsio/grparser.c                     | 1379 +++
 src/plugins/cfitsio/grparser.h                     |  185 +
 src/plugins/cfitsio/histo.c                        | 2221 +++++
 src/plugins/cfitsio/imcompress.c                   | 9247 ++++++++++++++++++++
 src/plugins/cfitsio/infback.c                      |  632 ++
 src/plugins/cfitsio/inffast.c                      |  340 +
 src/plugins/cfitsio/inffast.h                      |   11 +
 src/plugins/cfitsio/inffixed.h                     |   94 +
 src/plugins/cfitsio/inflate.c                      | 1480 ++++
 src/plugins/cfitsio/inflate.h                      |  122 +
 src/plugins/cfitsio/inftrees.c                     |  330 +
 src/plugins/cfitsio/inftrees.h                     |   62 +
 src/plugins/cfitsio/iraffits.c                     | 2073 +++++
 src/plugins/cfitsio/iter_a.c                       |  147 +
 src/plugins/cfitsio/iter_b.c                       |  114 +
 src/plugins/cfitsio/iter_c.c                       |  171 +
 src/plugins/cfitsio/iter_image.c                   |   93 +
 src/plugins/cfitsio/iter_var.c                     |  100 +
 src/plugins/cfitsio/longnam.h                      |  592 ++
 src/plugins/cfitsio/modkey.c                       | 1706 ++++
 src/plugins/cfitsio/pliocomp.c                     |  331 +
 src/plugins/cfitsio/putcol.c                       | 1929 ++++
 src/plugins/cfitsio/putcolb.c                      | 1012 +++
 src/plugins/cfitsio/putcold.c                      | 1059 +++
 src/plugins/cfitsio/putcole.c                      | 1073 +++
 src/plugins/cfitsio/putcoli.c                      |  985 +++
 src/plugins/cfitsio/putcolj.c                      | 1990 +++++
 src/plugins/cfitsio/putcolk.c                      | 1012 +++
 src/plugins/cfitsio/putcoll.c                      |  369 +
 src/plugins/cfitsio/putcols.c                      |  303 +
 src/plugins/cfitsio/putcolsb.c                     |  974 ++
 src/plugins/cfitsio/putcolu.c                      |  629 ++
 src/plugins/cfitsio/putcolui.c                     |  969 ++
 src/plugins/cfitsio/putcoluj.c                     |  977 +++
 src/plugins/cfitsio/putcoluk.c                     |  993 +++
 src/plugins/cfitsio/putkey.c                       | 3085 +++++++
 src/plugins/cfitsio/quantize.c                     | 3888 ++++++++
 src/plugins/cfitsio/region.c                       | 1747 ++++
 src/plugins/cfitsio/region.h                       |   82 +
 src/plugins/cfitsio/ricecomp.c                     | 1382 +++
 src/plugins/cfitsio/scalnull.c                     |  229 +
 src/plugins/cfitsio/swapproc.c                     |  247 +
 src/plugins/cfitsio/trees.c                        | 1242 +++
 src/plugins/cfitsio/trees.h                        |  128 +
 src/plugins/cfitsio/uncompr.c                      |   57 +
 src/plugins/cfitsio/wcssub.c                       | 1043 +++
 src/plugins/cfitsio/wcsutil.c                      |  502 ++
 src/plugins/cfitsio/zcompress.c                    |  504 ++
 src/plugins/cfitsio/zconf.h                        |  426 +
 src/plugins/cfitsio/zlib.h                         | 1613 ++++
 src/plugins/cfitsio/zuncompress.c                  |  603 ++
 src/plugins/cfitsio/zutil.c                        |  316 +
 src/plugins/cfitsio/zutil.h                        |  272 +
 .../dcmtk/config/include/dcmtk/config/cfwin32.h    |    2 +-
 116 files changed, 145200 insertions(+), 150 deletions(-)
---
diff --git a/README b/README
index 4628f70..a201518 100644
--- a/README
+++ b/README
@@ -1,21 +1,5 @@
-Release notes for version 0.9.23
-Sunday 2012-06-10 
+Release notes for version 0.9.24beta1
+Thursday 2012-06-21 
 
-This is a major bug fix version that contain the following fixes:
+This is a test version of giv compiled for Windows64
 
-* If auto-contrast is off, leave 8-bit file contrast untouched on load.
-* In the marks viewer replaced full path filename with basename of the file.
-* Solved sync between menus and functionality for balloons, toggles, etc.
-* Added default options for auto-contrast, sub-pixel, and auto-fill.
-* Fixed display in the marker tree view which did not always reflect what marks were visible.
-* Made mark view properly update when opened when navigating between files.
-* Removed redundant redraws on autofit.
-* Fixed major memory leak. (Marks where not released when going to next file).
-* Added display of multi-line strings.
-* Made giv honor no auto-resive for giv files without images.
-* Changed mousewheel zooming so that it zooms around mouse pointer.
-* Added different strength zooming by control and shift mouse wheel.
-* Fixed broken measure tool text on Windows.
-* Made click zoom disabled when clicking in the window when giv is not the focused window.
-* Cleaned up menus so that top menu and right click menu show the same entries.
-* Documentation updates.
diff --git a/SConstruct b/SConstruct
index 317013d..7d99d0e 100644
--- a/SConstruct
+++ b/SConstruct
@@ -50,6 +50,8 @@ def template_fill(env, target, source):
     inp.close()
 
 if ARGUMENTS.get('mingw', 0):
+    env['HOST']='w32'
+    env['LOCAL_DIR']='mingw32'
     env['CC']='i686-w64-mingw32-gcc'
     env['CXX']='i686-w64-mingw32-g++'
     env['AR']='i686-w64-mingw32-ar'
@@ -64,9 +66,11 @@ if ARGUMENTS.get('mingw', 0):
     env['ROOT'] = ""
     env['DLLWRAP'] = "i686-w64-mingw32-dllwrap"
     env['DLLTOOL'] = "i686-w64-mingw32-dlltool"
+    env['WINDRES'] = "i686-w64-mingw32-windres"
     env['DLLWRAP_FLAGS'] = "--mno-cygwin --as=${AS} --export-all --driver-name ${CXX} --dll-tool-name ${DLLTOOL} -s"
     env.Append(CPPFLAGS= ['-mms-bitfields'])
 
+    env['SYSROOT'] = r"\usr\i686-w64-mingw32\sys-root"
     env.Command("giv.wine.nsi",
                 ["giv.wine.nsi.in",
                  "SConstruct",
@@ -93,6 +97,53 @@ if ARGUMENTS.get('mingw', 0):
     env['PACKAGE_DOC_DIR'] = '../doc'
     env['PACKAGE_PLUGIN_DIR'] = '../plugins'
     env['arch']='mingw32'
+elif ARGUMENTS.get('mingw64', 0):
+    env['HOST']='w64'
+    env['LOCAL_DIR']='mingw64'
+    env['SYSROOT'] = r"\usr\x86_64-w64-mingw32\sys-root"
+    env['CC']='x86_64-w64-mingw32-gcc'
+    env['CXX']='x86_64-w64-mingw32-g++'
+    env['AR']='x86_64-w64-mingw32-ar'
+    env['RANLIB']='x86_64-w64-mingw32-ranlib'
+    env['PKGCONFIG'] = "env PKG_CONFIG_PATH=/usr/x86_64-w64-mingw32/sys-root/mingw/lib/pkgconfig:/usr/local/${LOCAL_DIR}/lib/pkgconfig pkg-config"
+    env['OBJSUFFIX']=".obj"
+    env['PROGSUFFIX'] = ".exe"
+    env['SHOBJSUFFIX']=".obj"
+    env['SHLIBSUFFIX'] = ".dll"
+    env['SHLIBPREFIX'] = ""
+    env['PREFIX'] = "/usr/x86_64-w64-mingw32/sys-root"
+    env['ROOT'] = ""
+    env['DLLWRAP'] = "x86_64-w64-mingw32-dllwrap"
+    env['DLLTOOL'] = "x86_64-w64-mingw32-dlltool"
+    env['WINDRES'] = "x86_64-w64-mingw32-windres"
+    env['DLLWRAP_FLAGS'] = "--mno-cygwin --as=${AS} --export-all --driver-name ${CXX} --dll-tool-name ${DLLTOOL} -s"
+    env.Append(CPPFLAGS= ['-mms-bitfields'])
+
+    env.Command("giv.wine.nsi",
+                ["giv.wine.nsi.in",
+                 "SConstruct",
+                 "configure.in"
+                ],
+                template_fill
+                )
+    env.Command("COPYING.dos",
+                "COPYING",
+                ["unix2dos < COPYING > COPYING.dos"])
+    
+    env.Command("InstallGiv" + env['VER'] + "-w64.exe",
+                ["src/giv.exe",
+                 "giv.wine.nsi",
+                 "src/plugins/tiff.dll",
+                 "src/plugins/dicom.dll",
+                 "src/plugins/npy.dll",
+                 ],
+                ["makensis giv.wine.nsi"])
+    env.Append(LINKFLAGS=['-mwindows'])
+
+    # TBD - make this installation dependent
+    env['PACKAGE_DOC_DIR'] = '../doc'
+    env['PACKAGE_PLUGIN_DIR'] = '../plugins'
+    env['arch']='mingw64'
 else:
     # Posix by default
     env['PKGCONFIG'] = "pkg-config"
diff --git a/configure.in b/configure.in
index 490255e..d39f26b 100644
--- a/configure.in
+++ b/configure.in
@@ -5,7 +5,7 @@ AM_CONFIG_HEADER(config.h)
 PACKAGE=givwidget
 GIVWIDGET_API_VERSION=2.0
 
-AM_INIT_AUTOMAKE(giv, 0.9.23)
+AM_INIT_AUTOMAKE(giv, 0.9.24beta2)
 
 dnl Use libtool to get shared libraries
 LT_PREREQ
diff --git a/giv.wine.nsi.in b/giv.wine.nsi.in
index fd0c180..f10e56b 100644
--- a/giv.wine.nsi.in
+++ b/giv.wine.nsi.in
@@ -1,5 +1,5 @@
 Name "Giv"
-OutFile "InstallGiv- VER@.exe"
+OutFile "InstallGiv- VER@- HOST@.exe"
 Icon "giv-logo.ico"
 UninstallIcon "giv-logo-install.ico"
 
@@ -35,41 +35,41 @@ File /r examples
 SetOutPath $INSTDIR\bin
 File src\Giv.exe
 File src\giv-image.dll
-File \usr\i686-w64-mingw32\sys-root\mingw\bin\libgcc_s_sjlj-1.dll
-File \usr\i686-w64-mingw32\sys-root\mingw\bin\libexpat-1.dll
-File \usr\i686-w64-mingw32\sys-root\mingw\bin\libstdc++-6.dll
-File \usr\i686-w64-mingw32\sys-root\mingw\bin\iconv.dll
-File \usr\i686-w64-mingw32\sys-root\mingw\bin\libpcre-1.dll
-File \usr\i686-w64-mingw32\sys-root\mingw\bin\libintl-8.dll
-File \usr\i686-w64-mingw32\sys-root\mingw\bin\libffi-6.dll
-File \usr\i686-w64-mingw32\sys-root\mingw\bin\libgdk-win32*.dll
-File \usr\i686-w64-mingw32\sys-root\mingw\bin\libgdk_pixbuf*.dll
-File \usr\i686-w64-mingw32\sys-root\mingw\bin\libgtk-win32*.dll
-File \usr\i686-w64-mingw32\sys-root\mingw\bin\libgio*.dll
-File \usr\i686-w64-mingw32\sys-root\mingw\bin\libcairo*.dll
-File \usr\i686-w64-mingw32\sys-root\mingw\bin\libjasper-1.dll
-File \usr\i686-w64-mingw32\sys-root\mingw\bin\zlib*.dll
-File \usr\i686-w64-mingw32\sys-root\mingw\bin\libglib*.dll
-File \usr\i686-w64-mingw32\sys-root\mingw\bin\libatk*.dll
-File \usr\i686-w64-mingw32\sys-root\mingw\bin\libgobject*.dll
-File \usr\i686-w64-mingw32\sys-root\mingw\bin\libgmodule*.dll
-File \usr\i686-w64-mingw32\sys-root\mingw\bin\libgthread*.dll
-File \usr\i686-w64-mingw32\sys-root\mingw\bin\libpango*.dll
-File \usr\i686-w64-mingw32\sys-root\mingw\bin\libpng*.dll
-File \usr\i686-w64-mingw32\sys-root\mingw\bin\libtiff*.dll
-File \usr\i686-w64-mingw32\sys-root\mingw\bin\libjpeg*.dll
-File \usr\i686-w64-mingw32\sys-root\mingw\bin\libpixman-1-0.dll
-File \usr\i686-w64-mingw32\sys-root\mingw\bin\libfontconfig*.dll
-File \usr\i686-w64-mingw32\sys-root\mingw\bin\libfreetype*.dll
-File \usr\local\mingw32\bin\libjson-glib-1.0-0.dll
-File \usr\i686-w64-mingw32\sys-root\mingw\bin\gdk-pixbuf-query-loaders.exe
+File @SYSROOT \mingw\bin\libgcc_s_sjlj-1 dll
+File @SYSROOT \mingw\bin\libexpat-1 dll
+File @SYSROOT \mingw\bin\libstdc++-6 dll
+File @SYSROOT \mingw\bin\iconv dll
+File @SYSROOT \mingw\bin\libpcre-1 dll
+File @SYSROOT \mingw\bin\libintl-8 dll
+File @SYSROOT \mingw\bin\libffi-6 dll
+File @SYSROOT \mingw\bin\libgdk-win32* dll
+File @SYSROOT \mingw\bin\libgdk_pixbuf* dll
+File @SYSROOT \mingw\bin\libgtk-win32* dll
+File @SYSROOT \mingw\bin\libgio* dll
+File @SYSROOT \mingw\bin\libcairo* dll
+File @SYSROOT \mingw\bin\libjasper-1 dll
+File @SYSROOT \mingw\bin\zlib* dll
+File @SYSROOT \mingw\bin\libglib* dll
+File @SYSROOT \mingw\bin\libatk* dll
+File @SYSROOT \mingw\bin\libgobject* dll
+File @SYSROOT \mingw\bin\libgmodule* dll
+File @SYSROOT \mingw\bin\libgthread* dll
+File @SYSROOT \mingw\bin\libpango* dll
+File @SYSROOT \mingw\bin\libpng* dll
+File @SYSROOT \mingw\bin\libtiff* dll
+File @SYSROOT \mingw\bin\libjpeg* dll
+File @SYSROOT \mingw\bin\libpixman-1-0 dll
+File @SYSROOT \mingw\bin\libfontconfig* dll
+File @SYSROOT \mingw\bin\libfreetype* dll
+#File \usr\local\mingw32\bin\libjson-glib-1.0-0.dll
+File @SYSROOT \mingw\bin\gdk-pixbuf-query-loaders exe
 
 SetOutPath $INSTDIR\etc
-File /r \usr\i686-w64-mingw32\sys-root\mingw\etc\gtk-2.0
-File /r \usr\i686-w64-mingw32\sys-root\mingw\etc\fonts
-File /r \usr\i686-w64-mingw32\sys-root\mingw\etc\pango
+File /r @SYSROOT \mingw\etc\gtk-2 0
+File /r @SYSROOT \mingw\etc\fonts
+File /r @SYSROOT \mingw\etc\pango
 SetOutPath $INSTDIR\etc\gtk-2.0
-File \usr\i686-w64-mingw32\sys-root\mingw\share\themes\MS-Windows\gtk-2.0\gtkrc
+File @SYSROOT \mingw\share\themes\MS-Windows\gtk-2 0\gtkrc
 
 # Plugins
 SetOutPath $INSTDIR\plugins
@@ -77,15 +77,15 @@ File src\plugins\*.dll
 
 # pango
 SetOutPath $INSTDIR\lib
-File /r \usr\i686-w64-mingw32\sys-root\mingw\lib\pango
+File /r @SYSROOT \mingw\lib\pango
 
 # pixbuf etc
 SetOutPath $INSTDIR\lib\gdk-pixbuf-2.0\2.10.0
-File /r \usr\i686-w64-mingw32\sys-root\mingw\lib\gdk-pixbuf-2.0\2.10.0\loaders*
+File /r @SYSROOT \mingw\lib\gdk-pixbuf-2 0\2 10 0\loaders*
 SetOutPath $INSTDIR\lib\gtk-2.0\2.10.0\engines
-File \usr\i686-w64-mingw32\sys-root\mingw\lib\gtk-2.0\2.10.0\engines\*
+File @SYSROOT \mingw\lib\gtk-2 0\2 10 0\engines\*
 SetOutPath $INSTDIR\share\themes 
-File /r \usr\i686-w64-mingw32\sys-root\mingw\share\themes\*
+File /r @SYSROOT \mingw\share\themes\*
 
 # Build the gdk-pixbuf.loaders file automatically
 #ExpandEnvStrings $0 %COMSPEC%
diff --git a/notes.org b/notes.org
index 695a5cd..31959e6 100644
--- a/notes.org
+++ b/notes.org
@@ -35,3 +35,26 @@
 ** Done/Todo
    - [X] Recursively turn on and off child nodes when toggling the display of a parent in the model viewer.
    - [X] Why is initial scaling different from the scaling received with "fill"? Due to the sticky autoscale setting. Fixed by forcing fill on initial load of dataset.
+* <2012-06-21 Thu>
+** Windows 64 bit compilation
+   - Need to compile the following libraries as they are not provided by federa:
+     - [ ] cfitsio
+     - [ ] json-glib
+     - [ ] Fix dcm library
+*** cfitsio compilation
+    - Got sources from http://heasarc.gsfc.nasa.gov/fitsio/
+    - Commands:
+    #+begin_src sh
+   cd ~/src
+   tar -xf /tmp/cfitsio3300.tar.gz
+   cd cfitsio
+   env CC=x86_64-w64-mingw32-gcc ./configure --host=x86_64-w64-mingw32 --prefix=/usr/local/mingw64
+   make -j 4
+   make install
+    #+end_src
+   - Even though compilation of cfitsio succeeded linking failed. Need to figure out why. Meanwhile removed fitsio plugin from installation.
+   - Get errors like the following:
+   #+begin_example
+   /usr/lib/gcc/x86_64-w64-mingw32/4.7.0/../../../../x86_64-w64-mingw32/bin/ld: skipping incompatible /usr/local/mingw64/lib/libcfitsio.a when searching for -lcfitsio
+   #+end_example
+    - Solved fitsio problem by literal inclusion of sources.
diff --git a/src/SConscript b/src/SConscript
index bbd2671..6ac34e6 100644
--- a/src/SConscript
+++ b/src/SConscript
@@ -37,14 +37,13 @@ src_giv_exe = ["giv.cc",
                "dovtk-lasso.c",
                ]
 
-if ARGUMENTS.get('mingw', 0):
+if ARGUMENTS.get('mingw', 0) or ARGUMENTS.get('mingw64',0):
     res = env.Command("giv.res.obj",
                       ["giv.rc",
                        "giv-logo.ico"
                       ],
-                      ["i686-w64-mingw32-windres giv.rc giv.res.obj"],
+                      ["${WINDRES} giv.rc giv.res.obj"],
                       chdir=1)
-    print "res = ", res
     src_giv_exe = src_giv_exe + res
     
 #env.Program("giv",
diff --git a/src/giv-win.gob b/src/giv-win.gob
index 15c003f..86832f0 100644
--- a/src/giv-win.gob
+++ b/src/giv-win.gob
@@ -1893,6 +1893,7 @@ cb_key_press_event (GtkWidget * widget,
               selfp->current_slice = 0;
 
           // Redraw the image
+          giv_image_ref(selfp->img_org);
           giv_win_set_image(GIV_WIN(self),
                             selfp->img_org);
           giv_win_set_image_info(GIV_WIN(self));
@@ -3298,9 +3299,6 @@ apply_color_map(GivWin *self)
         return;
     }
         
-    if (selfp->img_display)
-      g_object_unref(selfp->img_display);
-
     const guint8 *tmap = NULL;
     if (selfp->colormap == PSEUDO_COLOR_OFF
         //        || !selfp->img_is_mono
@@ -4331,4 +4329,3 @@ static void create_remote_commands(GivWin *self)
 }
 
 %}
-
diff --git a/src/gtkimageviewer/gtk-image-viewer.gob b/src/gtkimageviewer/gtk-image-viewer.gob
index b0a8d9a..6f763a4 100644
--- a/src/gtkimageviewer/gtk-image-viewer.gob
+++ b/src/gtkimageviewer/gtk-image-viewer.gob
@@ -1159,7 +1159,7 @@ class Gtk:Image:Viewer from Gtk:Widget
                                    0xff505050,
                                    0xffa0a0a0
                                    );
-        gdk_pixbuf_unref(img_scaled);
+        g_object_unref(img_scaled);
         img_scaled = img_comp;
     }
     if (!selfp->frozen)
@@ -1173,7 +1173,7 @@ class Gtk:Image:Viewer from Gtk:Widget
                        selfp->current_x0,
                        selfp->current_y0);
     
-    gdk_pixbuf_unref(img_scaled);
+    g_object_unref(img_scaled);
 
     // If we have just drawn the whole buffer, then copy the result
     // to the cache.
@@ -1520,12 +1520,12 @@ class Gtk:Image:Viewer from Gtk:Widget
     gboolean do_need_fill = (selfp->scroll_min_x == selfp->scroll_max_x)
       || (size_is_different && selfp->do_fill_on_resize);
     if (selfp->image)
-      gdk_pixbuf_unref(selfp->image);
+      g_object_unref(selfp->image);
     selfp->image = image;
 
     if (image)
       {
-        gdk_pixbuf_ref(image);
+        g_object_ref(image);
         selfp->scroll_min_x = 0;
         selfp->scroll_min_y = 0;
         selfp->scroll_width = gdk_pixbuf_get_width(image);
@@ -1544,7 +1544,7 @@ class Gtk:Image:Viewer from Gtk:Widget
   get_image(self)
   {
     if (selfp->image)
-        gdk_pixbuf_ref(selfp->image);
+        g_object_ref(selfp->image);
     return selfp->image;
   }
 
diff --git a/src/plugins/SConscript b/src/plugins/SConscript
index 52a2982..b6c9a0b 100644
--- a/src/plugins/SConscript
+++ b/src/plugins/SConscript
@@ -11,11 +11,11 @@ for p in [#'pgm.c',
 
 # Change this to Maemo
 if not env['SBOX']:
-    env.ParseConfig('${PKGCONFIG} --cflags --libs cfitsio')
     env.SharedLibrary('fits',
                       ['fits.c'],
-                      LIBPATH=['..','../gtkimageviewer','../agg','../pcre','../plis']+env['LIBPATH'],
+                      LIBPATH=['..','../gtkimageviewer','../agg','../pcre','../plis','#/src/plugins/cfitsio/${arch}/${VARIANT}']+env['LIBPATH'],
                       LIBS=['cfitsio','giv-image']+env['LIBS'],
+                      CPPPATH=['cfitsio']+env['CPPPATH']
                       )
     
     env.SharedLibrary('dicom',
@@ -37,11 +37,12 @@ if not env['SBOX']:
         png_lib = ['png15']
     else:
         png_lib = []
-    env.SharedLibrary('png',
-                      ['png.c'],
-                      LIBPATH=['..','../gtkimageviewer','../agg','../pcre','../plis'] + env['LIBPATH'],
-                      LIBS=['giv-image']+png_lib+env['LIBS'],
-                      )
+#    env.SharedLibrary('png',
+#                      ['png.c'],
+#                      LIBPATH=['..','../gtkimageviewer','../agg','../pcre','../plis'] + env['LIBPATH'],
+#                      LIBS=['giv-image']+png_lib+env['LIBS'],
+#                      )
     
-    SConscript('dcmtk/SConscript',
+    SConscript(['dcmtk/SConscript',
+                'cfitsio/SConscript'],
                exports='env')
diff --git a/src/plugins/cfitsio/README b/src/plugins/cfitsio/README
new file mode 100644
index 0000000..03c103b
--- /dev/null
+++ b/src/plugins/cfitsio/README
@@ -0,0 +1,2 @@
+This directory contains all c and h files from the cfitsio3300.tar.gz file
+(except those containing main() function).
\ No newline at end of file
diff --git a/src/plugins/cfitsio/adler32.c b/src/plugins/cfitsio/adler32.c
new file mode 100644
index 0000000..172de60
--- /dev/null
+++ b/src/plugins/cfitsio/adler32.c
@@ -0,0 +1,167 @@
+/* adler32.c -- compute the Adler-32 checksum of a data stream
+ * Copyright (C) 1995-2007 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+#include "zutil.h"
+
+#define local static
+
+local uLong adler32_combine_(uLong adler1, uLong adler2, z_off64_t len2);
+
+#define BASE 65521UL    /* largest prime smaller than 65536 */
+#define NMAX 5552
+/* NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 */
+
+#define DO1(buf,i)  {adler += (buf)[i]; sum2 += adler;}
+#define DO2(buf,i)  DO1(buf,i); DO1(buf,i+1);
+#define DO4(buf,i)  DO2(buf,i); DO2(buf,i+2);
+#define DO8(buf,i)  DO4(buf,i); DO4(buf,i+4);
+#define DO16(buf)   DO8(buf,0); DO8(buf,8);
+
+/* use NO_DIVIDE if your processor does not do division in hardware */
+#ifdef NO_DIVIDE
+#  define MOD(a) \
+    do { \
+        if (a >= (BASE << 16)) a -= (BASE << 16); \
+        if (a >= (BASE << 15)) a -= (BASE << 15); \
+        if (a >= (BASE << 14)) a -= (BASE << 14); \
+        if (a >= (BASE << 13)) a -= (BASE << 13); \
+        if (a >= (BASE << 12)) a -= (BASE << 12); \
+        if (a >= (BASE << 11)) a -= (BASE << 11); \
+        if (a >= (BASE << 10)) a -= (BASE << 10); \
+        if (a >= (BASE << 9)) a -= (BASE << 9); \
+        if (a >= (BASE << 8)) a -= (BASE << 8); \
+        if (a >= (BASE << 7)) a -= (BASE << 7); \
+        if (a >= (BASE << 6)) a -= (BASE << 6); \
+        if (a >= (BASE << 5)) a -= (BASE << 5); \
+        if (a >= (BASE << 4)) a -= (BASE << 4); \
+        if (a >= (BASE << 3)) a -= (BASE << 3); \
+        if (a >= (BASE << 2)) a -= (BASE << 2); \
+        if (a >= (BASE << 1)) a -= (BASE << 1); \
+        if (a >= BASE) a -= BASE; \
+    } while (0)
+#  define MOD4(a) \
+    do { \
+        if (a >= (BASE << 4)) a -= (BASE << 4); \
+        if (a >= (BASE << 3)) a -= (BASE << 3); \
+        if (a >= (BASE << 2)) a -= (BASE << 2); \
+        if (a >= (BASE << 1)) a -= (BASE << 1); \
+        if (a >= BASE) a -= BASE; \
+    } while (0)
+#else
+#  define MOD(a) a %= BASE
+#  define MOD4(a) a %= BASE
+#endif
+
+/* ========================================================================= */
+uLong ZEXPORT adler32(adler, buf, len)
+    uLong adler;
+    const Bytef *buf;
+    uInt len;
+{
+    unsigned long sum2;
+    unsigned n;
+
+    /* split Adler-32 into component sums */
+    sum2 = (adler >> 16) & 0xffff;
+    adler &= 0xffff;
+
+    /* in case user likes doing a byte at a time, keep it fast */
+    if (len == 1) {
+        adler += buf[0];
+        if (adler >= BASE)
+            adler -= BASE;
+        sum2 += adler;
+        if (sum2 >= BASE)
+            sum2 -= BASE;
+        return adler | (sum2 << 16);
+    }
+
+    /* initial Adler-32 value (deferred check for len == 1 speed) */
+    if (buf == Z_NULL)
+        return 1L;
+
+    /* in case short lengths are provided, keep it somewhat fast */
+    if (len < 16) {
+        while (len--) {
+            adler += *buf++;
+            sum2 += adler;
+        }
+        if (adler >= BASE)
+            adler -= BASE;
+        MOD4(sum2);             /* only added so many BASE's */
+        return adler | (sum2 << 16);
+    }
+
+    /* do length NMAX blocks -- requires just one modulo operation */
+    while (len >= NMAX) {
+        len -= NMAX;
+        n = NMAX / 16;          /* NMAX is divisible by 16 */
+        do {
+            DO16(buf);          /* 16 sums unrolled */
+            buf += 16;
+        } while (--n);
+        MOD(adler);
+        MOD(sum2);
+    }
+
+    /* do remaining bytes (less than NMAX, still just one modulo) */
+    if (len) {                  /* avoid modulos if none remaining */
+        while (len >= 16) {
+            len -= 16;
+            DO16(buf);
+            buf += 16;
+        }
+        while (len--) {
+            adler += *buf++;
+            sum2 += adler;
+        }
+        MOD(adler);
+        MOD(sum2);
+    }
+
+    /* return recombined sums */
+    return adler | (sum2 << 16);
+}
+
+/* ========================================================================= */
+local uLong adler32_combine_(adler1, adler2, len2)
+    uLong adler1;
+    uLong adler2;
+    z_off64_t len2;
+{
+    unsigned long sum1;
+    unsigned long sum2;
+    unsigned rem;
+
+    /* the derivation of this formula is left as an exercise for the reader */
+    rem = (unsigned)(len2 % BASE);
+    sum1 = adler1 & 0xffff;
+    sum2 = rem * sum1;
+    MOD(sum2);
+    sum1 += (adler2 & 0xffff) + BASE - 1;
+    sum2 += ((adler1 >> 16) & 0xffff) + ((adler2 >> 16) & 0xffff) + BASE - rem;
+    if (sum1 >= BASE) sum1 -= BASE;
+    if (sum1 >= BASE) sum1 -= BASE;
+    if (sum2 >= (BASE << 1)) sum2 -= (BASE << 1);
+    if (sum2 >= BASE) sum2 -= BASE;
+    return sum1 | (sum2 << 16);
+}
+
+/* ========================================================================= */
+uLong ZEXPORT adler32_combine(adler1, adler2, len2)
+    uLong adler1;
+    uLong adler2;
+    z_off_t len2;
+{
+    return adler32_combine_(adler1, adler2, len2);
+}
+
+uLong ZEXPORT adler32_combine64(adler1, adler2, len2)
+    uLong adler1;
+    uLong adler2;
+    z_off64_t len2;
+{
+    return adler32_combine_(adler1, adler2, len2);
+}
diff --git a/src/plugins/cfitsio/buffers.c b/src/plugins/cfitsio/buffers.c
new file mode 100644
index 0000000..8d80f46
--- /dev/null
+++ b/src/plugins/cfitsio/buffers.c
@@ -0,0 +1,1371 @@
+/*  This file, buffers.c, contains the core set of FITSIO routines         */
+/*  that use or manage the internal set of IO buffers.                     */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+
+#include <string.h>
+#include <stdlib.h>
+#include "fitsio2.h"
+
+/*--------------------------------------------------------------------------*/
+int ffmbyt(fitsfile *fptr,    /* I - FITS file pointer                */
+           LONGLONG bytepos,     /* I - byte position in file to move to */
+           int err_mode,      /* I - 1=ignore error, 0 = return error */
+           int *status)       /* IO - error status                    */
+{
+/*
+  Move to the input byte location in the file.  When writing to a file, a move
+  may sometimes be made to a position beyond the current EOF.  The err_mode
+  parameter determines whether such conditions should be returned as an error
+  or simply ignored.
+*/
+    long record;
+
+    if (*status > 0)
+       return(*status);
+
+    if (bytepos < 0)
+        return(*status = NEG_FILE_POS);
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    record = (long) (bytepos / IOBUFLEN);  /* zero-indexed record number */
+
+    /* if this is not the current record, then load it */
+    if ( ((fptr->Fptr)->curbuf < 0) || 
+         (record != (fptr->Fptr)->bufrecnum[(fptr->Fptr)->curbuf])) 
+        ffldrc(fptr, record, err_mode, status);
+
+    if (*status <= 0)
+        (fptr->Fptr)->bytepos = bytepos;  /* save new file position */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpbyt(fitsfile *fptr,   /* I - FITS file pointer                    */
+           LONGLONG nbytes,      /* I - number of bytes to write             */
+           void *buffer,     /* I - buffer containing the bytes to write */
+           int *status)      /* IO - error status                        */
+/*
+  put (write) the buffer of bytes to the output FITS file, starting at
+  the current file position.  Write large blocks of data directly to disk;
+  write smaller segments to intermediate IO buffers to improve efficiency.
+*/
+{
+    int ii, nbuff;
+    LONGLONG filepos;
+    long recstart, recend;
+    long ntodo, bufpos, nspace, nwrite;
+    char *cptr;
+
+    if (*status > 0)
+       return(*status);
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    cptr = (char *)buffer;
+    ntodo =  (long) nbytes;
+
+    if ((fptr->Fptr)->curbuf < 0)  /* no current data buffer for this file */
+    {                              /* so reload the last one that was used */
+      ffldrc(fptr, (long) (((fptr->Fptr)->bytepos) / IOBUFLEN), REPORT_EOF, status);
+    }
+
+    if (nbytes >= MINDIRECT)
+    {
+      /* write large blocks of data directly to disk instead of via buffers */
+      /* first, fill up the current IO buffer before flushing it to disk */
+
+      nbuff = (fptr->Fptr)->curbuf;      /* current IO buffer number */
+      filepos = (fptr->Fptr)->bytepos;   /* save the write starting position */
+      recstart = (fptr->Fptr)->bufrecnum[nbuff];                 /* starting record */
+      recend = (long) ((filepos + nbytes - 1) / IOBUFLEN);  /* ending record   */
+
+      /* bufpos is the starting position within the IO buffer */
+      bufpos = (long) (filepos - ((LONGLONG)recstart * IOBUFLEN));
+      nspace = IOBUFLEN - bufpos;   /* amount of space left in the buffer */
+
+      if (nspace)
+      { /* fill up the IO buffer */
+        memcpy((fptr->Fptr)->iobuffer + (nbuff * IOBUFLEN) + bufpos, cptr, nspace);
+        ntodo -= nspace;           /* decrement remaining number of bytes */
+        cptr += nspace;            /* increment user buffer pointer */
+        filepos += nspace;         /* increment file position pointer */
+        (fptr->Fptr)->dirty[nbuff] = TRUE;       /* mark record as having been modified */
+      }
+
+      for (ii = 0; ii < NIOBUF; ii++) /* flush any affected buffers to disk */
+      {
+        if ((fptr->Fptr)->bufrecnum[ii] >= recstart
+            && (fptr->Fptr)->bufrecnum[ii] <= recend )
+        {
+          if ((fptr->Fptr)->dirty[ii])        /* flush modified buffer to disk */
+             ffbfwt(fptr->Fptr, ii, status);
+
+          (fptr->Fptr)->bufrecnum[ii] = -1;  /* disassociate buffer from the file */
+        }
+      }
+
+      /* move to the correct write position */
+      if ((fptr->Fptr)->io_pos != filepos)
+         ffseek(fptr->Fptr, filepos);
+
+      nwrite = ((ntodo - 1) / IOBUFLEN) * IOBUFLEN; /* don't write last buff */
+
+      ffwrite(fptr->Fptr, nwrite, cptr, status); /* write the data */
+      ntodo -= nwrite;                /* decrement remaining number of bytes */
+      cptr += nwrite;                  /* increment user buffer pointer */
+      (fptr->Fptr)->io_pos = filepos + nwrite; /* update the file position */
+
+      if ((fptr->Fptr)->io_pos >= (fptr->Fptr)->filesize) /* at the EOF? */
+      {
+        (fptr->Fptr)->filesize = (fptr->Fptr)->io_pos; /* increment file size */
+
+        /* initialize the current buffer with the correct fill value */
+        if ((fptr->Fptr)->hdutype == ASCII_TBL)
+          memset((fptr->Fptr)->iobuffer + (nbuff * IOBUFLEN), 32, IOBUFLEN);  /* blank fill */
+        else
+          memset((fptr->Fptr)->iobuffer + (nbuff * IOBUFLEN),  0, IOBUFLEN);  /* zero fill */
+      }
+      else
+      {
+        /* read next record */
+        ffread(fptr->Fptr, IOBUFLEN, (fptr->Fptr)->iobuffer + (nbuff * IOBUFLEN), status);
+        (fptr->Fptr)->io_pos += IOBUFLEN; 
+      }
+
+      /* copy remaining bytes from user buffer into current IO buffer */
+      memcpy((fptr->Fptr)->iobuffer + (nbuff * IOBUFLEN), cptr, ntodo);
+      (fptr->Fptr)->dirty[nbuff] = TRUE;       /* mark record as having been modified */
+      (fptr->Fptr)->bufrecnum[nbuff] = recend; /* record number */
+
+      (fptr->Fptr)->logfilesize = maxvalue((fptr->Fptr)->logfilesize, 
+                                       (LONGLONG)(recend + 1) * IOBUFLEN);
+      (fptr->Fptr)->bytepos = filepos + nwrite + ntodo;
+    }
+    else
+    {
+      /* bufpos is the starting position in IO buffer */
+      bufpos = (long) ((fptr->Fptr)->bytepos - ((LONGLONG)(fptr->Fptr)->bufrecnum[(fptr->Fptr)->curbuf] *
+               IOBUFLEN));
+      nspace = IOBUFLEN - bufpos;   /* amount of space left in the buffer */
+
+      while (ntodo)
+      {
+        nwrite = minvalue(ntodo, nspace);
+
+        /* copy bytes from user's buffer to the IO buffer */
+        memcpy((fptr->Fptr)->iobuffer + ((fptr->Fptr)->curbuf * IOBUFLEN) + bufpos, cptr, nwrite);
+        ntodo -= nwrite;            /* decrement remaining number of bytes */
+        cptr += nwrite;
+        (fptr->Fptr)->bytepos += nwrite;  /* increment file position pointer */
+        (fptr->Fptr)->dirty[(fptr->Fptr)->curbuf] = TRUE; /* mark record as modified */
+
+        if (ntodo)                  /* load next record into a buffer */
+        {
+          ffldrc(fptr, (long) ((fptr->Fptr)->bytepos / IOBUFLEN), IGNORE_EOF, status);
+          bufpos = 0;
+          nspace = IOBUFLEN;
+        }
+      }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpbytoff(fitsfile *fptr, /* I - FITS file pointer                   */
+           long gsize,        /* I - size of each group of bytes         */
+           long ngroups,      /* I - number of groups to write           */
+           long offset,       /* I - size of gap between groups          */
+           void *buffer,      /* I - buffer to be written                */
+           int *status)       /* IO - error status                       */
+/*
+  put (write) the buffer of bytes to the output FITS file, with an offset
+  between each group of bytes.  This function combines ffmbyt and ffpbyt
+  for increased efficiency.
+*/
+{
+    int bcurrent;
+    long ii, bufpos, nspace, nwrite, record;
+    char *cptr, *ioptr;
+
+    if (*status > 0)
+       return(*status);
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    if ((fptr->Fptr)->curbuf < 0)  /* no current data buffer for this file */
+    {                              /* so reload the last one that was used */
+      ffldrc(fptr, (long) (((fptr->Fptr)->bytepos) / IOBUFLEN), REPORT_EOF, status);
+    }
+
+    cptr = (char *)buffer;
+    bcurrent = (fptr->Fptr)->curbuf;     /* number of the current IO buffer */
+    record = (fptr->Fptr)->bufrecnum[bcurrent];  /* zero-indexed record number */
+    bufpos = (long) ((fptr->Fptr)->bytepos - ((LONGLONG)record * IOBUFLEN)); /* start pos */
+    nspace = IOBUFLEN - bufpos;  /* amount of space left in buffer */
+    ioptr = (fptr->Fptr)->iobuffer + (bcurrent * IOBUFLEN) + bufpos;  
+
+    for (ii = 1; ii < ngroups; ii++)  /* write all but the last group */
+    {
+      /* copy bytes from user's buffer to the IO buffer */
+      nwrite = minvalue(gsize, nspace);
+      memcpy(ioptr, cptr, nwrite);
+      cptr += nwrite;          /* increment buffer pointer */
+
+      if (nwrite < gsize)        /* entire group did not fit */
+      {
+        (fptr->Fptr)->dirty[bcurrent] = TRUE;  /* mark record as having been modified */
+        record++;
+        ffldrc(fptr, record, IGNORE_EOF, status);  /* load next record */
+        bcurrent = (fptr->Fptr)->curbuf;
+        ioptr   = (fptr->Fptr)->iobuffer + (bcurrent * IOBUFLEN);
+
+        nwrite  = gsize - nwrite;
+        memcpy(ioptr, cptr, nwrite);
+        cptr   += nwrite;            /* increment buffer pointer */
+        ioptr  += (offset + nwrite); /* increment IO buffer pointer */
+        nspace = IOBUFLEN - offset - nwrite;  /* amount of space left */
+      }
+      else
+      {
+        ioptr  += (offset + nwrite);  /* increment IO bufer pointer */
+        nspace -= (offset + nwrite);
+      }
+
+      if (nspace <= 0) /* beyond current record? */
+      {
+        (fptr->Fptr)->dirty[bcurrent] = TRUE;
+        record += ((IOBUFLEN - nspace) / IOBUFLEN); /* new record number */
+        ffldrc(fptr, record, IGNORE_EOF, status);
+        bcurrent = (fptr->Fptr)->curbuf;
+
+        bufpos = (-nspace) % IOBUFLEN; /* starting buffer pos */
+        nspace = IOBUFLEN - bufpos;
+        ioptr = (fptr->Fptr)->iobuffer + (bcurrent * IOBUFLEN) + bufpos;  
+      }
+    }
+      
+    /* now write the last group */
+    nwrite = minvalue(gsize, nspace);
+    memcpy(ioptr, cptr, nwrite);
+    cptr += nwrite;          /* increment buffer pointer */
+
+    if (nwrite < gsize)        /* entire group did not fit */
+    {
+      (fptr->Fptr)->dirty[bcurrent] = TRUE;  /* mark record as having been modified */
+      record++;
+      ffldrc(fptr, record, IGNORE_EOF, status);  /* load next record */
+      bcurrent = (fptr->Fptr)->curbuf;
+      ioptr   = (fptr->Fptr)->iobuffer + (bcurrent * IOBUFLEN);
+
+      nwrite  = gsize - nwrite;
+      memcpy(ioptr, cptr, nwrite);
+    }
+
+    (fptr->Fptr)->dirty[bcurrent] = TRUE;    /* mark record as having been modified */
+    (fptr->Fptr)->bytepos = (fptr->Fptr)->bytepos + (ngroups * gsize)
+                                  + (ngroups - 1) * offset;
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgbyt(fitsfile *fptr,    /* I - FITS file pointer             */
+           LONGLONG nbytes,       /* I - number of bytes to read       */
+           void *buffer,      /* O - buffer to read into           */
+           int *status)       /* IO - error status                 */
+/*
+  get (read) the requested number of bytes from the file, starting at
+  the current file position.  Read large blocks of data directly from disk;
+  read smaller segments via intermediate IO buffers to improve efficiency.
+*/
+{
+    int ii;
+    LONGLONG filepos;
+    long recstart, recend, ntodo, bufpos, nspace, nread;
+    char *cptr;
+
+    if (*status > 0)
+       return(*status);
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    cptr = (char *)buffer;
+
+    if (nbytes >= MINDIRECT)
+    {
+      /* read large blocks of data directly from disk instead of via buffers */
+      filepos = (fptr->Fptr)->bytepos; /* save the read starting position */
+
+/*  note that in this case, ffmbyt has not been called, and so        */
+/*  bufrecnum[(fptr->Fptr)->curbuf] does not point to the intended */
+/*  output buffer */
+
+      recstart = (long) (filepos / IOBUFLEN);               /* starting record */
+      recend = (long) ((filepos + nbytes - 1) / IOBUFLEN);  /* ending record   */
+
+      for (ii = 0; ii < NIOBUF; ii++) /* flush any affected buffers to disk */
+      {
+        if ((fptr->Fptr)->dirty[ii] && 
+            (fptr->Fptr)->bufrecnum[ii] >= recstart && (fptr->Fptr)->bufrecnum[ii] <= recend)
+            {
+              ffbfwt(fptr->Fptr, ii, status);    /* flush modified buffer to disk */
+            }
+      }
+
+       /* move to the correct read position */
+      if ((fptr->Fptr)->io_pos != filepos)
+         ffseek(fptr->Fptr, filepos);
+
+      ffread(fptr->Fptr, (long) nbytes, cptr, status); /* read the data */
+      (fptr->Fptr)->io_pos = filepos + nbytes; /* update the file position */
+    }
+    else
+    {
+      /* read small chucks of data using the IO buffers for efficiency */
+
+      if ((fptr->Fptr)->curbuf < 0)  /* no current data buffer for this file */
+      {                              /* so reload the last one that was used */
+        ffldrc(fptr, (long) (((fptr->Fptr)->bytepos) / IOBUFLEN), REPORT_EOF, status);
+      }
+
+      /* bufpos is the starting position in IO buffer */
+      bufpos = (long) ((fptr->Fptr)->bytepos - ((LONGLONG)(fptr->Fptr)->bufrecnum[(fptr->Fptr)->curbuf] *
+                IOBUFLEN));
+      nspace = IOBUFLEN - bufpos;   /* amount of space left in the buffer */
+
+      ntodo =  (long) nbytes;
+      while (ntodo)
+      {
+        nread  = minvalue(ntodo, nspace);
+
+        /* copy bytes from IO buffer to user's buffer */
+        memcpy(cptr, (fptr->Fptr)->iobuffer + ((fptr->Fptr)->curbuf * IOBUFLEN) + bufpos, nread);
+        ntodo -= nread;            /* decrement remaining number of bytes */
+        cptr  += nread;
+        (fptr->Fptr)->bytepos += nread;    /* increment file position pointer */
+
+        if (ntodo)                  /* load next record into a buffer */
+        {
+          ffldrc(fptr, (long) ((fptr->Fptr)->bytepos / IOBUFLEN), REPORT_EOF, status);
+          bufpos = 0;
+          nspace = IOBUFLEN;
+        }
+      }
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgbytoff(fitsfile *fptr, /* I - FITS file pointer                   */
+           long gsize,        /* I - size of each group of bytes         */
+           long ngroups,      /* I - number of groups to read            */
+           long offset,       /* I - size of gap between groups (may be < 0) */
+           void *buffer,      /* I - buffer to be filled                 */
+           int *status)       /* IO - error status                       */
+/*
+  get (read) the requested number of bytes from the file, starting at
+  the current file position.  This function combines ffmbyt and ffgbyt
+  for increased efficiency.
+*/
+{
+    int bcurrent;
+    long ii, bufpos, nspace, nread, record;
+    char *cptr, *ioptr;
+
+    if (*status > 0)
+       return(*status);
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    if ((fptr->Fptr)->curbuf < 0)  /* no current data buffer for this file */
+    {                              /* so reload the last one that was used */
+      ffldrc(fptr, (long) (((fptr->Fptr)->bytepos) / IOBUFLEN), REPORT_EOF, status);
+    }
+
+    cptr = (char *)buffer;
+    bcurrent = (fptr->Fptr)->curbuf;     /* number of the current IO buffer */
+    record = (fptr->Fptr)->bufrecnum[bcurrent];  /* zero-indexed record number */
+    bufpos = (long) ((fptr->Fptr)->bytepos - ((LONGLONG)record * IOBUFLEN)); /* start pos */
+    nspace = IOBUFLEN - bufpos;  /* amount of space left in buffer */
+    ioptr = (fptr->Fptr)->iobuffer + (bcurrent * IOBUFLEN) + bufpos;  
+
+    for (ii = 1; ii < ngroups; ii++)  /* read all but the last group */
+    {
+      /* copy bytes from IO buffer to the user's buffer */
+      nread = minvalue(gsize, nspace);
+      memcpy(cptr, ioptr, nread);
+      cptr += nread;          /* increment buffer pointer */
+
+      if (nread < gsize)        /* entire group did not fit */
+      {
+        record++;
+        ffldrc(fptr, record, REPORT_EOF, status);  /* load next record */
+        bcurrent = (fptr->Fptr)->curbuf;
+        ioptr   = (fptr->Fptr)->iobuffer + (bcurrent * IOBUFLEN);
+
+        nread  = gsize - nread;
+        memcpy(cptr, ioptr, nread);
+        cptr   += nread;            /* increment buffer pointer */
+        ioptr  += (offset + nread); /* increment IO buffer pointer */
+        nspace = IOBUFLEN - offset - nread;  /* amount of space left */
+      }
+      else
+      {
+        ioptr  += (offset + nread);  /* increment IO bufer pointer */
+        nspace -= (offset + nread);
+      }
+
+      if (nspace <= 0 || nspace > IOBUFLEN) /* beyond current record? */
+      {
+        if (nspace <= 0)
+        {
+          record += ((IOBUFLEN - nspace) / IOBUFLEN); /* new record number */
+          bufpos = (-nspace) % IOBUFLEN; /* starting buffer pos */
+        }
+        else
+        {
+          record -= ((nspace - 1 ) / IOBUFLEN); /* new record number */
+          bufpos = IOBUFLEN - (nspace % IOBUFLEN); /* starting buffer pos */
+        }
+
+        ffldrc(fptr, record, REPORT_EOF, status);
+        bcurrent = (fptr->Fptr)->curbuf;
+
+        nspace = IOBUFLEN - bufpos;
+        ioptr = (fptr->Fptr)->iobuffer + (bcurrent * IOBUFLEN) + bufpos;
+      }
+    }
+
+    /* now read the last group */
+    nread = minvalue(gsize, nspace);
+    memcpy(cptr, ioptr, nread);
+    cptr += nread;          /* increment buffer pointer */
+
+    if (nread < gsize)        /* entire group did not fit */
+    {
+      record++;
+      ffldrc(fptr, record, REPORT_EOF, status);  /* load next record */
+      bcurrent = (fptr->Fptr)->curbuf;
+      ioptr   = (fptr->Fptr)->iobuffer + (bcurrent * IOBUFLEN);
+
+      nread  = gsize - nread;
+      memcpy(cptr, ioptr, nread);
+    }
+
+    (fptr->Fptr)->bytepos = (fptr->Fptr)->bytepos + (ngroups * gsize)
+                                  + (ngroups - 1) * offset;
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffldrc(fitsfile *fptr,        /* I - FITS file pointer             */
+           long record,           /* I - record number to be loaded    */
+           int err_mode,          /* I - 1=ignore EOF, 0 = return EOF error */
+           int *status)           /* IO - error status                 */
+{
+/*
+  low-level routine to load a specified record from a file into
+  a physical buffer, if it is not already loaded.  Reset all
+  pointers to make this the new current record for that file.
+  Update ages of all the physical buffers.
+*/
+    int ibuff, nbuff;
+    LONGLONG rstart;
+
+    /* check if record is already loaded in one of the buffers */
+    /* search from youngest to oldest buffer for efficiency */
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    for (ibuff = NIOBUF - 1; ibuff >= 0; ibuff--)
+    {
+      nbuff = (fptr->Fptr)->ageindex[ibuff];
+      if (record == (fptr->Fptr)->bufrecnum[nbuff]) {
+         goto updatebuf;  /* use 'goto' for efficiency */
+      }
+    }
+
+    /* record is not already loaded */
+    rstart = (LONGLONG)record * IOBUFLEN;
+
+    if ( !err_mode && (rstart >= (fptr->Fptr)->logfilesize) )  /* EOF? */
+         return(*status = END_OF_FILE);
+
+    if (ffwhbf(fptr, &nbuff) < 0)  /* which buffer should we reuse? */
+       return(*status = TOO_MANY_FILES); 
+
+    if ((fptr->Fptr)->dirty[nbuff])
+       ffbfwt(fptr->Fptr, nbuff, status); /* write dirty buffer to disk */
+
+    if (rstart >= (fptr->Fptr)->filesize)  /* EOF? */
+    {
+      /* initialize an empty buffer with the correct fill value */
+      if ((fptr->Fptr)->hdutype == ASCII_TBL)
+         memset((fptr->Fptr)->iobuffer + (nbuff * IOBUFLEN), 32, IOBUFLEN); /* blank fill */
+      else
+         memset((fptr->Fptr)->iobuffer + (nbuff * IOBUFLEN),  0, IOBUFLEN);  /* zero fill */
+
+      (fptr->Fptr)->logfilesize = maxvalue((fptr->Fptr)->logfilesize, 
+              rstart + IOBUFLEN);
+
+      (fptr->Fptr)->dirty[nbuff] = TRUE;  /* mark record as having been modified */
+    }
+    else  /* not EOF, so read record from disk */
+    {
+      if ((fptr->Fptr)->io_pos != rstart)
+           ffseek(fptr->Fptr, rstart);
+
+      ffread(fptr->Fptr, IOBUFLEN, (fptr->Fptr)->iobuffer + (nbuff * IOBUFLEN), status);
+      (fptr->Fptr)->io_pos = rstart + IOBUFLEN;  /* set new IO position */
+    }
+
+    (fptr->Fptr)->bufrecnum[nbuff] = record;   /* record number contained in buffer */
+
+updatebuf:
+
+    (fptr->Fptr)->curbuf = nbuff; /* this is the current buffer for this file */
+
+    if (ibuff < 0)
+    { 
+      /* find the current position of the buffer in the age index */
+      for (ibuff = 0; ibuff < NIOBUF; ibuff++)
+         if ((fptr->Fptr)->ageindex[ibuff] == nbuff)
+            break;  
+    }
+
+    /* increment the age of all the buffers that were younger than it */
+    for (ibuff++; ibuff < NIOBUF; ibuff++)
+      (fptr->Fptr)->ageindex[ibuff - 1] = (fptr->Fptr)->ageindex[ibuff];
+
+    (fptr->Fptr)->ageindex[NIOBUF - 1] = nbuff; /* this is now the youngest buffer */
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffwhbf(fitsfile *fptr,        /* I - FITS file pointer             */
+           int *nbuff)            /* O - which buffer to use           */
+{
+/*
+  decide which buffer to (re)use to hold a new file record
+*/
+        return(*nbuff = (fptr->Fptr)->ageindex[0]);  /* return oldest buffer */
+}
+/*--------------------------------------------------------------------------*/
+int ffflus(fitsfile *fptr,   /* I - FITS file pointer                       */
+           int *status)      /* IO - error status                           */
+/*
+  Flush all the data in the current FITS file to disk. This ensures that if
+  the program subsequently dies, the disk FITS file will be closed correctly.
+*/
+{
+    int hdunum, hdutype;
+
+    if (*status > 0)
+        return(*status);
+
+    ffghdn(fptr, &hdunum);     /* get the current HDU number */
+
+    if (ffchdu(fptr,status) > 0)   /* close out the current HDU */
+        ffpmsg("ffflus could not close the current HDU.");
+
+    ffflsh(fptr, FALSE, status);  /* flush any modified IO buffers to disk */
+
+    if (ffgext(fptr, hdunum - 1, &hdutype, status) > 0) /* reopen HDU */
+        ffpmsg("ffflus could not reopen the current HDU.");
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffflsh(fitsfile *fptr,        /* I - FITS file pointer           */
+           int clearbuf,          /* I - also clear buffer contents? */
+           int *status)           /* IO - error status               */
+{
+/*
+  flush all dirty IO buffers associated with the file to disk
+*/
+    int ii;
+
+/*
+   no need to move to a different HDU
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+*/
+    for (ii = 0; ii < NIOBUF; ii++)
+    {
+	/* flush modified buffer to disk */
+        if ((fptr->Fptr)->bufrecnum[ii] >= 0 &&(fptr->Fptr)->dirty[ii])
+           ffbfwt(fptr->Fptr, ii, status);
+
+        if (clearbuf)
+          (fptr->Fptr)->bufrecnum[ii] = -1;  /* set contents of buffer as undefined */
+    }
+
+    if (*status != READONLY_FILE)
+      ffflushx(fptr->Fptr);  /* flush system buffers to disk */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffbfeof(fitsfile *fptr,        /* I - FITS file pointer           */
+           int *status)           /* IO - error status               */
+{
+/*
+  clear any buffers beyond the end of file
+*/
+    int ii;
+
+    for (ii = 0; ii < NIOBUF; ii++)
+    {
+        if ( (LONGLONG) (fptr->Fptr)->bufrecnum[ii] * IOBUFLEN >= fptr->Fptr->filesize)
+        {
+            (fptr->Fptr)->bufrecnum[ii] = -1;  /* set contents of buffer as undefined */
+        }
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffbfwt(FITSfile *Fptr,        /* I - FITS file pointer           */
+           int nbuff,             /* I - which buffer to write          */
+           int *status)           /* IO - error status                  */
+{
+/*
+  write contents of buffer to file;  If the position of the buffer
+  is beyond the current EOF, then the file may need to be extended
+  with fill values, and/or with the contents of some of the other
+  i/o buffers.
+*/
+    int  ii,ibuff;
+    long jj, irec, minrec, nloop;
+    LONGLONG filepos;
+
+    static char zeros[IOBUFLEN];  /*  initialized to zero by default */
+
+    if (!(Fptr->writemode) )
+    {
+        ffpmsg("Error: trying to write to READONLY file.");
+        if (Fptr->driver == 8) {  /* gzip compressed file */
+	  ffpmsg("Cannot write to a GZIP or COMPRESS compressed file.");
+	}
+        Fptr->dirty[nbuff] = FALSE;  /* reset buffer status to prevent later probs */
+        *status = READONLY_FILE;
+        return(*status);
+    }
+
+    filepos = (LONGLONG)Fptr->bufrecnum[nbuff] * IOBUFLEN;
+
+    if (filepos <= Fptr->filesize)
+    {
+      /* record is located within current file, so just write it */
+
+      /* move to the correct write position */
+      if (Fptr->io_pos != filepos)
+         ffseek(Fptr, filepos);
+
+      ffwrite(Fptr, IOBUFLEN, Fptr->iobuffer + (nbuff * IOBUFLEN), status);
+      Fptr->io_pos = filepos + IOBUFLEN;
+
+      if (filepos == Fptr->filesize)   /* appended new record? */
+         Fptr->filesize += IOBUFLEN;   /* increment the file size */
+
+      Fptr->dirty[nbuff] = FALSE;
+    }
+
+    else  /* if record is beyond the EOF, append any other records */ 
+          /* and/or insert fill values if necessary */
+    {
+      /* move to EOF */
+      if (Fptr->io_pos != Fptr->filesize)
+         ffseek(Fptr, Fptr->filesize);
+
+      ibuff = NIOBUF;  /* initialize to impossible value */
+      while(ibuff != nbuff) /* repeat until requested buffer is written */
+      {
+        minrec = (long) (Fptr->filesize / IOBUFLEN);
+
+        /* write lowest record beyond the EOF first */
+
+        irec = Fptr->bufrecnum[nbuff]; /* initially point to the requested buffer */
+        ibuff = nbuff;
+
+        for (ii = 0; ii < NIOBUF; ii++)
+        {
+          if (Fptr->bufrecnum[ii] >= minrec &&
+            Fptr->bufrecnum[ii] < irec)
+          {
+            irec = Fptr->bufrecnum[ii];  /* found a lower record */
+            ibuff = ii;
+          }
+        }
+
+        filepos = (LONGLONG)irec * IOBUFLEN;  /* byte offset of record in file */
+
+        /* append 1 or more fill records if necessary */
+        if (filepos > Fptr->filesize)
+        {                    
+          nloop = (long) ((filepos - (Fptr->filesize)) / IOBUFLEN); 
+          for (jj = 0; jj < nloop && !(*status); jj++)
+            ffwrite(Fptr, IOBUFLEN, zeros, status);
+
+/*
+ffseek(Fptr, filepos);
+*/
+          Fptr->filesize = filepos;   /* increment the file size */
+        } 
+
+        /* write the buffer itself */
+        ffwrite(Fptr, IOBUFLEN, Fptr->iobuffer + (ibuff * IOBUFLEN), status);
+        Fptr->dirty[ibuff] = FALSE;
+
+        Fptr->filesize += IOBUFLEN;     /* increment the file size */
+      } /* loop back if more buffers need to be written */
+
+      Fptr->io_pos = Fptr->filesize;  /* currently positioned at EOF */
+    }
+
+    return(*status);       
+}
+/*--------------------------------------------------------------------------*/
+int ffgrsz( fitsfile *fptr, /* I - FITS file pionter                        */
+            long *ndata,    /* O - optimal amount of data to access         */
+            int  *status)   /* IO - error status                            */
+/*
+  Returns an optimal value for the number of rows in a binary table
+  or the number of pixels in an image that should be read or written
+  at one time for maximum efficiency. Accessing more data than this
+  may cause excessive flushing and rereading of buffers to/from disk.
+*/
+{
+    int typecode, bytesperpixel;
+
+    /* There are NIOBUF internal buffers available each IOBUFLEN bytes long. */
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+      if ( ffrdef(fptr, status) > 0)   /* rescan header to get hdu struct */
+           return(*status);
+
+    if ((fptr->Fptr)->hdutype == IMAGE_HDU ) /* calc pixels per buffer size */
+    {
+      /* image pixels are in column 2 of the 'table' */
+      ffgtcl(fptr, 2, &typecode, NULL, NULL, status);
+      bytesperpixel = typecode / 10;
+      *ndata = ((NIOBUF - 1) * IOBUFLEN) / bytesperpixel;
+    }
+    else   /* calc number of rows that fit in buffers */
+    {
+      *ndata = (long) (((NIOBUF - 1) * IOBUFLEN) / maxvalue(1,
+               (fptr->Fptr)->rowlength));
+      *ndata = maxvalue(1, *ndata); 
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgtbb(fitsfile *fptr,        /* I - FITS file pointer                 */
+           LONGLONG firstrow,         /* I - starting row (1 = first row)      */
+           LONGLONG firstchar,        /* I - starting byte in row (1=first)    */
+           LONGLONG nchars,           /* I - number of bytes to read           */
+           unsigned char *values, /* I - array of bytes to read            */
+           int *status)           /* IO - error status                     */
+/*
+  read a consecutive string of bytes from an ascii or binary table.
+  This will span multiple rows of the table if nchars + firstchar is
+  greater than the length of a row.
+*/
+{
+    LONGLONG bytepos, endrow;
+
+    if (*status > 0 || nchars <= 0)
+        return(*status);
+
+    else if (firstrow < 1)
+        return(*status=BAD_ROW_NUM);
+
+    else if (firstchar < 1)
+        return(*status=BAD_ELEM_NUM);
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    /* check that we do not exceed number of rows in the table */
+    endrow = ((firstchar + nchars - 2) / (fptr->Fptr)->rowlength) + firstrow;
+    if (endrow > (fptr->Fptr)->numrows)
+    {
+        ffpmsg("attempt to read past end of table (ffgtbb)");
+        return(*status=BAD_ROW_NUM);
+    }
+
+    /* move the i/o pointer to the start of the sequence of characters */
+    bytepos = (fptr->Fptr)->datastart +
+              ((fptr->Fptr)->rowlength * (firstrow - 1)) +
+              firstchar - 1;
+
+    ffmbyt(fptr, bytepos, REPORT_EOF, status);
+    ffgbyt(fptr, nchars, values, status);  /* read the bytes */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgi1b(fitsfile *fptr, /* I - FITS file pointer                         */
+           LONGLONG byteloc,  /* I - position within file to start reading     */
+           long nvals,     /* I - number of pixels to read                  */
+           long incre,     /* I - byte increment between pixels             */
+           unsigned char *values, /* O - returned array of values           */
+           int *status)    /* IO - error status                             */
+/*
+  get (read) the array of values from the FITS file, doing machine dependent
+  format conversion (e.g. byte-swapping) if necessary.
+*/
+{
+    LONGLONG postemp;
+
+    if (incre == 1)      /* read all the values at once (contiguous bytes) */
+    {
+        if (nvals < MINDIRECT)  /* read normally via IO buffers */
+        {
+           ffmbyt(fptr, byteloc, REPORT_EOF, status);
+           ffgbyt(fptr, nvals, values, status);
+        }
+        else            /* read directly from disk, bypassing IO buffers */
+        {
+           postemp = (fptr->Fptr)->bytepos;   /* store current file position */
+           (fptr->Fptr)->bytepos = byteloc;   /* set to the desired position */
+           ffgbyt(fptr, nvals, values, status);
+           (fptr->Fptr)->bytepos = postemp;   /* reset to original position */
+        }
+    }
+    else         /* have to read each value individually (not contiguous ) */
+    {
+        ffmbyt(fptr, byteloc, REPORT_EOF, status);
+        ffgbytoff(fptr, 1, nvals, incre - 1, values, status);
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgi2b(fitsfile *fptr,  /* I - FITS file pointer                        */
+           LONGLONG byteloc,   /* I - position within file to start reading    */
+           long nvals,      /* I - number of pixels to read                 */
+           long incre,      /* I - byte increment between pixels            */
+           short *values,   /* O - returned array of values                 */
+           int *status)     /* IO - error status                            */
+/*
+  get (read) the array of values from the FITS file, doing machine dependent
+  format conversion (e.g. byte-swapping) if necessary.
+*/
+{
+    LONGLONG postemp;
+
+    if (incre == 2)      /* read all the values at once (contiguous bytes) */
+    {
+        if (nvals * 2 < MINDIRECT)  /* read normally via IO buffers */
+        {
+           ffmbyt(fptr, byteloc, REPORT_EOF, status);
+           ffgbyt(fptr, nvals * 2, values, status);
+        }
+        else            /* read directly from disk, bypassing IO buffers */
+        {
+           postemp = (fptr->Fptr)->bytepos;   /* store current file position */
+           (fptr->Fptr)->bytepos = byteloc;   /* set to the desired position */
+           ffgbyt(fptr, nvals * 2, values, status);
+           (fptr->Fptr)->bytepos = postemp;   /* reset to original position */
+        }
+    }
+    else         /* have to read each value individually (not contiguous ) */
+    {
+        ffmbyt(fptr, byteloc, REPORT_EOF, status);
+        ffgbytoff(fptr, 2, nvals, incre - 2, values, status);
+    }
+
+#if BYTESWAPPED
+    ffswap2(values, nvals);    /* reverse order of bytes in each value */
+#endif
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgi4b(fitsfile *fptr,  /* I - FITS file pointer                        */
+           LONGLONG byteloc,   /* I - position within file to start reading    */
+           long nvals,      /* I - number of pixels to read                 */
+           long incre,      /* I - byte increment between pixels            */
+           INT32BIT *values, /* O - returned array of values                */
+           int *status)     /* IO - error status                            */
+/*
+  get (read) the array of values from the FITS file, doing machine dependent
+  format conversion (e.g. byte-swapping) if necessary.
+*/
+{
+    LONGLONG postemp;
+
+    if (incre == 4)      /* read all the values at once (contiguous bytes) */
+    {
+        if (nvals * 4 < MINDIRECT)  /* read normally via IO buffers */
+        {
+           ffmbyt(fptr, byteloc, REPORT_EOF, status);
+           ffgbyt(fptr, nvals * 4, values, status);
+        }
+        else            /* read directly from disk, bypassing IO buffers */
+        {
+           postemp = (fptr->Fptr)->bytepos;   /* store current file position */
+           (fptr->Fptr)->bytepos = byteloc;   /* set to the desired position */
+           ffgbyt(fptr, nvals * 4, values, status);
+           (fptr->Fptr)->bytepos = postemp;   /* reset to original position */
+        }
+    }
+    else         /* have to read each value individually (not contiguous ) */
+    {
+        ffmbyt(fptr, byteloc, REPORT_EOF, status);
+        ffgbytoff(fptr, 4, nvals, incre - 4, values, status);
+    }
+
+#if BYTESWAPPED
+    ffswap4(values, nvals);    /* reverse order of bytes in each value */
+#endif
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgi8b(fitsfile *fptr,  /* I - FITS file pointer                        */
+           LONGLONG byteloc,   /* I - position within file to start reading    */
+           long nvals,      /* I - number of pixels to read                 */
+           long incre,      /* I - byte increment between pixels            */
+           long *values,  /* O - returned array of values                 */
+           int *status)     /* IO - error status                            */
+/*
+  get (read) the array of values from the FITS file, doing machine dependent
+  format conversion (e.g. byte-swapping) if necessary.
+
+  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+  This routine reads 'nvals' 8-byte integers into 'values'.
+  This works both on platforms that have sizeof(long) = 64, and 32,
+  as long as 'values' has been allocated to large enough to hold
+  8 * nvals bytes of data.
+  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+*/
+{
+    LONGLONG  postemp;
+
+    if (incre == 8)      /* read all the values at once (contiguous bytes) */
+    {
+        if (nvals * 8 < MINDIRECT)  /* read normally via IO buffers */
+        {
+           ffmbyt(fptr, byteloc, REPORT_EOF, status);
+           ffgbyt(fptr, nvals * 8, values, status);
+        }
+        else            /* read directly from disk, bypassing IO buffers */
+        {
+           postemp = (fptr->Fptr)->bytepos;   /* store current file position */
+           (fptr->Fptr)->bytepos = byteloc;   /* set to the desired position */
+           ffgbyt(fptr, nvals * 8, values, status);
+           (fptr->Fptr)->bytepos = postemp;   /* reset to original position */
+        }
+    }
+    else         /* have to read each value individually (not contiguous ) */
+    {
+        ffmbyt(fptr, byteloc, REPORT_EOF, status);
+        ffgbytoff(fptr, 8, nvals, incre - 8, values, status);
+    }
+
+#if BYTESWAPPED
+    ffswap8((double *) values, nvals); /* reverse bytes in each value */
+#endif
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgr4b(fitsfile *fptr,  /* I - FITS file pointer                        */
+           LONGLONG byteloc,   /* I - position within file to start reading    */
+           long nvals,      /* I - number of pixels to read                 */
+           long incre,      /* I - byte increment between pixels            */
+           float *values,   /* O - returned array of values                 */
+           int *status)     /* IO - error status                            */
+/*
+  get (read) the array of values from the FITS file, doing machine dependent
+  format conversion (e.g. byte-swapping) if necessary.
+*/
+{
+    LONGLONG postemp;
+
+#if MACHINE == VAXVMS
+    long ii;
+
+#elif (MACHINE == ALPHAVMS) && (FLOATTYPE == GFLOAT)
+    short *sptr;
+    long ii;
+
+#endif
+
+
+    if (incre == 4)      /* read all the values at once (contiguous bytes) */
+    {
+        if (nvals * 4 < MINDIRECT)  /* read normally via IO buffers */
+        {
+           ffmbyt(fptr, byteloc, REPORT_EOF, status);
+           ffgbyt(fptr, nvals * 4, values, status);
+        }
+        else            /* read directly from disk, bypassing IO buffers */
+        {
+           postemp = (fptr->Fptr)->bytepos;   /* store current file position */
+           (fptr->Fptr)->bytepos = byteloc;   /* set to the desired position */
+           ffgbyt(fptr, nvals * 4, values, status);
+           (fptr->Fptr)->bytepos = postemp;   /* reset to original position */
+        }
+    }
+    else         /* have to read each value individually (not contiguous ) */
+    {
+        ffmbyt(fptr, byteloc, REPORT_EOF, status);
+        ffgbytoff(fptr, 4, nvals, incre - 4, values, status);
+    }
+
+
+#if MACHINE == VAXVMS
+
+    ii = nvals;                      /* call VAX macro routine to convert */
+    ieevur(values, values, &ii);     /* from  IEEE float -> F float       */
+
+#elif (MACHINE == ALPHAVMS) && (FLOATTYPE == GFLOAT)
+
+    ffswap2( (short *) values, nvals * 2);  /* swap pairs of bytes */
+
+    /* convert from IEEE float format to VMS GFLOAT float format */
+    sptr = (short *) values;
+    for (ii = 0; ii < nvals; ii++, sptr += 2)
+    {
+        if (!fnan(*sptr) )  /* test for NaN or underflow */
+            values[ii] *= 4.0;
+    }
+
+#elif BYTESWAPPED
+    ffswap4((INT32BIT *)values, nvals);  /* reverse order of bytes in values */
+#endif
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgr8b(fitsfile *fptr,  /* I - FITS file pointer                        */
+           LONGLONG byteloc,   /* I - position within file to start reading    */
+           long nvals,      /* I - number of pixels to read                 */
+           long incre,      /* I - byte increment between pixels            */
+           double *values,  /* O - returned array of values                 */
+           int *status)     /* IO - error status                            */
+/*
+  get (read) the array of values from the FITS file, doing machine dependent
+  format conversion (e.g. byte-swapping) if necessary.
+*/
+{
+    LONGLONG  postemp;
+
+#if MACHINE == VAXVMS
+    long ii;
+
+#elif (MACHINE == ALPHAVMS) && (FLOATTYPE == GFLOAT)
+    short *sptr;
+    long ii;
+
+#endif
+
+    if (incre == 8)      /* read all the values at once (contiguous bytes) */
+    {
+        if (nvals * 8 < MINDIRECT)  /* read normally via IO buffers */
+        {
+           ffmbyt(fptr, byteloc, REPORT_EOF, status);
+           ffgbyt(fptr, nvals * 8, values, status);
+        }
+        else            /* read directly from disk, bypassing IO buffers */
+        {
+           postemp = (fptr->Fptr)->bytepos;   /* store current file position */
+           (fptr->Fptr)->bytepos = byteloc;   /* set to the desired position */
+           ffgbyt(fptr, nvals * 8, values, status);
+           (fptr->Fptr)->bytepos = postemp;   /* reset to original position */
+        }
+    }
+    else         /* have to read each value individually (not contiguous ) */
+    {
+        ffmbyt(fptr, byteloc, REPORT_EOF, status);
+        ffgbytoff(fptr, 8, nvals, incre - 8, values, status);
+    }
+
+#if MACHINE == VAXVMS
+    ii = nvals;                      /* call VAX macro routine to convert */
+    ieevud(values, values, &ii);     /* from  IEEE float -> D float       */
+
+#elif (MACHINE == ALPHAVMS) && (FLOATTYPE == GFLOAT)
+    ffswap2( (short *) values, nvals * 4);  /* swap pairs of bytes */
+
+    /* convert from IEEE float format to VMS GFLOAT float format */
+    sptr = (short *) values;
+    for (ii = 0; ii < nvals; ii++, sptr += 4)
+    {
+        if (!dnan(*sptr) )  /* test for NaN or underflow */
+            values[ii] *= 4.0;
+    }
+
+#elif BYTESWAPPED
+    ffswap8(values, nvals);   /* reverse order of bytes in each value */
+#endif
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffptbb(fitsfile *fptr,        /* I - FITS file pointer                 */
+           LONGLONG firstrow,         /* I - starting row (1 = first row)      */
+           LONGLONG firstchar,        /* I - starting byte in row (1=first)    */
+           LONGLONG nchars,           /* I - number of bytes to write          */
+           unsigned char *values, /* I - array of bytes to write           */
+           int *status)           /* IO - error status                     */
+/*
+  write a consecutive string of bytes to an ascii or binary table.
+  This will span multiple rows of the table if nchars + firstchar is
+  greater than the length of a row.
+*/
+{
+    LONGLONG bytepos, endrow, nrows;
+    char message[81];
+
+    if (*status > 0 || nchars <= 0)
+        return(*status);
+
+    else if (firstrow < 1)
+        return(*status=BAD_ROW_NUM);
+
+    else if (firstchar < 1)
+        return(*status=BAD_ELEM_NUM);
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    else if ((fptr->Fptr)->datastart < 0) /* rescan header if data undefined */
+        ffrdef(fptr, status);
+
+    endrow = ((firstchar + nchars - 2) / (fptr->Fptr)->rowlength) + firstrow;
+
+    /* check if we are writing beyond the current end of table */
+    if (endrow > (fptr->Fptr)->numrows)
+    {
+        /* if there are more HDUs following the current one, or */
+        /* if there is a data heap, then we must insert space */
+        /* for the new rows.  */
+        if ( !((fptr->Fptr)->lasthdu) || (fptr->Fptr)->heapsize > 0)
+        {
+            nrows = endrow - ((fptr->Fptr)->numrows);
+
+            /* ffirow also updates the heap address and numrows */
+            if (ffirow(fptr, (fptr->Fptr)->numrows, nrows, status) > 0)
+            {
+                 sprintf(message,
+                 "ffptbb failed to add space for %.0f new rows in table.",
+                         (double) nrows);
+                 ffpmsg(message);
+                 return(*status);
+            }
+        }
+        else
+        {
+            /* manally update heap starting address */
+            (fptr->Fptr)->heapstart += 
+            ((LONGLONG)(endrow - (fptr->Fptr)->numrows) * 
+                    (fptr->Fptr)->rowlength );
+
+            (fptr->Fptr)->numrows = endrow; /* update number of rows */
+        }
+    }
+
+    /* move the i/o pointer to the start of the sequence of characters */
+    bytepos = (fptr->Fptr)->datastart +
+              ((fptr->Fptr)->rowlength * (firstrow - 1)) +
+              firstchar - 1;
+
+    ffmbyt(fptr, bytepos, IGNORE_EOF, status);
+    ffpbyt(fptr, nchars, values, status);  /* write the bytes */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpi1b(fitsfile *fptr, /* I - FITS file pointer                         */
+           long nvals,     /* I - number of pixels in the values array      */
+           long incre,     /* I - byte increment between pixels             */
+           unsigned char *values, /* I - array of values to write           */
+           int *status)    /* IO - error status                             */
+/*
+  put (write) the array of values to the FITS file, doing machine dependent
+  format conversion (e.g. byte-swapping) if necessary.
+*/
+{
+    if (incre == 1)      /* write all the values at once (contiguous bytes) */
+
+        ffpbyt(fptr, nvals, values, status);
+
+    else         /* have to write each value individually (not contiguous ) */
+
+        ffpbytoff(fptr, 1, nvals, incre - 1, values, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpi2b(fitsfile *fptr, /* I - FITS file pointer                         */
+           long nvals,     /* I - number of pixels in the values array      */
+           long incre,     /* I - byte increment between pixels             */
+           short *values,  /* I - array of values to write                  */
+           int *status)    /* IO - error status                             */
+/*
+  put (write) the array of values to the FITS file, doing machine dependent
+  format conversion (e.g. byte-swapping) if necessary.
+*/
+{
+#if BYTESWAPPED
+    ffswap2(values, nvals);  /* reverse order of bytes in each value */
+#endif
+
+    if (incre == 2)      /* write all the values at once (contiguous bytes) */
+
+        ffpbyt(fptr, nvals * 2, values, status);
+
+    else         /* have to write each value individually (not contiguous ) */
+
+        ffpbytoff(fptr, 2, nvals, incre - 2, values, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpi4b(fitsfile *fptr, /* I - FITS file pointer                         */
+           long nvals,     /* I - number of pixels in the values array      */
+           long incre,     /* I - byte increment between pixels             */
+           INT32BIT *values, /* I - array of values to write                */
+           int *status)    /* IO - error status                             */
+/*
+  put (write) the array of values to the FITS file, doing machine dependent
+  format conversion (e.g. byte-swapping) if necessary.
+*/
+{
+#if BYTESWAPPED
+    ffswap4(values, nvals);    /* reverse order of bytes in each value */
+#endif
+
+    if (incre == 4)      /* write all the values at once (contiguous bytes) */
+
+        ffpbyt(fptr, nvals * 4, values, status);
+
+    else         /* have to write each value individually (not contiguous ) */
+
+        ffpbytoff(fptr, 4, nvals, incre - 4, values, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpi8b(fitsfile *fptr, /* I - FITS file pointer                         */
+           long nvals,     /* I - number of pixels in the values array      */
+           long incre,     /* I - byte increment between pixels             */
+           long *values,   /* I - array of values to write                */
+           int *status)    /* IO - error status                             */
+/*
+  put (write) the array of values to the FITS file, doing machine dependent
+  format conversion (e.g. byte-swapping) if necessary.
+
+  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+  This routine writes 'nvals' 8-byte integers from 'values'.
+  This works both on platforms that have sizeof(long) = 64, and 32,
+  as long as 'values' has been allocated to large enough to hold
+  8 * nvals bytes of data.
+  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+*/
+{
+#if BYTESWAPPED
+    ffswap8((double *) values, nvals);    /* reverse bytes in each value */
+#endif
+
+    if (incre == 8)      /* write all the values at once (contiguous bytes) */
+
+        ffpbyt(fptr, nvals * 8, values, status);
+
+    else         /* have to write each value individually (not contiguous ) */
+
+        ffpbytoff(fptr, 8, nvals, incre - 8, values, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpr4b(fitsfile *fptr, /* I - FITS file pointer                         */
+           long nvals,     /* I - number of pixels in the values array      */
+           long incre,     /* I - byte increment between pixels             */
+           float *values,  /* I - array of values to write                  */
+           int *status)    /* IO - error status                             */
+/*
+  put (write) the array of values to the FITS file, doing machine dependent
+  format conversion (e.g. byte-swapping) if necessary.
+*/
+{
+#if MACHINE == VAXVMS
+    long ii;
+
+    ii = nvals;                      /* call VAX macro routine to convert */
+    ieevpr(values, values, &ii);     /* from F float -> IEEE float        */
+
+#elif (MACHINE == ALPHAVMS) && (FLOATTYPE == GFLOAT)
+    long ii;
+
+    /* convert from VMS FFLOAT float format to IEEE float format */
+    for (ii = 0; ii < nvals; ii++)
+        values[ii] *= 0.25;
+
+    ffswap2( (short *) values, nvals * 2);  /* swap pairs of bytes */
+
+#elif BYTESWAPPED
+    ffswap4((INT32BIT *) values, nvals); /* reverse order of bytes in values */
+#endif
+
+    if (incre == 4)      /* write all the values at once (contiguous bytes) */
+
+        ffpbyt(fptr, nvals * 4, values, status);
+
+    else         /* have to write each value individually (not contiguous ) */
+
+        ffpbytoff(fptr, 4, nvals, incre - 4, values, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpr8b(fitsfile *fptr, /* I - FITS file pointer                         */
+           long nvals,     /* I - number of pixels in the values array      */
+           long incre,     /* I - byte increment between pixels             */
+           double *values, /* I - array of values to write                  */
+           int *status)    /* IO - error status                             */
+/*
+  put (write) the array of values to the FITS file, doing machine dependent
+  format conversion (e.g. byte-swapping) if necessary.
+*/
+{
+#if MACHINE == VAXVMS
+    long ii;
+
+    ii = nvals;                      /* call VAX macro routine to convert */
+    ieevpd(values, values, &ii);     /* from D float -> IEEE float        */
+
+#elif (MACHINE == ALPHAVMS) && (FLOATTYPE == GFLOAT)
+    long ii;
+
+    /* convert from VMS GFLOAT float format to IEEE float format */
+    for (ii = 0; ii < nvals; ii++)
+        values[ii] *= 0.25;
+
+    ffswap2( (short *) values, nvals * 4);  /* swap pairs of bytes */
+
+#elif BYTESWAPPED
+    ffswap8(values, nvals); /* reverse order of bytes in each value */
+#endif
+
+    if (incre == 8)      /* write all the values at once (contiguous bytes) */
+
+        ffpbyt(fptr, nvals * 8, values, status);
+
+    else         /* have to write each value individually (not contiguous ) */
+
+        ffpbytoff(fptr, 8, nvals, incre - 8, values, status);
+
+    return(*status);
+}
+
diff --git a/src/plugins/cfitsio/cfileio.c b/src/plugins/cfitsio/cfileio.c
new file mode 100644
index 0000000..ca081a0
--- /dev/null
+++ b/src/plugins/cfitsio/cfileio.c
@@ -0,0 +1,6859 @@
+/*  This file, cfileio.c, contains the low-level file access routines.     */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+
+#include <string.h>
+#include <stdlib.h>
+#include <math.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stddef.h>  /* apparently needed to define size_t */
+#include "fitsio2.h"
+#include "group.h"
+
+#define MAX_PREFIX_LEN 20  /* max length of file type prefix (e.g. 'http://') */
+#define MAX_DRIVERS 24     /* max number of file I/O drivers */
+
+typedef struct    /* structure containing pointers to I/O driver functions */ 
+{   char prefix[MAX_PREFIX_LEN];
+    int (*init)(void);
+    int (*shutdown)(void);
+    int (*setoptions)(int option);
+    int (*getoptions)(int *options);
+    int (*getversion)(int *version);
+    int (*checkfile)(char *urltype, char *infile, char *outfile);
+    int (*open)(char *filename, int rwmode, int *driverhandle);
+    int (*create)(char *filename, int *drivehandle);
+    int (*truncate)(int drivehandle, LONGLONG size);
+    int (*close)(int drivehandle);
+    int (*remove)(char *filename);
+    int (*size)(int drivehandle, LONGLONG *size);
+    int (*flush)(int drivehandle);
+    int (*seek)(int drivehandle, LONGLONG offset);
+    int (*read)(int drivehandle, void *buffer, long nbytes);
+    int (*write)(int drivehandle, void *buffer, long nbytes);
+} fitsdriver;
+
+fitsdriver driverTable[MAX_DRIVERS];  /* allocate driver tables */
+
+FITSfile *FptrTable[NMAXFILES];  /* this table of Fptr pointers is */
+                                 /* used by fits_already_open */
+
+int need_to_initialize = 1;    /* true if CFITSIO has not been initialized */
+int no_of_drivers = 0;         /* number of currently defined I/O drivers */
+
+static int pixel_filter_helper(fitsfile **fptr, char *outfile,
+				char *expr,  int *status);
+
+
+#ifdef _REENTRANT
+
+pthread_mutex_t Fitsio_InitLock = PTHREAD_MUTEX_INITIALIZER;
+
+#endif
+
+/*--------------------------------------------------------------------------*/
+int fitsio_init_lock(void)
+{
+  static int need_to_init = 1;
+  
+#ifdef _REENTRANT
+
+  pthread_mutexattr_t mutex_init;
+
+  FFLOCK1(Fitsio_InitLock);
+
+  if (need_to_init) {
+
+    /* Init the main fitsio lock here since we need a a recursive lock */
+
+    assert(!pthread_mutexattr_init(&mutex_init));
+#ifdef linux
+    assert(!pthread_mutexattr_settype(&mutex_init,
+				     PTHREAD_MUTEX_RECURSIVE_NP));
+#else
+    assert(!pthread_mutexattr_settype(&mutex_init,
+				     PTHREAD_MUTEX_RECURSIVE));
+#endif
+
+    assert(!pthread_mutex_init(&Fitsio_Lock,&mutex_init));
+    need_to_init = 0;
+  }
+
+  FFUNLOCK1(Fitsio_InitLock);
+
+#endif
+
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int ffomem(fitsfile **fptr,      /* O - FITS file pointer                   */ 
+           const char *name,     /* I - name of file to open                */
+           int mode,             /* I - 0 = open readonly; 1 = read/write   */
+           void **buffptr,       /* I - address of memory pointer           */
+           size_t *buffsize,     /* I - size of buffer, in bytes            */
+           size_t deltasize,     /* I - increment for future realloc's      */
+           void *(*mem_realloc)(void *p, size_t newsize), /* function       */
+           int *status)          /* IO - error status                       */
+/*
+  Open an existing FITS file in core memory.  This is a specialized version
+  of ffopen.
+*/
+{
+    int ii, driver, handle, hdutyp, slen, movetotype, extvers, extnum;
+    char extname[FLEN_VALUE];
+    LONGLONG filesize;
+    char urltype[MAX_PREFIX_LEN], infile[FLEN_FILENAME], outfile[FLEN_FILENAME];
+    char extspec[FLEN_FILENAME], rowfilter[FLEN_FILENAME];
+    char binspec[FLEN_FILENAME], colspec[FLEN_FILENAME];
+    char imagecolname[FLEN_VALUE], rowexpress[FLEN_FILENAME];
+    char *url, errmsg[FLEN_ERRMSG];
+    char *hdtype[3] = {"IMAGE", "TABLE", "BINTABLE"};
+
+    if (*status > 0)
+        return(*status);
+
+    *fptr = 0;                   /* initialize null file pointer */
+
+    if (need_to_initialize)           /* this is called only once */
+    {
+        *status = fits_init_cfitsio();
+
+        if (*status > 0)
+            return(*status);
+    }
+
+    url = (char *) name;
+    while (*url == ' ')  /* ignore leading spaces in the file spec */
+        url++;
+
+        /* parse the input file specification */
+    fits_parse_input_url(url, urltype, infile, outfile, extspec,
+              rowfilter, binspec, colspec, status);
+
+    strcpy(urltype, "memkeep://");   /* URL type for pre-existing memory file */
+
+    *status = urltype2driver(urltype, &driver);
+
+    if (*status > 0)
+    {
+        ffpmsg("could not find driver for pre-existing memory file: (ffomem)");
+        return(*status);
+    }
+
+    /* call driver routine to open the memory file */
+    FFLOCK;  /* lock this while searching for vacant handle */
+    *status =   mem_openmem( buffptr, buffsize,deltasize,
+                            mem_realloc,  &handle);
+    FFUNLOCK;
+
+    if (*status > 0)
+    {
+         ffpmsg("failed to open pre-existing memory file: (ffomem)");
+         return(*status);
+    }
+
+        /* get initial file size */
+    *status = (*driverTable[driver].size)(handle, &filesize);
+
+    if (*status > 0)
+    {
+        (*driverTable[driver].close)(handle);  /* close the file */
+        ffpmsg("failed get the size of the memory file: (ffomem)");
+        return(*status);
+    }
+
+        /* allocate fitsfile structure and initialize = 0 */
+    *fptr = (fitsfile *) calloc(1, sizeof(fitsfile));
+
+    if (!(*fptr))
+    {
+        (*driverTable[driver].close)(handle);  /* close the file */
+        ffpmsg("failed to allocate structure for following file: (ffomem)");
+        ffpmsg(url);
+        return(*status = MEMORY_ALLOCATION);
+    }
+
+        /* allocate FITSfile structure and initialize = 0 */
+    (*fptr)->Fptr = (FITSfile *) calloc(1, sizeof(FITSfile));
+
+    if (!((*fptr)->Fptr))
+    {
+        (*driverTable[driver].close)(handle);  /* close the file */
+        ffpmsg("failed to allocate structure for following file: (ffomem)");
+        ffpmsg(url);
+        free(*fptr);
+        *fptr = 0;       
+        return(*status = MEMORY_ALLOCATION);
+    }
+
+    slen = strlen(url) + 1;
+    slen = maxvalue(slen, 32); /* reserve at least 32 chars */ 
+    ((*fptr)->Fptr)->filename = (char *) malloc(slen); /* mem for file name */
+
+    if ( !(((*fptr)->Fptr)->filename) )
+    {
+        (*driverTable[driver].close)(handle);  /* close the file */
+        ffpmsg("failed to allocate memory for filename: (ffomem)");
+        ffpmsg(url);
+        free((*fptr)->Fptr);
+        free(*fptr);
+        *fptr = 0;              /* return null file pointer */
+        return(*status = MEMORY_ALLOCATION);
+    }
+
+    /* mem for headstart array */
+    ((*fptr)->Fptr)->headstart = (LONGLONG *) calloc(1001, sizeof(LONGLONG)); 
+
+    if ( !(((*fptr)->Fptr)->headstart) )
+    {
+        (*driverTable[driver].close)(handle);  /* close the file */
+        ffpmsg("failed to allocate memory for headstart array: (ffomem)");
+        ffpmsg(url);
+        free( ((*fptr)->Fptr)->filename);
+        free((*fptr)->Fptr);
+        free(*fptr);
+        *fptr = 0;              /* return null file pointer */
+        return(*status = MEMORY_ALLOCATION);
+    }
+
+    /* mem for file I/O buffers */
+    ((*fptr)->Fptr)->iobuffer = (char *) calloc(NIOBUF, IOBUFLEN);
+
+    if ( !(((*fptr)->Fptr)->iobuffer) )
+    {
+        (*driverTable[driver].close)(handle);  /* close the file */
+        ffpmsg("failed to allocate memory for iobuffer array: (ffomem)");
+        ffpmsg(url);
+        free( ((*fptr)->Fptr)->headstart);    /* free memory for headstart array */
+        free( ((*fptr)->Fptr)->filename);
+        free((*fptr)->Fptr);
+        free(*fptr);
+        *fptr = 0;              /* return null file pointer */
+        return(*status = MEMORY_ALLOCATION);
+    }
+
+    /* initialize the ageindex array (relative age of the I/O buffers) */
+    /* and initialize the bufrecnum array as being empty */
+    for (ii = 0; ii < NIOBUF; ii++)  {
+        ((*fptr)->Fptr)->ageindex[ii] = ii;
+        ((*fptr)->Fptr)->bufrecnum[ii] = -1;
+    }
+
+        /* store the parameters describing the file */
+    ((*fptr)->Fptr)->MAXHDU = 1000;              /* initial size of headstart */
+    ((*fptr)->Fptr)->filehandle = handle;        /* file handle */
+    ((*fptr)->Fptr)->driver = driver;            /* driver number */
+    strcpy(((*fptr)->Fptr)->filename, url);      /* full input filename */
+    ((*fptr)->Fptr)->filesize = filesize;        /* physical file size */
+    ((*fptr)->Fptr)->logfilesize = filesize;     /* logical file size */
+    ((*fptr)->Fptr)->writemode = mode;      /* read-write mode    */
+    ((*fptr)->Fptr)->datastart = DATA_UNDEFINED; /* unknown start of data */
+    ((*fptr)->Fptr)->curbuf = -1;             /* undefined current IO buffer */
+    ((*fptr)->Fptr)->open_count = 1;     /* structure is currently used once */
+    ((*fptr)->Fptr)->validcode = VALIDSTRUC; /* flag denoting valid structure */
+
+    ffldrc(*fptr, 0, REPORT_EOF, status);     /* load first record */
+
+    fits_store_Fptr( (*fptr)->Fptr, status);  /* store Fptr address */
+
+    if (ffrhdu(*fptr, &hdutyp, status) > 0)  /* determine HDU structure */
+    {
+        ffpmsg(
+          "ffomem could not interpret primary array header of file: (ffomem)");
+        ffpmsg(url);
+
+        if (*status == UNKNOWN_REC)
+           ffpmsg("This does not look like a FITS file.");
+
+        ffclos(*fptr, status);
+        *fptr = 0;              /* return null file pointer */
+    }
+
+    /* ---------------------------------------------------------- */
+    /* move to desired extension, if specified as part of the URL */
+    /* ---------------------------------------------------------- */
+
+    imagecolname[0] = '\0';
+    rowexpress[0] = '\0';
+
+    if (*extspec)
+    {
+       /* parse the extension specifier into individual parameters */
+       ffexts(extspec, &extnum, 
+         extname, &extvers, &movetotype, imagecolname, rowexpress, status);
+
+
+      if (*status > 0)
+          return(*status);
+
+      if (extnum)
+      {
+        ffmahd(*fptr, extnum + 1, &hdutyp, status);
+      }
+      else if (*extname) /* move to named extension, if specified */
+      {
+        ffmnhd(*fptr, movetotype, extname, extvers, status);
+      }
+
+      if (*status > 0)
+      {
+        ffpmsg("ffomem could not move to the specified extension:");
+        if (extnum > 0)
+        {
+          sprintf(errmsg,
+          " extension number %d doesn't exist or couldn't be opened.",extnum);
+          ffpmsg(errmsg);
+        }
+        else
+        {
+          sprintf(errmsg,
+          " extension with EXTNAME = %s,", extname);
+          ffpmsg(errmsg);
+
+          if (extvers)
+          {
+             sprintf(errmsg,
+             "           and with EXTVERS = %d,", extvers);
+             ffpmsg(errmsg);
+          }
+
+          if (movetotype != ANY_HDU)
+          {
+             sprintf(errmsg,
+             "           and with XTENSION = %s,", hdtype[movetotype]);
+             ffpmsg(errmsg);
+          }
+
+          ffpmsg(" doesn't exist or couldn't be opened.");
+        }
+        return(*status);
+      }
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffdkopn(fitsfile **fptr,      /* O - FITS file pointer                   */ 
+           const char *name,     /* I - full name of file to open           */
+           int mode,             /* I - 0 = open readonly; 1 = read/write   */
+           int *status)          /* IO - error status                       */
+/*
+  Open an existing FITS file on magnetic disk with either readonly or 
+  read/write access.  The routine does not support CFITSIO's extended
+  filename syntax and simply uses the entire input 'name' string as
+  the name of the file.
+*/
+{
+    if (*status > 0)
+        return(*status);
+
+    *status = OPEN_DISK_FILE;
+
+    ffopen(fptr, name, mode, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffdopn(fitsfile **fptr,      /* O - FITS file pointer                   */ 
+           const char *name,     /* I - full name of file to open           */
+           int mode,             /* I - 0 = open readonly; 1 = read/write   */
+           int *status)          /* IO - error status                       */
+/*
+  Open an existing FITS file with either readonly or read/write access. and
+  move to the first HDU that contains 'interesting' data, if the primary
+  array contains a null image (i.e., NAXIS = 0). 
+*/
+{
+    if (*status > 0)
+        return(*status);
+
+    *status = SKIP_NULL_PRIMARY;
+
+    ffopen(fptr, name, mode, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fftopn(fitsfile **fptr,      /* O - FITS file pointer                   */ 
+           const char *name,     /* I - full name of file to open           */
+           int mode,             /* I - 0 = open readonly; 1 = read/write   */
+           int *status)          /* IO - error status                       */
+/*
+  Open an existing FITS file with either readonly or read/write access. and
+  move to the first HDU that contains 'interesting' table (not an image). 
+*/
+{
+    int hdutype;
+
+    if (*status > 0)
+        return(*status);
+
+    *status = SKIP_IMAGE;
+
+    ffopen(fptr, name, mode, status);
+
+    if (ffghdt(*fptr, &hdutype, status) <= 0) {
+        if (hdutype == IMAGE_HDU)
+            *status = NOT_TABLE;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffiopn(fitsfile **fptr,      /* O - FITS file pointer                   */ 
+           const char *name,     /* I - full name of file to open           */
+           int mode,             /* I - 0 = open readonly; 1 = read/write   */
+           int *status)          /* IO - error status                       */
+/*
+  Open an existing FITS file with either readonly or read/write access. and
+  move to the first HDU that contains 'interesting' image (not an table). 
+*/
+{
+    int hdutype;
+
+    if (*status > 0)
+        return(*status);
+
+    *status = SKIP_TABLE;
+
+    ffopen(fptr, name, mode, status);
+
+    if (ffghdt(*fptr, &hdutype, status) <= 0) {
+        if (hdutype != IMAGE_HDU)
+            *status = NOT_IMAGE;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffopentest(double version,   /* I - CFITSIO version number, from the    */
+                                 /*     application program (fitsio.h file) */
+           fitsfile **fptr,      /* O - FITS file pointer                   */ 
+           const char *name,     /* I - full name of file to open           */
+           int mode,             /* I - 0 = open readonly; 1 = read/write   */
+           int *status)          /* IO - error status                       */
+/*
+  Open an existing FITS file with either readonly or read/write access.
+  First test that the version of fitsio.h used to build the CFITSIO library
+  is the same as the version used in building the application program that
+  links to the library.
+*/
+{
+    if (version != CFITSIO_VERSION)
+    {
+        printf("ERROR: Mismatch in the version of the fitsio.h include file used to build\n");
+	printf("the CFITSIO library, and the version included by the application program:\n");
+	printf("   Version used to build the CFITSIO library   = %f\n",CFITSIO_VERSION);
+	printf("   Version included by the application program = %f\n",version);
+	
+        *status = FILE_NOT_OPENED;
+	return(*status);
+    }
+
+    /* now call the normal file open routine */
+    ffopen(fptr, name, mode, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffopen(fitsfile **fptr,      /* O - FITS file pointer                   */ 
+           const char *name,     /* I - full name of file to open           */
+           int mode,             /* I - 0 = open readonly; 1 = read/write   */
+           int *status)          /* IO - error status                       */
+/*
+  Open an existing FITS file with either readonly or read/write access.
+*/
+{
+    fitsfile *newptr;
+    int  ii, driver, hdutyp, hdunum, slen, writecopy, isopen;
+    LONGLONG filesize;
+    long rownum, nrows, goodrows;
+    int extnum, extvers, handle, movetotype, tstatus = 0, only_one = 0;
+    char urltype[MAX_PREFIX_LEN], infile[FLEN_FILENAME], outfile[FLEN_FILENAME];
+    char origurltype[MAX_PREFIX_LEN], extspec[FLEN_FILENAME];
+    char extname[FLEN_VALUE], rowfilter[FLEN_FILENAME], tblname[FLEN_VALUE];
+    char imagecolname[FLEN_VALUE], rowexpress[FLEN_FILENAME];
+    char binspec[FLEN_FILENAME], colspec[FLEN_FILENAME], pixfilter[FLEN_FILENAME];
+    char histfilename[FLEN_FILENAME];
+    char filtfilename[FLEN_FILENAME];
+    char wtcol[FLEN_VALUE];
+    char minname[4][FLEN_VALUE], maxname[4][FLEN_VALUE];
+    char binname[4][FLEN_VALUE];
+
+    char *url;
+    double minin[4], maxin[4], binsizein[4], weight;
+    int imagetype, naxis = 1, haxis, recip;
+    int skip_null = 0, skip_image = 0, skip_table = 0, open_disk_file = 0;
+    char colname[4][FLEN_VALUE];
+    char errmsg[FLEN_ERRMSG];
+    char *hdtype[3] = {"IMAGE", "TABLE", "BINTABLE"};
+    char *rowselect = 0;
+
+    if (*status > 0)
+        return(*status);
+
+    if (*status == SKIP_NULL_PRIMARY)
+    {
+      /* this special status value is used as a flag by ffdopn to tell */
+      /* ffopen to skip over a null primary array when opening the file. */
+
+       skip_null = 1;
+       *status = 0;
+    }
+    else if (*status == SKIP_IMAGE)
+    {
+      /* this special status value is used as a flag by fftopn to tell */
+      /* ffopen to move to 1st significant table when opening the file. */
+
+       skip_image = 1;
+       *status = 0;
+    }
+    else if (*status == SKIP_TABLE)
+    {
+      /* this special status value is used as a flag by ffiopn to tell */
+      /* ffopen to move to 1st significant image when opening the file. */
+
+       skip_table = 1;
+       *status = 0;
+    }
+    else if (*status == OPEN_DISK_FILE)
+    {
+      /* this special status value is used as a flag by ffdkopn to tell */
+      /* ffopen to not interpret the input filename using CFITSIO's    */
+      /* extended filename syntax, and simply open the specified disk file */
+
+       open_disk_file = 1;
+       *status = 0;
+    }
+    
+    *fptr = 0;              /* initialize null file pointer */
+    writecopy = 0;  /* have we made a write-able copy of the input file? */
+
+    if (need_to_initialize) {          /* this is called only once */
+       *status = fits_init_cfitsio();
+    }
+    
+    if (*status > 0)
+        return(*status);
+
+    url = (char *) name;
+    while (*url == ' ')  /* ignore leading spaces in the filename */
+        url++;
+
+    if (*url == '\0')
+    {
+        ffpmsg("Name of file to open is blank. (ffopen)");
+        return(*status = FILE_NOT_OPENED);
+    }
+
+    if (open_disk_file)
+    {
+      /* treat the input URL literally as the name of the file to open */
+      /* and don't try to parse the URL using the extended filename syntax */
+      
+        if (strlen(url) > FLEN_FILENAME - 1) {
+            ffpmsg("Name of file to open is too long. (ffopen)");
+            return(*status = FILE_NOT_OPENED);
+        }
+
+        strcpy(infile,url);
+        strcpy(urltype, "file://");
+        outfile[0] = '\0';
+        extspec[0] = '\0';
+        binspec[0] = '\0';
+        colspec[0] = '\0';
+        rowfilter[0] = '\0';
+        pixfilter[0] = '\0';
+    }
+    else
+    {
+        /* parse the input file specification */
+
+        /* NOTE: This routine tests that all the strings do not */
+	/* overflow the standard buffer sizes (FLEN_FILENAME, etc.) */
+	/* therefore in general we do not have to worry about buffer */
+	/* overflow of any of the returned strings. */
+	
+        fits_parse_input_filename(url, urltype, infile, outfile, extspec,
+              rowfilter, binspec, colspec, pixfilter, status);
+    }
+    
+    if (*status > 0)
+    {
+        ffpmsg("could not parse the input filename: (ffopen)");
+        ffpmsg(url);
+        return(*status);
+    }
+
+    imagecolname[0] = '\0';
+    rowexpress[0] = '\0';
+
+    if (*extspec)
+    {
+       slen = strlen(extspec);
+       if (extspec[slen - 1] == '#') {  /* special symbol to mean only copy this extension */
+           extspec[slen - 1] = '\0';
+	   only_one = 1;
+       }
+
+       /* parse the extension specifier into individual parameters */
+       ffexts(extspec, &extnum, 
+         extname, &extvers, &movetotype, imagecolname, rowexpress, status);
+
+      if (*status > 0)
+          return(*status);
+    }
+
+    /*-------------------------------------------------------------------*/
+    /* special cases:                                                    */
+    /*-------------------------------------------------------------------*/
+
+    histfilename[0] = '\0';
+    filtfilename[0] = '\0';
+    if (*outfile && (*binspec || *imagecolname || *pixfilter))
+    {
+        /* if binspec or imagecolumn are specified, then the  */
+        /* output file name is intended for the final image,  */
+        /* and not a copy of the input file.                  */
+
+        strcpy(histfilename, outfile);
+        outfile[0] = '\0';
+    }
+    else if (*outfile && (*rowfilter || *colspec))
+    {
+        /* if rowfilter or colspece are specified, then the    */
+        /* output file name is intended for the filtered file  */
+        /* and not a copy of the input file.                   */
+
+        strcpy(filtfilename, outfile);
+        outfile[0] = '\0';
+    }
+
+    /*-------------------------------------------------------------------*/
+    /* check if this same file is already open, and if so, attach to it  */
+    /*-------------------------------------------------------------------*/
+
+    FFLOCK;
+    if (fits_already_open(fptr, url, urltype, infile, extspec, rowfilter,
+            binspec, colspec, mode, &isopen, status) > 0)
+    {
+        FFUNLOCK;
+        return(*status);
+    }
+    FFUNLOCK;
+
+    if (isopen) {
+       goto move2hdu;  
+    }
+
+    /* get the driver number corresponding to this urltype */
+    *status = urltype2driver(urltype, &driver);
+
+    if (*status > 0)
+    {
+        ffpmsg("could not find driver for this file: (ffopen)");
+        ffpmsg(urltype);
+        ffpmsg(url);
+        return(*status);
+    }
+
+    /*-------------------------------------------------------------------
+        deal with all those messy special cases which may require that
+        a different driver be used:
+            - is disk file compressed?
+            - are ftp:, gsiftp:, or http: files compressed?
+            - has user requested that a local copy be made of
+              the ftp or http file?
+      -------------------------------------------------------------------*/
+
+    if (driverTable[driver].checkfile)
+    {
+        strcpy(origurltype,urltype);  /* Save the urltype */
+
+        /* 'checkfile' may modify the urltype, infile and outfile strings */
+        *status =  (*driverTable[driver].checkfile)(urltype, infile, outfile);
+
+        if (*status)
+        {
+            ffpmsg("checkfile failed for this file: (ffopen)");
+            ffpmsg(url);
+            return(*status);
+        }
+
+        if (strcmp(origurltype, urltype))  /* did driver changed on us? */
+        {
+            *status = urltype2driver(urltype, &driver);
+            if (*status > 0)
+            {
+                ffpmsg("could not change driver for this file: (ffopen)");
+                ffpmsg(url);
+                ffpmsg(urltype);
+                return(*status);
+            }
+        }
+    }
+
+    /* call appropriate driver to open the file */
+    if (driverTable[driver].open)
+    {
+        FFLOCK;  /* lock this while searching for vacant handle */
+        *status =  (*driverTable[driver].open)(infile, mode, &handle);
+        FFUNLOCK;
+        if (*status > 0)
+        {
+            ffpmsg("failed to find or open the following file: (ffopen)");
+            ffpmsg(url);
+            return(*status);
+       }
+    }
+    else
+    {
+        ffpmsg("cannot open an existing file of this type: (ffopen)");
+        ffpmsg(url);
+        return(*status = FILE_NOT_OPENED);
+    }
+
+        /* get initial file size */
+    *status = (*driverTable[driver].size)(handle, &filesize);
+    if (*status > 0)
+    {
+        (*driverTable[driver].close)(handle);  /* close the file */
+        ffpmsg("failed get the size of the following file: (ffopen)");
+        ffpmsg(url);
+        return(*status);
+    }
+
+        /* allocate fitsfile structure and initialize = 0 */
+    *fptr = (fitsfile *) calloc(1, sizeof(fitsfile));
+
+    if (!(*fptr))
+    {
+        (*driverTable[driver].close)(handle);  /* close the file */
+        ffpmsg("failed to allocate structure for following file: (ffopen)");
+        ffpmsg(url);
+        return(*status = MEMORY_ALLOCATION);
+    }
+
+        /* allocate FITSfile structure and initialize = 0 */
+    (*fptr)->Fptr = (FITSfile *) calloc(1, sizeof(FITSfile));
+
+    if (!((*fptr)->Fptr))
+    {
+        (*driverTable[driver].close)(handle);  /* close the file */
+        ffpmsg("failed to allocate structure for following file: (ffopen)");
+        ffpmsg(url);
+        free(*fptr);
+        *fptr = 0;       
+        return(*status = MEMORY_ALLOCATION);
+    }
+
+    slen = strlen(url) + 1;
+    slen = maxvalue(slen, 32); /* reserve at least 32 chars */ 
+    ((*fptr)->Fptr)->filename = (char *) malloc(slen); /* mem for file name */
+
+    if ( !(((*fptr)->Fptr)->filename) )
+    {
+        (*driverTable[driver].close)(handle);  /* close the file */
+        ffpmsg("failed to allocate memory for filename: (ffopen)");
+        ffpmsg(url);
+        free((*fptr)->Fptr);
+        free(*fptr);
+        *fptr = 0;              /* return null file pointer */
+        return(*status = MEMORY_ALLOCATION);
+    }
+
+    /* mem for headstart array */
+    ((*fptr)->Fptr)->headstart = (LONGLONG *) calloc(1001, sizeof(LONGLONG));
+
+    if ( !(((*fptr)->Fptr)->headstart) )
+    {
+        (*driverTable[driver].close)(handle);  /* close the file */
+        ffpmsg("failed to allocate memory for headstart array: (ffopen)");
+        ffpmsg(url);
+        free( ((*fptr)->Fptr)->filename);
+        free((*fptr)->Fptr);
+        free(*fptr);
+        *fptr = 0;              /* return null file pointer */
+        return(*status = MEMORY_ALLOCATION);
+    }
+
+    /* mem for file I/O buffers */
+    ((*fptr)->Fptr)->iobuffer = (char *) calloc(NIOBUF, IOBUFLEN);
+
+    if ( !(((*fptr)->Fptr)->iobuffer) )
+    {
+        (*driverTable[driver].close)(handle);  /* close the file */
+        ffpmsg("failed to allocate memory for iobuffer array: (ffopen)");
+        ffpmsg(url);
+        free( ((*fptr)->Fptr)->headstart);    /* free memory for headstart array */
+        free( ((*fptr)->Fptr)->filename);
+        free((*fptr)->Fptr);
+        free(*fptr);
+        *fptr = 0;              /* return null file pointer */
+        return(*status = MEMORY_ALLOCATION);
+    }
+
+    /* initialize the ageindex array (relative age of the I/O buffers) */
+    /* and initialize the bufrecnum array as being empty */
+    for (ii = 0; ii < NIOBUF; ii++)  {
+        ((*fptr)->Fptr)->ageindex[ii] = ii;
+        ((*fptr)->Fptr)->bufrecnum[ii] = -1;
+    }
+
+        /* store the parameters describing the file */
+    ((*fptr)->Fptr)->MAXHDU = 1000;              /* initial size of headstart */
+    ((*fptr)->Fptr)->filehandle = handle;        /* file handle */
+    ((*fptr)->Fptr)->driver = driver;            /* driver number */
+    strcpy(((*fptr)->Fptr)->filename, url);      /* full input filename */
+    ((*fptr)->Fptr)->filesize = filesize;        /* physical file size */
+    ((*fptr)->Fptr)->logfilesize = filesize;     /* logical file size */
+    ((*fptr)->Fptr)->writemode = mode;           /* read-write mode    */
+    ((*fptr)->Fptr)->datastart = DATA_UNDEFINED; /* unknown start of data */
+    ((*fptr)->Fptr)->curbuf = -1;            /* undefined current IO buffer */
+    ((*fptr)->Fptr)->open_count = 1;      /* structure is currently used once */
+    ((*fptr)->Fptr)->validcode = VALIDSTRUC; /* flag denoting valid structure */
+    ((*fptr)->Fptr)->only_one = only_one; /* flag denoting only copy single extension */
+
+    ffldrc(*fptr, 0, REPORT_EOF, status);     /* load first record */
+
+    fits_store_Fptr( (*fptr)->Fptr, status);  /* store Fptr address */
+
+    if (ffrhdu(*fptr, &hdutyp, status) > 0)  /* determine HDU structure */
+    {
+        ffpmsg(
+          "ffopen could not interpret primary array header of file: ");
+        ffpmsg(url);
+
+        if (*status == UNKNOWN_REC)
+           ffpmsg("This does not look like a FITS file.");
+
+        ffclos(*fptr, status);
+        *fptr = 0;              /* return null file pointer */
+        return(*status);
+    }
+
+    /* ------------------------------------------------------------- */
+    /* At this point, the input file has been opened. If outfile was */
+    /* specified, then we have opened a copy of the file, not the    */
+    /* original file so it is safe to modify it if necessary         */
+    /* ------------------------------------------------------------- */
+
+    if (*outfile)
+        writecopy = 1;  
+
+move2hdu:
+
+    /* ---------------------------------------------------------- */
+    /* move to desired extension, if specified as part of the URL */
+    /* ---------------------------------------------------------- */
+
+    if (*extspec)
+    {
+      if (extnum)  /* extension number was specified */
+      {
+        ffmahd(*fptr, extnum + 1, &hdutyp, status);
+      }
+      else if (*extname) /* move to named extension, if specified */
+      {
+        ffmnhd(*fptr, movetotype, extname, extvers, status);
+      }
+
+      if (*status > 0)  /* clean up after error */
+      {
+        ffpmsg("ffopen could not move to the specified extension:");
+        if (extnum > 0)
+        {
+          sprintf(errmsg,
+          " extension number %d doesn't exist or couldn't be opened.",extnum);
+          ffpmsg(errmsg);
+        }
+        else
+        {
+          sprintf(errmsg,
+          " extension with EXTNAME = %s,", extname);
+          ffpmsg(errmsg);
+
+          if (extvers)
+          {
+             sprintf(errmsg,
+             "           and with EXTVERS = %d,", extvers);
+             ffpmsg(errmsg);
+          }
+
+          if (movetotype != ANY_HDU)
+          {
+             sprintf(errmsg,
+             "           and with XTENSION = %s,", hdtype[movetotype]);
+             ffpmsg(errmsg);
+          }
+
+          ffpmsg(" doesn't exist or couldn't be opened.");
+        }
+
+        ffclos(*fptr, status);
+        *fptr = 0;              /* return null file pointer */
+        return(*status);
+      }
+    }
+    else if (skip_null || skip_image || skip_table ||
+            (*imagecolname || *colspec || *rowfilter || *binspec))
+    {
+      /* ------------------------------------------------------------------
+
+      If no explicit extension specifier is given as part of the file
+      name, and, if a) skip_null is true (set if ffopen is called by
+      ffdopn) or b) skip_image or skip_table is true (set if ffopen is
+      called by fftopn or ffdopn) or c) other file filters are
+      specified, then CFITSIO will attempt to move to the first
+      'interesting' HDU after opening an existing FITS file (or to
+      first interesting table HDU if skip_image is true);
+
+      An 'interesting' HDU is defined to be either an image with NAXIS
+      > 0 (i.e., not a null array) or a table which has an EXTNAME
+      value which does not contain any of the following strings:
+         'GTI'  - Good Time Interval extension
+         'OBSTABLE'  - used in Beppo SAX data files
+
+      The main purpose for this is to allow CFITSIO to skip over a null
+      primary and other non-interesting HDUs when opening an existing
+      file, and move directly to the first extension that contains
+      significant data.
+      ------------------------------------------------------------------ */
+
+      fits_get_hdu_num(*fptr, &hdunum);
+      if (hdunum == 1) {
+
+        fits_get_img_dim(*fptr, &naxis, status);
+
+        if (naxis == 0 || skip_image) /* skip primary array */
+        {
+          while(1) 
+          {
+            /* see if the next HDU is 'interesting' */
+            if (fits_movrel_hdu(*fptr, 1, &hdutyp, status))
+            {
+               if (*status == END_OF_FILE)
+                  *status = 0;  /* reset expected error */
+
+               /* didn't find an interesting HDU so move back to beginning */
+               fits_movabs_hdu(*fptr, 1, &hdutyp, status);
+               break;
+            }
+
+            if (hdutyp == IMAGE_HDU && skip_image) {
+
+                continue;   /* skip images */
+
+            } else if (hdutyp != IMAGE_HDU && skip_table) {
+
+                continue;   /* skip tables */
+
+            } else if (hdutyp == IMAGE_HDU) {
+
+               fits_get_img_dim(*fptr, &naxis, status);
+               if (naxis > 0)
+                  break;  /* found a non-null image */
+
+            } else {
+
+               tstatus = 0;
+               tblname[0] = '\0';
+               fits_read_key(*fptr, TSTRING, "EXTNAME", tblname, NULL,&tstatus);
+
+               if ( (!strstr(tblname, "GTI") && !strstr(tblname, "gti")) &&
+                    strncasecmp(tblname, "OBSTABLE", 8) )
+                  break;  /* found an interesting table */
+            }
+          }  /* end while */
+        }
+      } /* end if (hdunum==1) */
+    }
+
+    if (*imagecolname)
+    {
+       /* ----------------------------------------------------------------- */
+       /* we need to open an image contained in a single table cell         */
+       /* First, determine which row of the table to use.                   */
+       /* ----------------------------------------------------------------- */
+
+       if (isdigit((int) *rowexpress))  /* is the row specification a number? */
+       {
+          sscanf(rowexpress, "%ld", &rownum);
+          if (rownum < 1)
+          {
+             ffpmsg("illegal rownum for image cell:");
+             ffpmsg(rowexpress);
+             ffpmsg("Could not open the following image in a table cell:");
+             ffpmsg(extspec);
+             ffclos(*fptr, status);
+             *fptr = 0;              /* return null file pointer */
+             return(*status = BAD_ROW_NUM);
+          }
+       }
+       else if (fits_find_first_row(*fptr, rowexpress, &rownum, status) > 0)
+       {
+          ffpmsg("Failed to find row matching this expression:");
+          ffpmsg(rowexpress);
+          ffpmsg("Could not open the following image in a table cell:");
+          ffpmsg(extspec);
+          ffclos(*fptr, status);
+          *fptr = 0;              /* return null file pointer */
+          return(*status);
+       }
+
+       if (rownum == 0)
+       {
+          ffpmsg("row statisfying this expression doesn't exist::");
+          ffpmsg(rowexpress);
+          ffpmsg("Could not open the following image in a table cell:");
+          ffpmsg(extspec);
+          ffclos(*fptr, status);
+          *fptr = 0;              /* return null file pointer */
+          return(*status = BAD_ROW_NUM);
+       }
+
+       /* determine the name of the new file to contain copy of the image */
+       if (*histfilename && !(*pixfilter) )
+           strcpy(outfile, histfilename); /* the original outfile name */
+       else
+           strcpy(outfile, "mem://_1");  /* create image file in memory */
+
+       /* Copy the image into new primary array and open it as the current */
+       /* fptr.  This will close the table that contains the original image. */
+
+       /* create new empty file to hold copy of the image */
+       if (ffinit(&newptr, outfile, status) > 0)
+       {
+          ffpmsg("failed to create file for copy of image in table cell:");
+          ffpmsg(outfile);
+          return(*status);
+       }
+       
+       if (fits_copy_cell2image(*fptr, newptr, imagecolname, rownum,
+                                status) > 0)
+       {
+          ffpmsg("Failed to copy table cell to new primary array:");
+          ffpmsg(extspec);
+          ffclos(*fptr, status);
+          *fptr = 0;              /* return null file pointer */
+          return(*status);
+       }
+
+       /* close the original file and set fptr to the new image */
+       ffclos(*fptr, status);
+
+       *fptr = newptr; /* reset the pointer to the new table */
+
+       writecopy = 1;  /* we are now dealing with a copy of the original file */
+
+       /* add some HISTORY; fits_copy_image_cell also wrote HISTORY keywords */
+       
+/*  disable this; leave it up to calling routine to write any HISTORY keywords
+       if (*extname)
+        sprintf(card,"HISTORY  in HDU '%.16s' of file '%.36s'",extname,infile);
+       else
+        sprintf(card,"HISTORY  in HDU %d of file '%.45s'", extnum, infile);
+
+       ffprec(*fptr, card, status);
+*/
+    }
+
+    /* --------------------------------------------------------------------- */
+    /* edit columns (and/or keywords) in the table, if specified in the URL  */
+    /* --------------------------------------------------------------------- */
+ 
+    if (*colspec)
+    {
+       /* the column specifier will modify the file, so make sure */
+       /* we are already dealing with a copy, or else make a new copy */
+
+       if (!writecopy)  /* Is the current file already a copy? */
+           writecopy = fits_is_this_a_copy(urltype);
+
+       if (!writecopy)
+       {
+           if (*filtfilename && *outfile == '\0')
+               strcpy(outfile, filtfilename); /* the original outfile name */
+           else
+               strcpy(outfile, "mem://_1");   /* will create copy in memory */
+
+           writecopy = 1;
+       }
+       else
+       {
+           ((*fptr)->Fptr)->writemode = READWRITE; /* we have write access */
+           outfile[0] = '\0';
+       }
+
+       if (ffedit_columns(fptr, outfile, colspec, status) > 0)
+       {
+           ffpmsg("editing columns in input table failed (ffopen)");
+           ffpmsg(" while trying to perform the following operation:");
+           ffpmsg(colspec);
+           ffclos(*fptr, status);
+           *fptr = 0;              /* return null file pointer */
+           return(*status);
+       }
+    }
+
+    /* ------------------------------------------------------------------- */
+    /* select rows from the table, if specified in the URL                 */
+    /* or select a subimage (if this is an image HDU and not a table)      */
+    /* ------------------------------------------------------------------- */
+ 
+    if (*rowfilter)
+    {
+     fits_get_hdu_type(*fptr, &hdutyp, status);  /* get type of HDU */
+     if (hdutyp == IMAGE_HDU)
+     {
+        /* this is an image so 'rowfilter' is an image section specification */
+
+        if (*filtfilename && *outfile == '\0')
+            strcpy(outfile, filtfilename); /* the original outfile name */
+        else if (*outfile == '\0') /* output file name not already defined? */
+            strcpy(outfile, "mem://_2");  /* will create file in memory */
+
+        /* create new file containing the image section, plus a copy of */
+        /* any other HDUs that exist in the input file.  This routine   */
+        /* will close the original image file and return a pointer      */
+        /* to the new file. */
+
+        if (fits_select_image_section(fptr, outfile, rowfilter, status) > 0)
+        {
+           ffpmsg("on-the-fly selection of image section failed (ffopen)");
+           ffpmsg(" while trying to use the following section filter:");
+           ffpmsg(rowfilter);
+           ffclos(*fptr, status);
+           *fptr = 0;              /* return null file pointer */
+           return(*status);
+        }
+     }
+     else
+     {
+       /* this is a table HDU, so the rowfilter is really a row filter */
+
+      if (*binspec)
+      {
+        /*  since we are going to make a histogram of the selected rows,   */
+        /*  it would be a waste of time and memory to make a whole copy of */
+        /*  the selected rows.  Instead, just construct an array of TRUE   */
+        /*  or FALSE values that indicate which rows are to be included    */
+        /*  in the histogram and pass that to the histogram generating     */
+        /*  routine                                                        */
+
+        fits_get_num_rows(*fptr, &nrows, status);  /* get no. of rows */
+
+        rowselect = (char *) calloc(nrows, 1);
+        if (!rowselect)
+        {
+           ffpmsg(
+           "failed to allocate memory for selected columns array (ffopen)");
+           ffpmsg(" while trying to select rows with the following filter:");
+           ffpmsg(rowfilter);
+           ffclos(*fptr, status);
+           *fptr = 0;              /* return null file pointer */
+           return(*status = MEMORY_ALLOCATION);
+        }
+
+        if (fits_find_rows(*fptr, rowfilter, 1L, nrows, &goodrows,
+            rowselect, status) > 0)
+        {
+           ffpmsg("selection of rows in input table failed (ffopen)");
+           ffpmsg(" while trying to select rows with the following filter:");
+           ffpmsg(rowfilter);
+           free(rowselect);
+           ffclos(*fptr, status);
+           *fptr = 0;              /* return null file pointer */
+           return(*status);
+        }
+      }
+      else
+      {
+        if (!writecopy)  /* Is the current file already a copy? */
+           writecopy = fits_is_this_a_copy(urltype);
+
+        if (!writecopy)
+        {
+           if (*filtfilename && *outfile == '\0')
+               strcpy(outfile, filtfilename); /* the original outfile name */
+           else if (*outfile == '\0') /* output filename not already defined? */
+               strcpy(outfile, "mem://_2");  /* will create copy in memory */
+        }
+        else
+        {
+           ((*fptr)->Fptr)->writemode = READWRITE; /* we have write access */
+           outfile[0] = '\0';
+        }
+
+        /* select rows in the table.  If a copy of the input file has */
+        /* not already been made, then this routine will make a copy */
+        /* and then close the input file, so that the modifications will */
+        /* only be made on the copy, not the original */
+
+        if (ffselect_table(fptr, outfile, rowfilter, status) > 0)
+        {
+          ffpmsg("on-the-fly selection of rows in input table failed (ffopen)");
+           ffpmsg(" while trying to select rows with the following filter:");
+           ffpmsg(rowfilter);
+           ffclos(*fptr, status);
+           *fptr = 0;              /* return null file pointer */
+           return(*status);
+        }
+
+        /* write history records */
+        ffphis(*fptr, 
+        "CFITSIO used the following filtering expression to create this table:",
+        status);
+        ffphis(*fptr, name, status);
+
+      }   /* end of no binspec case */
+     }   /* end of table HDU case */
+    }  /* end of rowfilter exists case */
+
+    /* ------------------------------------------------------------------- */
+    /* make an image histogram by binning columns, if specified in the URL */
+    /* ------------------------------------------------------------------- */
+ 
+    if (*binspec)
+    {
+       if (*histfilename  && !(*pixfilter) )
+           strcpy(outfile, histfilename); /* the original outfile name */
+       else
+           strcpy(outfile, "mem://_3");  /* create histogram in memory */
+                                         /* if not already copied the file */ 
+
+       /* parse the binning specifier into individual parameters */
+       ffbins(binspec, &imagetype, &haxis, colname, 
+                          minin, maxin, binsizein, 
+                          minname, maxname, binname,
+                          &weight, wtcol, &recip, status);
+
+       /* Create the histogram primary array and open it as the current fptr */
+       /* This will close the table that was used to create the histogram. */
+       ffhist2(fptr, outfile, imagetype, haxis, colname, minin, maxin,
+              binsizein, minname, maxname, binname,
+              weight, wtcol, recip, rowselect, status);
+
+       if (rowselect)
+          free(rowselect);
+
+       if (*status > 0)
+       {
+      ffpmsg("on-the-fly histogramming of input table failed (ffopen)");
+      ffpmsg(" while trying to execute the following histogram specification:");
+      ffpmsg(binspec);
+           ffclos(*fptr, status);
+           *fptr = 0;              /* return null file pointer */
+           return(*status);
+       }
+
+        /* write history records */
+        ffphis(*fptr,
+        "CFITSIO used the following expression to create this histogram:", 
+        status);
+        ffphis(*fptr, name, status);
+    }
+
+    if (*pixfilter)
+    {
+       if (*histfilename)
+           strcpy(outfile, histfilename); /* the original outfile name */
+       else
+           strcpy(outfile, "mem://_4");  /* create in memory */
+                                         /* if not already copied the file */ 
+
+       /* Ensure type of HDU is consistent with pixel filtering */
+       fits_get_hdu_type(*fptr, &hdutyp, status);  /* get type of HDU */
+       if (hdutyp == IMAGE_HDU) {
+
+          pixel_filter_helper(fptr, outfile, pixfilter, status);
+
+          if (*status > 0) {
+             ffpmsg("pixel filtering of input image failed (ffopen)");
+             ffpmsg(" while trying to execute the following:");
+             ffpmsg(pixfilter);
+             ffclos(*fptr, status);
+             *fptr = 0;              /* return null file pointer */
+             return(*status);
+          }
+
+          /* write history records */
+          ffphis(*fptr,
+          "CFITSIO used the following expression to create this image:",
+          status);
+          ffphis(*fptr, name, status);
+
+          return *status;
+       }
+       else {
+          ffpmsg("cannot use pixel filter on non-IMAGE HDU");
+          ffpmsg(pixfilter);
+          ffclos(*fptr, status);
+          *fptr = 0;              /* return null file pointer */
+          *status = NOT_IMAGE;
+          return(*status);
+       }
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffreopen(fitsfile *openfptr, /* I - FITS file pointer to open file  */ 
+             fitsfile **newfptr,  /* O - pointer to new re opened file   */
+             int *status)        /* IO - error status                   */
+/*
+  Reopen an existing FITS file with either readonly or read/write access.
+  The reopened file shares the same FITSfile structure but may point to a
+  different HDU within the file.
+*/
+{
+    if (*status > 0)
+        return(*status);
+
+    /* check that the open file pointer is valid */
+    if (!openfptr)
+        return(*status = NULL_INPUT_PTR);
+    else if ((openfptr->Fptr)->validcode != VALIDSTRUC) /* check magic value */
+        return(*status = BAD_FILEPTR); 
+
+        /* allocate fitsfile structure and initialize = 0 */
+    *newfptr = (fitsfile *) calloc(1, sizeof(fitsfile));
+
+    (*newfptr)->Fptr = openfptr->Fptr; /* both point to the same structure */
+    (*newfptr)->HDUposition = 0;  /* set initial position to primary array */
+    (((*newfptr)->Fptr)->open_count)++;   /* increment the file usage counter */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_store_Fptr(FITSfile *Fptr,  /* O - FITS file pointer               */ 
+           int *status)              /* IO - error status                   */
+/*
+   store the new Fptr address for future use by fits_already_open 
+*/
+{
+    int ii;
+
+    if (*status > 0)
+        return(*status);
+
+    FFLOCK;
+    for (ii = 0; ii < NMAXFILES; ii++) {
+        if (FptrTable[ii] == 0) {
+            FptrTable[ii] = Fptr;
+            break;
+        }
+    }
+    FFUNLOCK;
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_clear_Fptr(FITSfile *Fptr,  /* O - FITS file pointer               */ 
+           int *status)              /* IO - error status                   */
+/*
+   clear the Fptr address from the Fptr Table  
+*/
+{
+    int ii;
+
+    FFLOCK;
+    for (ii = 0; ii < NMAXFILES; ii++) {
+        if (FptrTable[ii] == Fptr) {
+            FptrTable[ii] = 0;
+            break;
+        }
+    }
+    FFUNLOCK;
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_already_open(fitsfile **fptr, /* I/O - FITS file pointer       */ 
+           char *url, 
+           char *urltype, 
+           char *infile, 
+           char *extspec, 
+           char *rowfilter,
+           char *binspec, 
+           char *colspec, 
+           int  mode,             /* I - 0 = open readonly; 1 = read/write   */
+           int  *isopen,          /* O - 1 = file is already open            */
+           int  *status)          /* IO - error status                       */
+/*
+  Check if the file to be opened is already open.  If so, then attach to it.
+*/
+
+   /* the input strings must not exceed the standard lengths */
+   /* of FLEN_FILENAME, MAX_PREFIX_LEN, etc. */
+
+     /*
+       this function was changed so that for files of access method FILE://
+       the file paths are compared using standard URL syntax and absolute
+       paths (as opposed to relative paths). This eliminates some instances
+       where a file is already opened but it is not realized because it
+       was opened with another file path. For instance, if the CWD is
+       /a/b/c and I open /a/b/c/foo.fits then open ./foo.fits the previous
+       version of this function would not have reconized that the two files
+       were the same. This version does recognize that the two files are
+       the same.
+     */
+{
+    FITSfile *oldFptr;
+    int ii;
+    char oldurltype[MAX_PREFIX_LEN], oldinfile[FLEN_FILENAME];
+    char oldextspec[FLEN_FILENAME], oldoutfile[FLEN_FILENAME];
+    char oldrowfilter[FLEN_FILENAME];
+    char oldbinspec[FLEN_FILENAME], oldcolspec[FLEN_FILENAME];
+    char cwd[FLEN_FILENAME];
+    char tmpStr[FLEN_FILENAME];
+    char tmpinfile[FLEN_FILENAME];
+
+    *isopen = 0;
+
+/*  When opening a file with readonly access then we simply let
+    the operating system open the file again, instead of using the CFITSIO
+    trick of attaching to the previously opened file.  This is required
+    if CFITSIO is running in a multi-threaded environment, because 2 different
+    threads cannot share the same FITSfile pointer.
+    
+    If the file is opened/reopened with write access, then the file MUST
+    only be physically opened once..
+*/ 
+    if (mode == 0)
+        return(*status);
+
+    if(strcasecmp(urltype,"FILE://") == 0)
+      {
+        fits_path2url(infile,tmpinfile,status);
+
+        if(tmpinfile[0] != '/')
+          {
+            fits_get_cwd(cwd,status);
+            strcat(cwd,"/");
+ 
+            if (strlen(cwd) + strlen(tmpinfile) > FLEN_FILENAME-1) {
+		  ffpmsg("File name is too long. (fits_already_open)");
+                  return(*status = FILE_NOT_OPENED);
+            }
+
+            strcat(cwd,tmpinfile);
+            fits_clean_url(cwd,tmpinfile,status);
+          }
+      }
+    else
+      strcpy(tmpinfile,infile);
+
+    for (ii = 0; ii < NMAXFILES; ii++)   /* check every buffer */
+    {
+        if (FptrTable[ii] != 0)
+        {
+          oldFptr = FptrTable[ii];
+
+          fits_parse_input_url(oldFptr->filename, oldurltype, 
+                    oldinfile, oldoutfile, oldextspec, oldrowfilter, 
+                    oldbinspec, oldcolspec, status);
+
+          if (*status > 0)
+          {
+            ffpmsg("could not parse the previously opened filename: (ffopen)");
+            ffpmsg(oldFptr->filename);
+            return(*status);
+          }
+
+          if(strcasecmp(oldurltype,"FILE://") == 0)
+            {
+              fits_path2url(oldinfile,tmpStr,status);
+              
+              if(tmpStr[0] != '/')
+                {
+                  fits_get_cwd(cwd,status);
+                  strcat(cwd,"/");
+
+
+                  strcat(cwd,tmpStr);
+                  fits_clean_url(cwd,tmpStr,status);
+                }
+
+              strcpy(oldinfile,tmpStr);
+            }
+
+          if (!strcmp(urltype, oldurltype) && !strcmp(tmpinfile, oldinfile) )
+          {
+              /* identical type of file and root file name */
+
+              if ( (!rowfilter[0] && !oldrowfilter[0] &&
+                    !binspec[0]   && !oldbinspec[0] &&
+                    !colspec[0]   && !oldcolspec[0])
+
+                  /* no filtering or binning specs for either file, so */
+                  /* this is a case where the same file is being reopened. */
+                  /* It doesn't matter if the extensions are different */
+
+                      ||   /* or */
+
+                  (!strcmp(rowfilter, oldrowfilter) &&
+                   !strcmp(binspec, oldbinspec)     &&
+                   !strcmp(colspec, oldcolspec)     &&
+                   !strcmp(extspec, oldextspec) ) )
+
+                  /* filtering specs are given and are identical, and */
+                  /* the same extension is specified */
+
+              {
+                  if (mode == READWRITE && oldFptr->writemode == READONLY)
+                  {
+                    /*
+                      cannot assume that a file previously opened with READONLY
+                      can now be written to (e.g., files on CDROM, or over the
+                      the network, or STDIN), so return with an error.
+                    */
+
+                    ffpmsg(
+                "cannot reopen file READWRITE when previously opened READONLY");
+                    ffpmsg(url);
+                    return(*status = FILE_NOT_OPENED);
+                  }
+
+                  *fptr = (fitsfile *) calloc(1, sizeof(fitsfile));
+
+                  if (!(*fptr))
+                  {
+                     ffpmsg(
+                   "failed to allocate structure for following file: (ffopen)");
+                     ffpmsg(url);
+                     return(*status = MEMORY_ALLOCATION);
+                  }
+
+                  (*fptr)->Fptr = oldFptr; /* point to the structure */
+                  (*fptr)->HDUposition = 0;     /* set initial position */
+                (((*fptr)->Fptr)->open_count)++;  /* increment usage counter */
+
+                  if (binspec[0])  /* if binning specified, don't move */
+                      extspec[0] = '\0';
+
+                  /* all the filtering has already been applied, so ignore */
+                  rowfilter[0] = '\0';
+                  binspec[0] = '\0';
+                  colspec[0] = '\0';
+
+                  *isopen = 1;
+              }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_is_this_a_copy(char *urltype) /* I - type of file */
+/*
+  specialized routine that returns 1 if the file is known to be a temporary
+  copy of the originally opened file.  Otherwise it returns 0.
+*/
+{
+  int iscopy;
+
+  if (!strncmp(urltype, "mem", 3) )
+     iscopy = 1;    /* file copy is in memory */
+  else if (!strncmp(urltype, "compress", 8) )
+     iscopy = 1;    /* compressed diskfile that is uncompressed in memory */
+  else if (!strncmp(urltype, "http", 4) )
+     iscopy = 1;    /* copied file using http protocol */
+  else if (!strncmp(urltype, "ftp", 3) )
+     iscopy = 1;    /* copied file using ftp protocol */
+  else if (!strncmp(urltype, "gsiftp", 6) )
+     iscopy = 1;    /* copied file using gsiftp protocol */
+  else if (!strncpy(urltype, "stdin", 5) )
+     iscopy = 1;    /* piped stdin has been copied to memory */
+  else
+     iscopy = 0;    /* file is not known to be a copy */
+ 
+    return(iscopy);
+}
+/*--------------------------------------------------------------------------*/
+int ffedit_columns(
+           fitsfile **fptr,  /* IO - pointer to input table; on output it  */
+                             /*      points to the new selected rows table */
+           char *outfile,    /* I - name for output file */
+           char *expr,       /* I - column edit expression    */
+           int *status)
+/*
+   modify columns in a table and/or header keywords in the HDU
+*/
+{
+    fitsfile *newptr;
+    int ii, hdunum, slen, colnum = -1, testnum, deletecol = 0, savecol = 0;
+    int numcols = 0, *colindex = 0, tstatus = 0;
+    char *cptr, *cptr2, *cptr3, clause[FLEN_FILENAME], keyname[FLEN_KEYWORD];
+    char colname[FLEN_VALUE], oldname[FLEN_VALUE], colformat[FLEN_VALUE];
+    char *file_expr = NULL, testname[FLEN_VALUE], card[FLEN_CARD];
+
+    if (*outfile)
+    {
+      /* create new empty file in to hold the selected rows */
+      if (ffinit(&newptr, outfile, status) > 0)
+      {
+        ffpmsg("failed to create file for copy (ffedit_columns)");
+        return(*status);
+      }
+
+      fits_get_hdu_num(*fptr, &hdunum);  /* current HDU number in input file */
+
+      /* copy all HDUs to the output copy, if the 'only_one' flag is not set */
+      if (!((*fptr)->Fptr)->only_one) {
+        for (ii = 1; 1; ii++)
+        {
+          if (fits_movabs_hdu(*fptr, ii, NULL, status) > 0)
+            break;
+
+          fits_copy_hdu(*fptr, newptr, 0, status);
+        }
+
+        if (*status == END_OF_FILE)
+        {
+          *status = 0;              /* got the expected EOF error; reset = 0  */
+        }
+        else if (*status > 0)
+        {
+          ffclos(newptr, status);
+          ffpmsg("failed to copy all HDUs from input file (ffedit_columns)");
+          return(*status);
+        }
+
+
+      } else {
+        /* only copy the primary array and the designated table extension */
+	fits_movabs_hdu(*fptr, 1, NULL, status);
+	fits_copy_hdu(*fptr, newptr, 0, status);
+	fits_movabs_hdu(*fptr, hdunum, NULL, status);
+	fits_copy_hdu(*fptr, newptr, 0, status);
+        if (*status > 0)
+        {
+          ffclos(newptr, status);
+          ffpmsg("failed to copy all HDUs from input file (ffedit_columns)");
+          return(*status);
+        }
+        hdunum = 2;
+      }
+
+      /* close the original file and return ptr to the new image */
+      ffclos(*fptr, status);
+
+      *fptr = newptr; /* reset the pointer to the new table */
+
+      /* move back to the selected table HDU */
+      if (fits_movabs_hdu(*fptr, hdunum, NULL, status) > 0)
+      {
+         ffpmsg("failed to copy the input file (ffedit_columns)");
+         return(*status);
+      }
+    }
+
+    /* remove the "col " from the beginning of the column edit expression */
+    cptr = expr + 4;
+
+    while (*cptr == ' ')
+         cptr++;         /* skip leading white space */
+   
+    /* Check if need to import expression from a file */
+
+    if( *cptr=='@' ) {
+       if( ffimport_file( cptr+1, &file_expr, status ) ) return(*status);
+       cptr = file_expr;
+       while (*cptr == ' ')
+          cptr++;         /* skip leading white space... again */
+    }
+
+    tstatus = 0;
+    ffgncl(*fptr, &numcols, &tstatus);  /* get initial # of cols */
+
+    /* parse expression and get first clause, if more than 1 */
+
+    while ((slen = fits_get_token(&cptr, ";", clause, NULL)) > 0 )
+    {
+        if( *cptr==';' ) cptr++;
+        clause[slen] = '\0';
+
+        if (clause[0] == '!' || clause[0] == '-')
+        {
+            /* ===================================== */
+            /* Case I. delete this column or keyword */
+            /* ===================================== */
+
+            if (ffgcno(*fptr, CASEINSEN, &clause[1], &colnum, status) <= 0)
+            {
+                /* a column with this name exists, so try to delete it */
+                if (ffdcol(*fptr, colnum, status) > 0)
+                {
+                    ffpmsg("failed to delete column in input file:");
+                    ffpmsg(clause);
+                    if( colindex ) free( colindex );
+                    if( file_expr ) free( file_expr );
+                    return(*status);
+                }
+                deletecol = 1; /* set flag that at least one col was deleted */
+                numcols--;
+                colnum = -1;
+            }
+            else
+            {
+	        ffcmsg();   /* clear previous error message from ffgcno */
+                /* try deleting a keyword with this name */
+                *status = 0;
+                if (ffdkey(*fptr, &clause[1], status) > 0)
+                {
+                    ffpmsg("column or keyword to be deleted does not exist:");
+                    ffpmsg(clause);
+                    if( colindex ) free( colindex );
+                    if( file_expr ) free( file_expr );
+                    return(*status);
+                }
+            }
+        }
+        else
+        {
+            /* ===================================================== */
+            /* Case II:
+	       this is either a column name, (case 1) 
+
+               or a new column name followed by double = ("==") followed
+               by the old name which is to be renamed. (case 2A)
+
+               or a column or keyword name followed by a single "=" and a
+	       calculation expression (case 2B) */
+            /* ===================================================== */
+            cptr2 = clause;
+            slen = fits_get_token(&cptr2, "( =", colname, NULL);
+
+
+            if (slen == 0)
+            {
+                ffpmsg("error: column or keyword name is blank:");
+                ffpmsg(clause);
+                if( colindex ) free( colindex );
+                if( file_expr ) free( file_expr );
+                return(*status= URL_PARSE_ERROR);
+            }
+
+	    /* If this is a keyword of the form 
+	         #KEYWORD# 
+	       then transform to the form
+	         #KEYWORDn
+	       where n is the previously used column number 
+	    */
+	    if (colname[0] == '#' &&
+		strstr(colname+1, "#") == (colname + strlen(colname) - 1)) 
+	    {
+		if (colnum <= 0) 
+		  {
+		    ffpmsg("The keyword name:");
+		    ffpmsg(colname);
+		    ffpmsg("is invalid unless a column has been previously");
+		    ffpmsg("created or editted by a calculator command");
+		    return(*status = URL_PARSE_ERROR);
+		  }
+		colname[strlen(colname)-1] = '\0';
+		/* Make keyword name and put it in oldname */
+		ffkeyn(colname+1, colnum, oldname, status);
+		if (*status) return (*status);
+		/* Re-copy back into colname */
+		strcpy(colname+1,oldname);
+	    }
+            else if  (strstr(colname, "#") == (colname + strlen(colname) - 1)) 
+	    {
+	        /*  colname is of the form "NAME#";  if
+		      a) colnum is defined, and
+		      b) a column with literal name "NAME#" does not exist, and
+		      c) a keyword with name "NAMEn" (where n=colnum) exists, then
+		    transfrom the colname string to "NAMEn", otherwise
+		    do nothing.
+		*/
+		if (colnum > 0) {  /* colnum must be defined */
+		  tstatus = 0;
+                  ffgcno(*fptr, CASEINSEN, colname, &testnum, &tstatus);
+		  if (tstatus != 0 && tstatus != COL_NOT_UNIQUE) 
+		  {  
+		    /* OK, column doesn't exist, now see if keyword exists */
+		    ffcmsg();   /* clear previous error message from ffgcno */
+		    strcpy(testname, colname);
+ 		    testname[strlen(testname)-1] = '\0';
+		    /* Make keyword name and put it in oldname */
+		    ffkeyn(testname, colnum, oldname, status);
+		    if (*status) return (*status);
+
+		    tstatus = 0;
+		    if (!fits_read_card(*fptr, oldname, card, &tstatus)) {
+		      /* Keyword does exist; copy real name back into colname */
+		      strcpy(colname,oldname);
+		    }
+		  }
+                }
+	    }
+
+            /* if we encountered an opening parenthesis, then we need to */
+            /* find the closing parenthesis, and concatinate the 2 strings */
+            /* This supports expressions like:
+                [col #EXTNAME(Extension name)="GTI"]
+            */
+            if (*cptr2  == '(')
+            {
+                fits_get_token(&cptr2, ")", oldname, NULL);
+                strcat(colname, oldname);
+                strcat(colname, ")");
+                cptr2++;
+            }
+
+            while (*cptr2 == ' ')
+                 cptr2++;         /* skip white space */
+
+            if (*cptr2 != '=')
+            {
+              /* ------------------------------------ */
+              /* case 1 - simply the name of a column */
+              /* ------------------------------------ */
+
+              /* look for matching column */
+              ffgcno(*fptr, CASEINSEN, colname, &testnum, status);
+
+              while (*status == COL_NOT_UNIQUE) 
+              {
+                 /* the column name contained wild cards, and it */
+                 /* matches more than one column in the table. */
+		 
+		 colnum = testnum;
+
+                 /* keep this column in the output file */
+                 savecol = 1;
+
+                 if (!colindex)
+                    colindex = (int *) calloc(999, sizeof(int));
+
+                 colindex[colnum - 1] = 1;  /* flag this column number */
+
+                 /* look for other matching column names */
+                 ffgcno(*fptr, CASEINSEN, colname, &testnum, status);
+
+                 if (*status == COL_NOT_FOUND)
+                    *status = 999;  /* temporary status flag value */
+              }
+
+              if (*status <= 0)
+              {
+	         colnum = testnum;
+		 
+                 /* keep this column in the output file */
+                 savecol = 1;
+
+                 if (!colindex)
+                    colindex = (int *) calloc(999, sizeof(int));
+
+                 colindex[colnum - 1] = 1;  /* flag this column number */
+              }
+              else if (*status == 999)
+              {
+                  /* this special flag value does not represent an error */
+                  *status = 0;  
+              }
+              else
+              {
+               ffpmsg("Syntax error in columns specifier in input URL:");
+               ffpmsg(cptr2);
+               if( colindex ) free( colindex );
+               if( file_expr ) free( file_expr );
+               return(*status = URL_PARSE_ERROR);
+              }
+            }
+            else
+            {
+              /* ----------------------------------------------- */
+              /* case 2 where the token ends with an equals sign */
+              /* ----------------------------------------------- */
+
+              cptr2++;   /* skip over the first '=' */
+
+              if (*cptr2 == '=')
+              {
+                /*................................................. */
+                /*  Case A:  rename a column or keyword;  syntax is
+                    "new_name == old_name"  */
+                /*................................................. */
+
+                cptr2++;  /* skip the 2nd '=' */
+                while (*cptr2 == ' ')
+                      cptr2++;       /* skip white space */
+
+                fits_get_token(&cptr2, " ", oldname, NULL);
+
+                /* get column number of the existing column */
+                if (ffgcno(*fptr, CASEINSEN, oldname, &colnum, status) <= 0)
+                {
+                    /* modify the TTYPEn keyword value with the new name */
+                    ffkeyn("TTYPE", colnum, keyname, status);
+
+                    if (ffmkys(*fptr, keyname, colname, NULL, status) > 0)
+                    {
+                      ffpmsg("failed to rename column in input file");
+                      ffpmsg(" oldname =");
+                      ffpmsg(oldname);
+                      ffpmsg(" newname =");
+                      ffpmsg(colname);
+                      if( colindex ) free( colindex );
+                      if( file_expr ) free( file_expr );
+                      return(*status);
+                    }
+                    /* keep this column in the output file */
+                    savecol = 1;
+                    if (!colindex)
+                       colindex = (int *) calloc(999, sizeof(int));
+
+                    colindex[colnum - 1] = 1;  /* flag this column number */
+                }
+                else
+                {
+                    /* try renaming a keyword */
+		    ffcmsg();   /* clear error message stack */
+                    *status = 0;
+                    if (ffmnam(*fptr, oldname, colname, status) > 0)
+                    {
+                      ffpmsg("column or keyword to be renamed does not exist:");
+                        ffpmsg(clause);
+                        if( colindex ) free( colindex );
+                        if( file_expr ) free( file_expr );
+                        return(*status);
+                    }
+                }
+              }  
+              else
+              {
+                /*...................................................... */
+                /* Case B: */
+                /* this must be a general column/keyword calc expression */
+                /* "name = expression" or "colname(TFORM) = expression" */
+                /*...................................................... */
+
+                /* parse the name and TFORM values, if present */
+                colformat[0] = '\0';
+                cptr3 = colname;
+
+                fits_get_token(&cptr3, "(", oldname, NULL);
+
+                if (cptr3[0] == '(' )
+                {
+                   cptr3++;  /* skip the '(' */
+                   fits_get_token(&cptr3, ")", colformat, NULL);
+                }
+
+                /* calculate values for the column or keyword */
+                /*   cptr2 = the expression to be calculated */
+                /*   oldname = name of the column or keyword */
+                /*   colformat = column format, or keyword comment string */
+                if (fits_calculator(*fptr, cptr2, *fptr, oldname, colformat,
+       	                        status) > 0) {
+				
+                        ffpmsg("Unable to calculate expression");
+                        return(*status);
+                }
+
+                /* test if this is a column and not a keyword */
+                tstatus = 0;
+                ffgcno(*fptr, CASEINSEN, oldname, &testnum, &tstatus);
+                if (tstatus == 0)
+                {
+                    /* keep this column in the output file */
+		    colnum = testnum;
+                    savecol = 1;
+
+                    if (!colindex)
+                      colindex = (int *) calloc(999, sizeof(int));
+
+                    colindex[colnum - 1] = 1;
+                    if (colnum > numcols)numcols++;
+                }
+		else
+		{
+		   ffcmsg();  /* clear the error message stack */
+		}
+              }
+            }
+        }
+    }
+
+    if (savecol && !deletecol)
+    {
+       /* need to delete all but the specified columns */
+       for (ii = numcols; ii > 0; ii--)
+       {
+         if (!colindex[ii-1])  /* delete this column */
+         {
+           if (ffdcol(*fptr, ii, status) > 0)
+           {
+             ffpmsg("failed to delete column in input file:");
+             ffpmsg(clause);
+             if( colindex ) free( colindex );
+             if( file_expr ) free( file_expr );
+             return(*status);
+           }
+         }
+       }
+    }
+
+    if( colindex ) free( colindex );
+    if( file_expr ) free( file_expr );
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_copy_cell2image(
+	   fitsfile *fptr,   /* I - point to input table */
+	   fitsfile *newptr, /* O - existing output file; new image HDU
+				    will be appended to it */
+           char *colname,    /* I - column name / number containing the image*/
+           long rownum,      /* I - number of the row containing the image */
+           int *status)      /* IO - error status */
+
+/*
+  Copy a table cell of a given row and column into an image extension.
+  The output file must already have been created.  A new image
+  extension will be created in that file.
+  
+  This routine was written by Craig Markwardt, GSFC
+*/
+
+{
+    unsigned char buffer[30000];
+    int hdutype, colnum, typecode, bitpix, naxis, maxelem, tstatus;
+    LONGLONG naxes[9], nbytes, firstbyte, ntodo;
+    LONGLONG repeat, startpos, elemnum, rowlen, tnull;
+    long twidth, incre;
+    double scale, zero;
+    char tform[20];
+    char card[FLEN_CARD];
+    char templt[FLEN_CARD] = "";
+
+    /* Table-to-image keyword translation table  */
+    /*                        INPUT      OUTPUT  */
+    /*                       01234567   01234567 */
+    char *patterns[][2] = {{"TSCALn",  "BSCALE"  },  /* Standard FITS keywords */
+			   {"TZEROn",  "BZERO"   },
+			   {"TUNITn",  "BUNIT"   },
+			   {"TNULLn",  "BLANK"   },
+			   {"TDMINn",  "DATAMIN" },
+			   {"TDMAXn",  "DATAMAX" },
+			   {"iCTYPn",  "CTYPEi"  },  /* Coordinate labels */
+			   {"iCTYna",  "CTYPEia" },
+			   {"iCUNIn",  "CUNITi"  },  /* Coordinate units */
+			   {"iCUNna",  "CUNITia" },
+			   {"iCRVLn",  "CRVALi"  },  /* WCS keywords */
+			   {"iCRVna",  "CRVALia" },
+			   {"iCDLTn",  "CDELTi"  },
+			   {"iCDEna",  "CDELTia" },
+			   {"iCRPXn",  "CRPIXi"  },
+			   {"iCRPna",  "CRPIXia" },
+			   {"ijPCna",  "PCi_ja"  },
+			   {"ijCDna",  "CDi_ja"  },
+			   {"iVn_ma",  "PVi_ma"  },
+			   {"iSn_ma",  "PSi_ma"  },
+			   {"iCRDna",  "CRDERia" },
+			   {"iCSYna",  "CSYERia" },
+			   {"iCROTn",  "CROTAi"  },
+			   {"WCAXna",  "WCSAXESa"},
+			   {"WCSNna",  "WCSNAMEa"},
+
+			   {"LONPna",  "LONPOLEa"},
+			   {"LATPna",  "LATPOLEa"},
+			   {"EQUIna",  "EQUINOXa"},
+			   {"MJDOBn",  "MJD-OBS" },
+			   {"MJDAn",   "MJD-AVG" },
+			   {"RADEna",  "RADESYSa"},
+			   {"iCNAna",  "CNAMEia" },
+			   {"DAVGn",   "DATE-AVG"},
+
+                           /* Delete table keywords related to other columns */
+			   {"T????#a", "-"       }, 
+ 			   {"TC??#a",  "-"       },
+ 			   {"TWCS#a",  "-"       },
+			   {"TDIM#",   "-"       }, 
+			   {"iCTYPm",  "-"       },
+			   {"iCUNIm",  "-"       },
+			   {"iCRVLm",  "-"       },
+			   {"iCDLTm",  "-"       },
+			   {"iCRPXm",  "-"       },
+			   {"iCTYma",  "-"       },
+			   {"iCUNma",  "-"       },
+			   {"iCRVma",  "-"       },
+			   {"iCDEma",  "-"       },
+			   {"iCRPma",  "-"       },
+			   {"ijPCma",  "-"       },
+			   {"ijCDma",  "-"       },
+			   {"iVm_ma",  "-"       },
+			   {"iSm_ma",  "-"       },
+			   {"iCRDma",  "-"       },
+			   {"iCSYma",  "-"       },
+			   {"iCROTm",  "-"       },
+			   {"WCAXma",  "-"       },
+			   {"WCSNma",  "-"       },
+
+			   {"LONPma",  "-"       },
+			   {"LATPma",  "-"       },
+			   {"EQUIma",  "-"       },
+			   {"MJDOBm",  "-"       },
+			   {"MJDAm",   "-"       },
+			   {"RADEma",  "-"       },
+			   {"iCNAma",  "-"       },
+			   {"DAVGm",   "-"       },
+
+			   {"EXTNAME", "-"       },  /* Remove structural keywords*/
+			   {"EXTVER",  "-"       },
+			   {"EXTLEVEL","-"       },
+			   {"CHECKSUM","-"       },
+			   {"DATASUM", "-"       },
+			   
+			   {"*",       "+"       }}; /* copy all other keywords */
+    int npat;
+
+    if (*status > 0)
+        return(*status);
+
+    /* get column number */
+    if (ffgcno(fptr, CASEINSEN, colname, &colnum, status) > 0)
+    {
+        ffpmsg("column containing image in table cell does not exist:");
+        ffpmsg(colname);
+        return(*status);
+    }
+
+    /*---------------------------------------------------*/
+    /*  Check input and get parameters about the column: */
+    /*---------------------------------------------------*/
+    if ( ffgcprll(fptr, colnum, rownum, 1L, 1L, 0, &scale, &zero,
+         tform, &twidth, &typecode, &maxelem, &startpos, &elemnum, &incre,
+         &repeat, &rowlen, &hdutype, &tnull, (char *) buffer, status) > 0 )
+         return(*status);
+
+     /* get the actual column name, in case a column number was given */
+    ffkeyn("", colnum, templt, &tstatus);
+    ffgcnn(fptr, CASEINSEN, templt, colname, &colnum, &tstatus);
+
+    if (hdutype != BINARY_TBL)
+    {
+        ffpmsg("This extension is not a binary table.");
+        ffpmsg(" Cannot open the image in a binary table cell.");
+        return(*status = NOT_BTABLE);
+    }
+
+    if (typecode < 0)
+    {
+        /* variable length array */
+        typecode *= -1;  
+
+        /* variable length arrays are 1-dimensional by default */
+        naxis = 1;
+        naxes[0] = repeat;
+    }
+    else
+    {
+        /* get the dimensions of the image */
+        ffgtdmll(fptr, colnum, 9, &naxis, naxes, status);
+    }
+
+    if (*status > 0)
+    {
+        ffpmsg("Error getting the dimensions of the image");
+        return(*status);
+    }
+
+    /* determine BITPIX value for the image */
+    if (typecode == TBYTE)
+    {
+        bitpix = BYTE_IMG;
+        nbytes = repeat;
+    }
+    else if (typecode == TSHORT)
+    {
+        bitpix = SHORT_IMG;
+        nbytes = repeat * 2;
+    }
+    else if (typecode == TLONG)
+    {
+        bitpix = LONG_IMG;
+        nbytes = repeat * 4;
+    }
+    else if (typecode == TFLOAT)
+    {
+        bitpix = FLOAT_IMG;
+        nbytes = repeat * 4;
+    }
+    else if (typecode == TDOUBLE)
+    {
+        bitpix = DOUBLE_IMG;
+        nbytes = repeat * 8;
+    }
+    else if (typecode == TLONGLONG)
+    {
+        bitpix = LONGLONG_IMG;
+        nbytes = repeat * 8;
+    }
+    else if (typecode == TLOGICAL)
+    {
+        bitpix = BYTE_IMG;
+        nbytes = repeat;
+    }
+    else
+    {
+        ffpmsg("Error: the following image column has invalid datatype:");
+        ffpmsg(colname);
+        ffpmsg(tform);
+        ffpmsg("Cannot open an image in a single row of this column.");
+        return(*status = BAD_TFORM);
+    }
+
+    /* create new image in output file */
+    if (ffcrimll(newptr, bitpix, naxis, naxes, status) > 0)
+    {
+        ffpmsg("failed to write required primary array keywords in the output file");
+        return(*status);
+    }
+
+    npat = sizeof(patterns)/sizeof(patterns[0][0])/2;
+    
+    /* skip over the first 8 keywords, starting just after TFIELDS */
+    fits_translate_keywords(fptr, newptr, 9, patterns, npat,
+			    colnum, 0, 0, status);
+
+    /* add some HISTORY  */
+    sprintf(card,"HISTORY  This image was copied from row %ld of column '%s',",
+            rownum, colname);
+/* disable this; leave it up to the caller to write history if needed.    
+    ffprec(newptr, card, status);
+*/
+    /* the use of ffread routine, below, requires that any 'dirty' */
+    /* buffers in memory be flushed back to the file first */
+    
+    ffflsh(fptr, FALSE, status);
+
+    /* finally, copy the data, one buffer size at a time */
+    ffmbyt(fptr, startpos, TRUE, status);
+    firstbyte = 1; 
+
+    /* the upper limit on the number of bytes must match the declaration */
+    /* read up to the first 30000 bytes in the normal way with ffgbyt */
+
+    ntodo = minvalue(30000, nbytes);
+    ffgbyt(fptr, ntodo, buffer, status);
+    ffptbb(newptr, 1, firstbyte, ntodo, buffer, status);
+
+    nbytes    -= ntodo;
+    firstbyte += ntodo;
+
+    /* read any additional bytes with low-level ffread routine, for speed */
+    while (nbytes && (*status <= 0) )
+    {
+        ntodo = minvalue(30000, nbytes);
+        ffread((fptr)->Fptr, (long) ntodo, buffer, status);
+        ffptbb(newptr, 1, firstbyte, ntodo, buffer, status);
+        nbytes    -= ntodo;
+        firstbyte += ntodo;
+    }
+
+    /* Re-scan the header so that CFITSIO knows about all the new keywords */
+    ffrdef(newptr,status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_copy_image2cell(
+	   fitsfile *fptr,   /* I - pointer to input image extension */
+	   fitsfile *newptr, /* I - pointer to output table */
+           char *colname,    /* I - name of column containing the image    */
+           long rownum,      /* I - number of the row containing the image */
+           int copykeyflag,  /* I - controls which keywords to copy */
+           int *status)      /* IO - error status */
+
+/* 
+   Copy an image extension into a table cell at a given row and
+   column.  The table must have already been created.  If the "colname"
+   column exists, it will be used, otherwise a new column will be created
+   in the table.
+
+   The "copykeyflag" parameter controls which keywords to copy from the 
+   input image to the output table header (with any appropriate translation).
+ 
+   copykeyflag = 0  -- no keywords will be copied
+   copykeyflag = 1  -- essentially all keywords will be copied
+   copykeyflag = 2  -- copy only the WCS related keywords 
+   
+  This routine was written by Craig Markwardt, GSFC
+
+*/
+{
+    tcolumn *colptr;
+    unsigned char buffer[30000];
+    int ii, hdutype, colnum, typecode, bitpix, naxis, ncols, hdunum;
+    char tformchar, tform[20], card[FLEN_CARD];
+    LONGLONG imgstart, naxes[9], nbytes, repeat, ntodo,firstbyte;
+    char filename[FLEN_FILENAME+20];
+
+    int npat;
+
+    int naxis1;
+    LONGLONG naxes1[9] = {0,0,0,0,0,0,0,0,0}, repeat1, width1;
+    int typecode1;
+    unsigned char dummy = 0;
+
+    LONGLONG headstart, datastart, dataend;
+
+    /* Image-to-table keyword translation table  */
+    /*                        INPUT      OUTPUT  */
+    /*                       01234567   01234567 */
+    char *patterns[][2] = {{"BSCALE",  "TSCALn"  },  /* Standard FITS keywords */
+			   {"BZERO",   "TZEROn"  },
+			   {"BUNIT",   "TUNITn"  },
+			   {"BLANK",   "TNULLn"  },
+			   {"DATAMIN", "TDMINn"  },
+			   {"DATAMAX", "TDMAXn"  },
+			   {"CTYPEi",  "iCTYPn"  },  /* Coordinate labels */
+			   {"CTYPEia", "iCTYna"  },
+			   {"CUNITi",  "iCUNIn"  },  /* Coordinate units */
+			   {"CUNITia", "iCUNna"  },
+			   {"CRVALi",  "iCRVLn"  },  /* WCS keywords */
+			   {"CRVALia", "iCRVna"  },
+			   {"CDELTi",  "iCDLTn"  },
+			   {"CDELTia", "iCDEna"  },
+			   {"CRPIXj",  "jCRPXn"  },
+			   {"CRPIXja", "jCRPna"  },
+			   {"PCi_ja",  "ijPCna"  },
+			   {"CDi_ja",  "ijCDna"  },
+			   {"PVi_ma",  "iVn_ma"  },
+			   {"PSi_ma",  "iSn_ma"  },
+			   {"WCSAXESa","WCAXna"  },
+			   {"WCSNAMEa","WCSNna"  },
+			   {"CRDERia", "iCRDna"  },
+			   {"CSYERia", "iCSYna"  },
+			   {"CROTAi",  "iCROTn"  },
+
+			   {"LONPOLEa","LONPna"},
+			   {"LATPOLEa","LATPna"},
+			   {"EQUINOXa","EQUIna"},
+			   {"MJD-OBS", "MJDOBn" },
+			   {"MJD-AVG", "MJDAn" },
+			   {"RADESYSa","RADEna"},
+			   {"CNAMEia", "iCNAna"  },
+			   {"DATE-AVG","DAVGn"},
+
+			   {"NAXISi",  "-"       },  /* Remove structural keywords*/
+			   {"PCOUNT",  "-"       },
+			   {"GCOUNT",  "-"       },
+			   {"EXTEND",  "-"       },
+			   {"EXTNAME", "-"       },
+			   {"EXTVER",  "-"       },
+			   {"EXTLEVEL","-"       },
+			   {"CHECKSUM","-"       },
+			   {"DATASUM", "-"       },
+			   {"*",       "+"       }}; /* copy all other keywords */
+
+    
+    if (*status > 0)
+        return(*status);
+
+    if (fptr == 0 || newptr == 0) return (*status = NULL_INPUT_PTR);
+
+    if (ffghdt(fptr, &hdutype, status) > 0) {
+      ffpmsg("could not get input HDU type");
+      return (*status);
+    }
+
+    if (hdutype != IMAGE_HDU) {
+        ffpmsg("The input extension is not an image.");
+        ffpmsg(" Cannot open the image.");
+        return(*status = NOT_IMAGE);
+    }
+
+    if (ffghdt(newptr, &hdutype, status) > 0) {
+      ffpmsg("could not get output HDU type");
+      return (*status);
+    }
+
+    if (hdutype != BINARY_TBL) {
+        ffpmsg("The output extension is not a table.");
+        return(*status = NOT_BTABLE);
+    }
+
+
+    if (ffgiprll(fptr, 9, &bitpix, &naxis, naxes, status) > 0) {
+      ffpmsg("Could not read image parameters.");
+      return (*status);
+    }
+
+    /* Determine total number of pixels in the image */
+    repeat = 1;
+    for (ii = 0; ii < naxis; ii++) repeat *= naxes[ii];
+
+    /* Determine the TFORM value for the table cell */
+    if (bitpix == BYTE_IMG) {
+      typecode = TBYTE;
+      tformchar = 'B';
+      nbytes = repeat;
+    } else if (bitpix == SHORT_IMG) {
+      typecode = TSHORT;
+      tformchar = 'I';
+      nbytes = repeat*2;
+    } else if (bitpix == LONG_IMG) {
+      typecode = TLONG;
+      tformchar = 'J';
+      nbytes = repeat*4;
+    } else if (bitpix == FLOAT_IMG) {
+      typecode = TFLOAT;
+      tformchar = 'E';
+      nbytes = repeat*4;
+    } else if (bitpix == DOUBLE_IMG) {
+      typecode = TDOUBLE;
+      tformchar = 'D';
+      nbytes = repeat*8;
+    } else if (bitpix == LONGLONG_IMG) {
+      typecode = TLONGLONG;
+      tformchar = 'K';
+      nbytes = repeat*8;
+    } else {
+      ffpmsg("Error: the image has an invalid datatype.");
+      return (*status = BAD_BITPIX);
+    }
+
+    /* get column number */
+    ffpmrk();
+    ffgcno(newptr, CASEINSEN, colname, &colnum, status);
+    ffcmrk();
+
+    /* Column does not exist; create it */
+    if (*status) {
+
+      *status = 0;
+      sprintf(tform, "%.0f%c", (double) repeat, tformchar);
+      ffgncl(newptr, &ncols, status);
+      colnum = ncols+1;
+      fficol(newptr, colnum, colname, tform, status);
+      ffptdmll(newptr, colnum, naxis, naxes, status);
+      
+      if (*status) {
+	ffpmsg("Could not insert new column into output table.");
+	return *status;
+      }
+
+    } else {
+
+      ffgtdmll(newptr, colnum, 9, &naxis1, naxes1, status);
+      if (*status > 0 || naxis != naxis1) {
+	ffpmsg("Input image dimensions and output table cell dimensions do not match.");
+	return (*status = BAD_DIMEN);
+      }
+      for (ii=0; ii<naxis; ii++) if (naxes[ii] != naxes1[ii]) {
+	ffpmsg("Input image dimensions and output table cell dimensions do not match.");
+	return (*status = BAD_DIMEN);
+      }
+
+      ffgtclll(newptr, colnum, &typecode1, &repeat1, &width1, status);
+      if ((*status > 0) || (typecode1 != typecode) || (repeat1 != repeat)) {
+	ffpmsg("Input image data type does not match output table cell type.");
+	return (*status = BAD_TFORM);
+      }
+    }
+
+    /* copy keywords from input image to output table, if required */
+    
+    if (copykeyflag) {
+    
+      npat = sizeof(patterns)/sizeof(patterns[0][0])/2;
+
+      if (copykeyflag == 2) {   /* copy only the WCS-related keywords */
+	patterns[npat-1][1] = "-";
+      }
+
+      /* The 3rd parameter value = 5 means skip the first 4 keywords in the image */
+      fits_translate_keywords(fptr, newptr, 5, patterns, npat,
+			      colnum, 0, 0, status);
+    }
+
+    /* Here is all the code to compute offsets:
+     *     * byte offset from start of row to column (dest table)
+     *     * byte offset from start of file to image data (source image)
+     */   
+ 
+    /* Force the writing of the row of the table by writing the last byte of
+        the array, which grows the table, and/or shifts following extensions */
+    ffpcl(newptr, TBYTE, colnum, rownum, repeat, 1, &dummy, status);
+
+    /* byte offset within the row to the start of the image column */
+    colptr  = (newptr->Fptr)->tableptr;   /* point to first column */
+    colptr += (colnum - 1);     /* offset to correct column structure */
+    firstbyte = colptr->tbcol + 1; 
+
+    /* get starting address of input image to be read */
+    ffghadll(fptr, &headstart, &datastart, &dataend, status);
+    imgstart = datastart;
+
+    sprintf(card, "HISTORY  Table column '%s' row %ld copied from image",
+	    colname, rownum);
+/*
+  Don't automatically write History keywords; leave this up to the caller. 
+    ffprec(newptr, card, status);
+*/
+
+    /* write HISTORY keyword with the file name (this is now disabled)*/
+
+    filename[0] = '\0'; hdunum = 0;
+    strcpy(filename, "HISTORY   ");
+    ffflnm(fptr, filename+strlen(filename), status);
+    ffghdn(fptr, &hdunum);
+    sprintf(filename+strlen(filename),"[%d]", hdunum-1);
+/*
+    ffprec(newptr, filename, status);
+*/
+
+    /* the use of ffread routine, below, requires that any 'dirty' */
+    /* buffers in memory be flushed back to the file first */
+    
+    ffflsh(fptr, FALSE, status);
+
+    /* move to the first byte of the input image */
+    ffmbyt(fptr, imgstart, TRUE, status);
+
+    ntodo = minvalue(30000L, nbytes);
+    ffgbyt(fptr, ntodo, buffer, status);  /* read input image */
+    ffptbb(newptr, rownum, firstbyte, ntodo, buffer, status); /* write to table */
+
+    nbytes    -= ntodo;
+    firstbyte += ntodo;
+
+
+    /* read any additional bytes with low-level ffread routine, for speed */
+    while (nbytes && (*status <= 0) )
+    {
+        ntodo = minvalue(30000L, nbytes);
+        ffread(fptr->Fptr, (long) ntodo, buffer, status);
+        ffptbb(newptr, rownum, firstbyte, ntodo, buffer, status);
+        nbytes    -= ntodo;
+        firstbyte += ntodo;
+    }
+
+    /* Re-scan the header so that CFITSIO knows about all the new keywords */
+    ffrdef(newptr,status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_select_image_section(
+           fitsfile **fptr,  /* IO - pointer to input image; on output it  */
+                             /*      points to the new subimage */
+           char *outfile,    /* I - name for output file        */
+           char *expr,       /* I - Image section expression    */
+           int *status)
+{
+  /*
+     copies an image section from the input file to a new output file.
+     Any HDUs preceding or following the image are also copied to the
+     output file.
+  */
+
+    fitsfile *newptr;
+    int ii, hdunum;
+
+    /* create new empty file to hold the image section */
+    if (ffinit(&newptr, outfile, status) > 0)
+    {
+        ffpmsg(
+         "failed to create output file for image section:");
+        ffpmsg(outfile);
+        return(*status);
+    }
+
+    fits_get_hdu_num(*fptr, &hdunum);  /* current HDU number in input file */
+
+    /* copy all preceding extensions to the output file, if 'only_one' flag not set */
+    if (!(((*fptr)->Fptr)->only_one)) {
+      for (ii = 1; ii < hdunum; ii++)
+      {
+        fits_movabs_hdu(*fptr, ii, NULL, status);
+        if (fits_copy_hdu(*fptr, newptr, 0, status) > 0)
+        {
+            ffclos(newptr, status);
+            return(*status);
+        }
+      }
+
+      /* move back to the original HDU position */
+      fits_movabs_hdu(*fptr, hdunum, NULL, status);
+    }
+
+    if (fits_copy_image_section(*fptr, newptr, expr, status) > 0)
+    {
+        ffclos(newptr, status);
+        return(*status);
+    }
+
+    /* copy any remaining HDUs to the output file, if 'only_one' flag not set */
+
+    if (!(((*fptr)->Fptr)->only_one)) {
+      for (ii = hdunum + 1; 1; ii++)
+      {
+        if (fits_movabs_hdu(*fptr, ii, NULL, status) > 0)
+            break;
+
+        fits_copy_hdu(*fptr, newptr, 0, status);
+      }
+
+      if (*status == END_OF_FILE)   
+        *status = 0;              /* got the expected EOF error; reset = 0  */
+      else if (*status > 0)
+      {
+        ffclos(newptr, status);
+        return(*status);
+      }
+    } else {
+      ii = hdunum + 1;  /* this value of ii is required below */
+    }
+
+    /* close the original file and return ptr to the new image */
+    ffclos(*fptr, status);
+
+    *fptr = newptr; /* reset the pointer to the new table */
+
+    /* move back to the image subsection */
+    if (ii - 1 != hdunum)
+        fits_movabs_hdu(*fptr, hdunum, NULL, status);
+    else
+    {
+        /* may have to reset BSCALE and BZERO pixel scaling, */
+        /* since the keywords were previously turned off */
+
+        if (ffrdef(*fptr, status) > 0)  
+        {
+            ffclos(*fptr, status);
+            return(*status);
+        }
+
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_copy_image_section(
+           fitsfile *fptr,  /* I - pointer to input image */
+           fitsfile *newptr,  /* I - pointer to output image */
+           char *expr,       /* I - Image section expression    */
+           int *status)
+{
+  /*
+     copies an image section from the input file to a new output HDU
+  */
+
+    int bitpix, naxis, numkeys, nkey;
+    long naxes[] = {1,1,1,1,1,1,1,1,1}, smin, smax, sinc;
+    long fpixels[] = {1,1,1,1,1,1,1,1,1};
+    long lpixels[] = {1,1,1,1,1,1,1,1,1};
+    long incs[] = {1,1,1,1,1,1,1,1,1};
+    char *cptr, keyname[FLEN_KEYWORD], card[FLEN_CARD];
+    int ii, tstatus, anynull;
+    long minrow, maxrow, minslice, maxslice, mincube, maxcube;
+    long firstpix;
+    long ncubeiter, nsliceiter, nrowiter, kiter, jiter, iiter;
+    int klen, kk, jj;
+    long outnaxes[9], outsize, buffsize;
+    double *buffer, crpix, cdelt;
+
+    if (*status > 0)
+        return(*status);
+
+    /* get the size of the input image */
+    fits_get_img_type(fptr, &bitpix, status);
+    fits_get_img_dim(fptr, &naxis, status);
+    if (fits_get_img_size(fptr, naxis, naxes, status) > 0)
+        return(*status);
+
+    if (naxis < 1 || naxis > 4)
+    {
+        ffpmsg(
+        "Input image either had NAXIS = 0 (NULL image) or has > 4 dimensions");
+        return(*status = BAD_NAXIS);
+    }
+
+    /* create output image with same size and type as the input image */
+    /*  Will update the size later */
+    fits_create_img(newptr, bitpix, naxis, naxes, status);
+
+    /* copy all other non-structural keywords from the input to output file */
+    fits_get_hdrspace(fptr, &numkeys, NULL, status);
+
+    for (nkey = 4; nkey <= numkeys; nkey++) /* skip the first few keywords */
+    {
+        fits_read_record(fptr, nkey, card, status);
+
+        if (fits_get_keyclass(card) > TYP_CMPRS_KEY)
+        {
+            /* write the record to the output file */
+            fits_write_record(newptr, card, status);
+        }
+    }
+
+    if (*status > 0)
+    {
+         ffpmsg("error copying header from input image to output image");
+         return(*status);
+    }
+
+    /* parse the section specifier to get min, max, and inc for each axis */
+    /* and the size of each output image axis */
+
+    cptr = expr;
+    for (ii=0; ii < naxis; ii++)
+    {
+       if (fits_get_section_range(&cptr, &smin, &smax, &sinc, status) > 0)
+       {
+          ffpmsg("error parsing the following image section specifier:");
+          ffpmsg(expr);
+          return(*status);
+       }
+
+       if (smax == 0)
+          smax = naxes[ii];   /* use whole axis  by default */
+       else if (smin == 0)
+          smin = naxes[ii];   /* use inverted whole axis */
+
+       if (smin > naxes[ii] || smax > naxes[ii])
+       {
+          ffpmsg("image section exceeds dimensions of input image:");
+          ffpmsg(expr);
+          return(*status = BAD_NAXIS);
+       }
+
+       fpixels[ii] = smin;
+       lpixels[ii] = smax;
+       incs[ii] = sinc;
+
+       if (smin <= smax)
+           outnaxes[ii] = (smax - smin + sinc) / sinc;
+       else
+           outnaxes[ii] = (smin - smax + sinc) / sinc;
+
+       /* modify the NAXISn keyword */
+       fits_make_keyn("NAXIS", ii + 1, keyname, status);
+       fits_modify_key_lng(newptr, keyname, outnaxes[ii], NULL, status);
+
+       /* modify the WCS keywords if necessary */
+
+       if (fpixels[ii] != 1 || incs[ii] != 1)
+       {
+            for (kk=-1;kk<26; kk++)  /* modify any alternate WCS keywords */
+	{
+         /* read the CRPIXn keyword if it exists in the input file */
+         fits_make_keyn("CRPIX", ii + 1, keyname, status);
+	 
+         if (kk != -1) {
+	   klen = strlen(keyname);
+	   keyname[klen]='A' + kk;
+	   keyname[klen + 1] = '\0';
+	 }
+
+         tstatus = 0;
+         if (fits_read_key(fptr, TDOUBLE, keyname, 
+             &crpix, NULL, &tstatus) == 0)
+         {
+           /* calculate the new CRPIXn value */
+           if (fpixels[ii] <= lpixels[ii]) {
+             crpix = (crpix - (fpixels[ii])) / incs[ii] + 1.0;
+              /*  crpix = (crpix - (fpixels[ii] - 1.0) - .5) / incs[ii] + 0.5; */
+           } else {
+             crpix = (fpixels[ii] - crpix)  / incs[ii] + 1.0;
+             /* crpix = (fpixels[ii] - (crpix - 1.0) - .5) / incs[ii] + 0.5; */
+           }
+
+           /* modify the value in the output file */
+           fits_modify_key_dbl(newptr, keyname, crpix, 15, NULL, status);
+
+           if (incs[ii] != 1 || fpixels[ii] > lpixels[ii])
+           {
+             /* read the CDELTn keyword if it exists in the input file */
+             fits_make_keyn("CDELT", ii + 1, keyname, status);
+
+             if (kk != -1) {
+	       klen = strlen(keyname);
+	       keyname[klen]='A' + kk;
+	       keyname[klen + 1] = '\0';
+	     }
+
+             tstatus = 0;
+             if (fits_read_key(fptr, TDOUBLE, keyname, 
+                 &cdelt, NULL, &tstatus) == 0)
+             {
+               /* calculate the new CDELTn value */
+               if (fpixels[ii] <= lpixels[ii])
+                 cdelt = cdelt * incs[ii];
+               else
+                 cdelt = cdelt * (-incs[ii]);
+              
+               /* modify the value in the output file */
+               fits_modify_key_dbl(newptr, keyname, cdelt, 15, NULL, status);
+             }
+
+             /* modify the CDi_j keywords if they exist in the input file */
+
+             fits_make_keyn("CD1_", ii + 1, keyname, status);
+
+             if (kk != -1) {
+	       klen = strlen(keyname);
+	       keyname[klen]='A' + kk;
+	       keyname[klen + 1] = '\0';
+	     }
+
+             for (jj=0; jj < 9; jj++)   /* look for up to 9 dimensions */
+	     {
+	       keyname[2] = '1' + jj;
+	       
+               tstatus = 0;
+               if (fits_read_key(fptr, TDOUBLE, keyname, 
+                 &cdelt, NULL, &tstatus) == 0)
+               {
+                 /* calculate the new CDi_j value */
+                 if (fpixels[ii] <= lpixels[ii])
+                   cdelt = cdelt * incs[ii];
+                 else
+                   cdelt = cdelt * (-incs[ii]);
+              
+                 /* modify the value in the output file */
+                 fits_modify_key_dbl(newptr, keyname, cdelt, 15, NULL, status);
+               }
+	     }
+	     
+           } /* end of if (incs[ii]... loop */
+         }   /* end of fits_read_key loop */
+	}    /* end of for (kk  loop */
+       }
+    }  /* end of main NAXIS loop */
+
+    if (ffrdef(newptr, status) > 0)  /* force the header to be scanned */
+    {
+        return(*status);
+    }
+
+    /* turn off any scaling of the pixel values */
+    fits_set_bscale(fptr,  1.0, 0.0, status);
+    fits_set_bscale(newptr, 1.0, 0.0, status);
+
+    /* to reduce memory foot print, just read/write image 1 row at a time */
+
+    outsize = outnaxes[0];
+    buffsize = (abs(bitpix) / 8) * outsize;
+
+    buffer = (double *) malloc(buffsize); /* allocate memory for the image row */
+    if (!buffer)
+    {
+        ffpmsg("fits_copy_image_section: no memory for image section");
+        return(*status = MEMORY_ALLOCATION);
+    }
+
+    /* read the image section then write it to the output file */
+
+    minrow = fpixels[1];
+    maxrow = lpixels[1];
+    if (minrow > maxrow) {
+        nrowiter = (minrow - maxrow + incs[1]) / incs[1];
+    } else {
+        nrowiter = (maxrow - minrow + incs[1]) / incs[1];
+    }
+
+    minslice = fpixels[2];
+    maxslice = lpixels[2];
+    if (minslice > maxslice) {
+        nsliceiter = (minslice - maxslice + incs[2]) / incs[2];
+    } else {
+        nsliceiter = (maxslice - minslice + incs[2]) / incs[2];
+    }
+
+    mincube = fpixels[3];
+    maxcube = lpixels[3];
+    if (mincube > maxcube) {
+        ncubeiter = (mincube - maxcube + incs[3]) / incs[3];
+    } else {
+        ncubeiter = (maxcube - mincube + incs[3]) / incs[3];
+    }
+
+    firstpix = 1;
+    for (kiter = 0; kiter < ncubeiter; kiter++)
+    {
+      if (mincube > maxcube) {
+	 fpixels[3] = mincube - (kiter * incs[3]);
+      } else {
+	 fpixels[3] = mincube + (kiter * incs[3]);
+      }
+      
+      lpixels[3] = fpixels[3];
+
+      for (jiter = 0; jiter < nsliceiter; jiter++)
+      {
+        if (minslice > maxslice) {
+	    fpixels[2] = minslice - (jiter * incs[2]);
+        } else {
+	    fpixels[2] = minslice + (jiter * incs[2]);
+        }
+
+	lpixels[2] = fpixels[2];
+
+        for (iiter = 0; iiter < nrowiter; iiter++)
+        {
+            if (minrow > maxrow) {
+	       fpixels[1] = minrow - (iiter * incs[1]);
+	    } else {
+	       fpixels[1] = minrow + (iiter * incs[1]);
+            }
+
+	    lpixels[1] = fpixels[1];
+
+	    if (bitpix == 8)
+	    {
+	        ffgsvb(fptr, 1, naxis, naxes, fpixels, lpixels, incs, 0,
+	            (unsigned char *) buffer, &anynull, status);
+
+	        ffpprb(newptr, 1, firstpix, outsize, (unsigned char *) buffer, status);
+	    }
+	    else if (bitpix == 16)
+	    {
+	        ffgsvi(fptr, 1, naxis, naxes, fpixels, lpixels, incs, 0,
+	            (short *) buffer, &anynull, status);
+
+	        ffppri(newptr, 1, firstpix, outsize, (short *) buffer, status);
+	    }
+	    else if (bitpix == 32)
+	    {
+	        ffgsvk(fptr, 1, naxis, naxes, fpixels, lpixels, incs, 0,
+	            (int *) buffer, &anynull, status);
+
+	        ffpprk(newptr, 1, firstpix, outsize, (int *) buffer, status);
+	    }
+	    else if (bitpix == -32)
+	    {
+	        ffgsve(fptr, 1, naxis, naxes, fpixels, lpixels, incs, FLOATNULLVALUE,
+	            (float *) buffer, &anynull, status);
+
+	        ffppne(newptr, 1, firstpix, outsize, (float *) buffer, FLOATNULLVALUE, status);
+	    }
+	    else if (bitpix == -64)
+	    {
+	        ffgsvd(fptr, 1, naxis, naxes, fpixels, lpixels, incs, DOUBLENULLVALUE,
+	             buffer, &anynull, status);
+
+	        ffppnd(newptr, 1, firstpix, outsize, buffer, DOUBLENULLVALUE,
+	               status);
+	    }
+	    else if (bitpix == 64)
+	    {
+	        ffgsvjj(fptr, 1, naxis, naxes, fpixels, lpixels, incs, 0,
+	            (LONGLONG *) buffer, &anynull, status);
+
+	        ffpprjj(newptr, 1, firstpix, outsize, (LONGLONG *) buffer, status);
+	    }
+
+            firstpix += outsize;
+        }
+      }
+    }
+
+    free(buffer);  /* finished with the memory */
+
+    if (*status > 0)
+    {
+        ffpmsg("fits_copy_image_section: error copying image section");
+        return(*status);
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_get_section_range(char **ptr, 
+                   long *secmin,
+                   long *secmax, 
+                   long *incre,
+                   int *status)
+/*
+   Parse the input image section specification string, returning 
+   the  min, max and increment values.
+   Typical string =   "1:512:2"  or "1:512"
+*/
+{
+    int slen, isanumber;
+    char token[FLEN_VALUE];
+
+    if (*status > 0)
+        return(*status);
+
+    slen = fits_get_token(ptr, " ,:", token, &isanumber); /* get 1st token */
+
+    /* support [:2,:2] type syntax, where the leading * is implied */
+    if (slen==0) strcpy(token,"*");
+
+    if (*token == '*')  /* wild card means to use the whole range */
+    {
+       *secmin = 1;
+       *secmax = 0;
+    }
+    else if (*token == '-' && *(token+1) == '*' )  /* invert the whole range */
+    {
+       *secmin = 0;
+       *secmax = 1;
+    }
+    else
+    {
+      if (slen == 0 || !isanumber || **ptr != ':')
+        return(*status = URL_PARSE_ERROR);   
+
+      /* the token contains the min value */
+      *secmin = atol(token);
+
+      (*ptr)++;  /* skip the colon between the min and max values */
+      slen = fits_get_token(ptr, " ,:", token, &isanumber); /* get token */
+
+      if (slen == 0 || !isanumber)
+        return(*status = URL_PARSE_ERROR);   
+
+      /* the token contains the max value */
+      *secmax = atol(token);
+    }
+
+    if (**ptr == ':')
+    {
+        (*ptr)++;  /* skip the colon between the max and incre values */
+        slen = fits_get_token(ptr, " ,", token, &isanumber); /* get token */
+
+        if (slen == 0 || !isanumber)
+            return(*status = URL_PARSE_ERROR);   
+
+        *incre = atol(token);
+    }
+    else
+        *incre = 1;  /* default increment if none is supplied */
+
+    if (**ptr == ',')
+        (*ptr)++;
+
+    while (**ptr == ' ')   /* skip any trailing blanks */
+         (*ptr)++;
+
+    if (*secmin < 0 || *secmax < 0 || *incre < 1)
+        *status = URL_PARSE_ERROR;
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffselect_table(
+           fitsfile **fptr,  /* IO - pointer to input table; on output it  */
+                             /*      points to the new selected rows table */
+           char *outfile,    /* I - name for output file */
+           char *expr,       /* I - Boolean expression    */
+           int *status)
+{
+    fitsfile *newptr;
+    int ii, hdunum;
+
+    if (*outfile)
+    {
+      /* create new empty file in to hold the selected rows */
+      if (ffinit(&newptr, outfile, status) > 0)
+      {
+        ffpmsg(
+         "failed to create file for selected rows from input table");
+        ffpmsg(outfile);
+        return(*status);
+      }
+
+      fits_get_hdu_num(*fptr, &hdunum);  /* current HDU number in input file */
+
+      /* copy all preceding extensions to the output file, if the 'only_one' flag is not set */
+      if (!((*fptr)->Fptr)->only_one) {
+        for (ii = 1; ii < hdunum; ii++)
+        {
+          fits_movabs_hdu(*fptr, ii, NULL, status);
+          if (fits_copy_hdu(*fptr, newptr, 0, status) > 0)
+          {
+            ffclos(newptr, status);
+            return(*status);
+          }
+        }
+      } else {
+          /* just copy the primary array */
+          fits_movabs_hdu(*fptr, 1, NULL, status);
+          if (fits_copy_hdu(*fptr, newptr, 0, status) > 0)
+          {
+            ffclos(newptr, status);
+            return(*status);
+          }
+      }
+      
+      fits_movabs_hdu(*fptr, hdunum, NULL, status);
+
+      /* copy all the header keywords from the input to output file */
+      if (fits_copy_header(*fptr, newptr, status) > 0)
+      {
+        ffclos(newptr, status);
+        return(*status);
+      }
+
+      /* set number of rows = 0 */
+      fits_modify_key_lng(newptr, "NAXIS2", 0, NULL,status);
+      (newptr->Fptr)->numrows = 0;
+      (newptr->Fptr)->origrows = 0;
+
+      if (ffrdef(newptr, status) > 0)  /* force the header to be scanned */
+      {
+        ffclos(newptr, status);
+        return(*status);
+      }
+    }
+    else
+        newptr = *fptr;  /* will delete rows in place in the table */
+
+    /* copy rows which satisfy the selection expression to the output table */
+    /* or delete the nonqualifying rows if *fptr = newptr.   */
+    if (fits_select_rows(*fptr, newptr, expr, status) > 0)
+    {
+        if (*outfile)
+            ffclos(newptr, status);
+
+        return(*status);
+    }
+
+    if (*outfile)
+    {
+      /* copy any remaining HDUs to the output copy */
+
+      if (!((*fptr)->Fptr)->only_one) {
+        for (ii = hdunum + 1; 1; ii++)
+        {
+          if (fits_movabs_hdu(*fptr, ii, NULL, status) > 0)
+            break;
+
+          fits_copy_hdu(*fptr, newptr, 0, status);
+        }
+
+        if (*status == END_OF_FILE)   
+          *status = 0;              /* got the expected EOF error; reset = 0  */
+        else if (*status > 0)
+        {
+          ffclos(newptr, status);
+          return(*status);
+        }
+      } else {
+        hdunum = 2;
+      }
+
+      /* close the original file and return ptr to the new image */
+      ffclos(*fptr, status);
+
+      *fptr = newptr; /* reset the pointer to the new table */
+
+      /* move back to the selected table HDU */
+      fits_movabs_hdu(*fptr, hdunum, NULL, status);
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffparsecompspec(fitsfile *fptr,  /* I - FITS file pointer               */
+           char *compspec,     /* I - image compression specification */
+           int *status)          /* IO - error status                       */
+/*
+  Parse the image compression specification that was give in square brackets
+  following the output FITS file name, as in these examples:
+
+    myfile.fits[compress]  - default Rice compression, row by row 
+    myfile.fits[compress TYPE] -  the first letter of TYPE defines the
+                                  compression algorithm:
+                                   R = Rice
+                                   G = GZIP
+                                   H = HCOMPRESS
+                                   HS = HCOMPRESS (with smoothing)
+				   B - BZIP2
+                                   P = PLIO
+
+    myfile.fits[compress TYPE 100,100] - the numbers give the dimensions
+                                         of the compression tiles.  Default
+                                         is NAXIS1, 1, 1, ...
+
+       other optional parameters may be specified following a semi-colon 
+       
+    myfile.fits[compress; q 8.0]          q specifies the floating point 
+    mufile.fits[compress TYPE; q -.0002]        quantization level;
+    myfile.fits[compress TYPE 100,100; q 10, s 25]  s specifies the HCOMPRESS
+                                                     integer scaling parameter
+
+The compression parameters are saved in the fptr->Fptr structure for use
+when writing FITS images.
+
+*/
+{
+    char *ptr1;
+
+    /* initialize with default values */
+    int ii, compresstype = RICE_1, smooth = 0;
+    long tilesize[MAX_COMPRESS_DIM] = {0,1,1,1,1,1};
+    float qlevel = 0.0, scale = 0.;
+
+    ptr1 = compspec;
+    while (*ptr1 == ' ')    /* ignore leading blanks */
+           ptr1++;
+
+    if (strncmp(ptr1, "compress", 8) && strncmp(ptr1, "COMPRESS", 8) )
+    {
+       /* apparently this string does not specify compression parameters */
+       return(*status = URL_PARSE_ERROR);
+    }
+
+    ptr1 += 8;
+    while (*ptr1 == ' ')    /* ignore leading blanks */
+           ptr1++;
+
+    /* ========================= */
+    /* look for compression type */
+    /* ========================= */
+
+    if (*ptr1 == 'r' || *ptr1 == 'R')
+    {
+        compresstype = RICE_1;
+        while (*ptr1 != ' ' && *ptr1 != ';' && *ptr1 != '\0') 
+           ptr1++;
+    }
+    else if (*ptr1 == 'g' || *ptr1 == 'G')
+    {
+        compresstype = GZIP_1;
+        while (*ptr1 != ' ' && *ptr1 != ';' && *ptr1 != '\0') 
+           ptr1++;
+
+    }
+/*
+    else if (*ptr1 == 'b' || *ptr1 == 'B')
+    {
+        compresstype = BZIP2_1;
+        while (*ptr1 != ' ' && *ptr1 != ';' && *ptr1 != '\0') 
+           ptr1++;
+
+    }
+*/
+    else if (*ptr1 == 'p' || *ptr1 == 'P')
+    {
+        compresstype = PLIO_1;
+        while (*ptr1 != ' ' && *ptr1 != ';' && *ptr1 != '\0') 
+           ptr1++;
+    }
+    else if (*ptr1 == 'h' || *ptr1 == 'H')
+    {
+        compresstype = HCOMPRESS_1;
+        ptr1++;
+        if (*ptr1 == 's' || *ptr1 == 'S')
+           smooth = 1;  /* apply smoothing when uncompressing HCOMPRESSed image */
+
+        while (*ptr1 != ' ' && *ptr1 != ';' && *ptr1 != '\0') 
+           ptr1++;
+    }
+
+    /* ======================== */
+    /* look for tile dimensions */
+    /* ======================== */
+
+    while (*ptr1 == ' ')    /* ignore leading blanks */
+           ptr1++;
+
+    ii = 0;
+    while (isdigit( (int) *ptr1) && ii < 9)
+    {
+       tilesize[ii] = atol(ptr1);  /* read the integer value */
+       ii++;
+
+       while (isdigit((int) *ptr1))    /* skip over the integer */
+           ptr1++;
+
+       if (*ptr1 == ',')
+           ptr1++;   /* skip over the comma */
+          
+       while (*ptr1 == ' ')    /* ignore leading blanks */
+           ptr1++;
+    }
+
+    /* ========================================================= */
+    /* look for semi-colon, followed by other optional parameters */
+    /* ========================================================= */
+
+    if (*ptr1 == ';') {
+        ptr1++;
+        while (*ptr1 == ' ')    /* ignore leading blanks */
+           ptr1++;
+
+          while (*ptr1 != 0) {  /* haven't reached end of string yet */
+
+              if (*ptr1 == 's' || *ptr1 == 'S') {
+                  /* this should be the HCOMPRESS "scale" parameter; default = 1 */
+	   
+                  ptr1++;
+                  while (*ptr1 == ' ')    /* ignore leading blanks */
+                      ptr1++;
+
+                  scale = (float) strtod(ptr1, &ptr1);
+
+                  while (*ptr1 == ' ' || *ptr1 == ',') /* skip over blanks or comma */
+                     ptr1++;
+
+            } else if (*ptr1 == 'q' || *ptr1 == 'Q') {
+                /* this should be the floating point quantization parameter */
+
+                  ptr1++;
+                  while (*ptr1 == ' ')    /* ignore leading blanks */
+                      ptr1++;
+
+                  qlevel = (float) strtod(ptr1, &ptr1);
+
+                  while (*ptr1 == ' ' || *ptr1 == ',') /* skip over blanks or comma */
+                     ptr1++;
+
+            } else {
+                return(*status = URL_PARSE_ERROR);
+            }
+        }
+    }
+
+    /* ================================= */
+    /* finished parsing; save the values */
+    /* ================================= */
+
+    fits_set_compression_type(fptr, compresstype, status);
+    fits_set_tile_dim(fptr, MAX_COMPRESS_DIM, tilesize, status);
+    
+    if (compresstype == HCOMPRESS_1) {
+        fits_set_hcomp_scale (fptr, scale,  status);
+        fits_set_hcomp_smooth(fptr, smooth, status);
+    }
+
+    if (qlevel != 0.0)
+        fits_set_quantize_level(fptr, qlevel, status);
+    
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffdkinit(fitsfile **fptr,      /* O - FITS file pointer                   */
+           const char *name,     /* I - name of file to create              */
+           int *status)          /* IO - error status                       */
+/*
+  Create and initialize a new FITS file on disk.  This routine differs
+  from ffinit in that the input 'name' is literally taken as the name
+  of the disk file to be created, and it does not support CFITSIO's 
+  extended filename syntax.
+*/
+{
+    if (*status > 0)
+        return(*status);
+
+    *status = CREATE_DISK_FILE;
+
+    ffinit(fptr, name,status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffinit(fitsfile **fptr,      /* O - FITS file pointer                   */
+           const char *name,     /* I - name of file to create              */
+           int *status)          /* IO - error status                       */
+/*
+  Create and initialize a new FITS file.
+*/
+{
+    int ii, driver, slen, clobber = 0;
+    char *url;
+    char urltype[MAX_PREFIX_LEN], outfile[FLEN_FILENAME];
+    char tmplfile[FLEN_FILENAME], compspec[80];
+    int handle, create_disk_file = 0;
+
+    if (*status > 0)
+        return(*status);
+
+    if (*status == CREATE_DISK_FILE)
+    {
+       create_disk_file = 1;
+       *status = 0;
+    }
+
+    *fptr = 0;              /* initialize null file pointer */
+
+    if (need_to_initialize)  {          /* this is called only once */
+        *status = fits_init_cfitsio();
+    }
+
+    if (*status > 0)
+        return(*status);
+
+    url = (char *) name;
+    while (*url == ' ')  /* ignore leading spaces in the filename */
+        url++;
+
+    if (*url == '\0')
+    {
+        ffpmsg("Name of file to create is blank. (ffinit)");
+        return(*status = FILE_NOT_CREATED);
+    }
+
+    if (create_disk_file)
+    {
+       if (strlen(url) > FLEN_FILENAME - 1)
+       {
+           ffpmsg("Filename is too long. (ffinit)");
+           return(*status = FILE_NOT_CREATED);
+       }
+
+       strcpy(outfile, url);
+       strcpy(urltype, "file://");
+       tmplfile[0] = '\0';
+       compspec[0] = '\0';
+    }
+    else
+    {
+       
+      /* check for clobber symbol, i.e,  overwrite existing file */
+      if (*url == '!')
+      {
+          clobber = TRUE;
+          url++;
+      }
+      else
+          clobber = FALSE;
+
+        /* parse the output file specification */
+	/* this routine checks that the strings will not overflow */
+      ffourl(url, urltype, outfile, tmplfile, compspec, status);
+
+      if (*status > 0)
+      {
+        ffpmsg("could not parse the output filename: (ffinit)");
+        ffpmsg(url);
+        return(*status);
+      }
+    }
+    
+        /* find which driver corresponds to the urltype */
+    *status = urltype2driver(urltype, &driver);
+
+    if (*status)
+    {
+        ffpmsg("could not find driver for this file: (ffinit)");
+        ffpmsg(url);
+        return(*status);
+    }
+
+        /* delete pre-existing file, if asked to do so */
+    if (clobber)
+    {
+        if (driverTable[driver].remove)
+             (*driverTable[driver].remove)(outfile);
+    }
+
+        /* call appropriate driver to create the file */
+    if (driverTable[driver].create)
+    {
+        FFLOCK;  /* lock this while searching for vacant handle */
+        *status = (*driverTable[driver].create)(outfile, &handle);
+        FFUNLOCK;
+        if (*status)
+        {
+            ffpmsg("failed to create new file (already exists?):");
+            ffpmsg(url);
+            return(*status);
+       }
+    }
+    else
+    {
+        ffpmsg("cannot create a new file of this type: (ffinit)");
+        ffpmsg(url);
+        return(*status = FILE_NOT_CREATED);
+    }
+
+        /* allocate fitsfile structure and initialize = 0 */
+    *fptr = (fitsfile *) calloc(1, sizeof(fitsfile));
+
+    if (!(*fptr))
+    {
+        (*driverTable[driver].close)(handle);  /* close the file */
+        ffpmsg("failed to allocate structure for following file: (ffopen)");
+        ffpmsg(url);
+        return(*status = MEMORY_ALLOCATION);
+    }
+
+        /* allocate FITSfile structure and initialize = 0 */
+    (*fptr)->Fptr = (FITSfile *) calloc(1, sizeof(FITSfile));
+
+    if (!((*fptr)->Fptr))
+    {
+        (*driverTable[driver].close)(handle);  /* close the file */
+        ffpmsg("failed to allocate structure for following file: (ffopen)");
+        ffpmsg(url);
+        free(*fptr);
+        *fptr = 0;       
+        return(*status = MEMORY_ALLOCATION);
+    }
+
+    slen = strlen(url) + 1;
+    slen = maxvalue(slen, 32); /* reserve at least 32 chars */ 
+    ((*fptr)->Fptr)->filename = (char *) malloc(slen); /* mem for file name */
+
+    if ( !(((*fptr)->Fptr)->filename) )
+    {
+        (*driverTable[driver].close)(handle);  /* close the file */
+        ffpmsg("failed to allocate memory for filename: (ffinit)");
+        ffpmsg(url);
+        free((*fptr)->Fptr);
+        free(*fptr);
+        *fptr = 0;              /* return null file pointer */
+        return(*status = FILE_NOT_CREATED);
+    }
+
+    /* mem for headstart array */
+    ((*fptr)->Fptr)->headstart = (LONGLONG *) calloc(1001, sizeof(LONGLONG)); 
+
+    if ( !(((*fptr)->Fptr)->headstart) )
+    {
+        (*driverTable[driver].close)(handle);  /* close the file */
+        ffpmsg("failed to allocate memory for headstart array: (ffinit)");
+        ffpmsg(url);
+        free( ((*fptr)->Fptr)->filename);
+        free((*fptr)->Fptr);
+        free(*fptr);
+        *fptr = 0;              /* return null file pointer */
+        return(*status = MEMORY_ALLOCATION);
+    }
+
+    /* mem for file I/O buffers */
+    ((*fptr)->Fptr)->iobuffer = (char *) calloc(NIOBUF, IOBUFLEN);
+
+    if ( !(((*fptr)->Fptr)->iobuffer) )
+    {
+        (*driverTable[driver].close)(handle);  /* close the file */
+        ffpmsg("failed to allocate memory for iobuffer array: (ffinit)");
+        ffpmsg(url);
+        free( ((*fptr)->Fptr)->headstart);    /* free memory for headstart array */
+        free( ((*fptr)->Fptr)->filename);
+        free((*fptr)->Fptr);
+        free(*fptr);
+        *fptr = 0;              /* return null file pointer */
+        return(*status = MEMORY_ALLOCATION);
+    }
+
+    /* initialize the ageindex array (relative age of the I/O buffers) */
+    /* and initialize the bufrecnum array as being empty */
+    for (ii = 0; ii < NIOBUF; ii++)  {
+        ((*fptr)->Fptr)->ageindex[ii] = ii;
+        ((*fptr)->Fptr)->bufrecnum[ii] = -1;
+    }
+
+        /* store the parameters describing the file */
+    ((*fptr)->Fptr)->MAXHDU = 1000;              /* initial size of headstart */
+    ((*fptr)->Fptr)->filehandle = handle;        /* store the file pointer */
+    ((*fptr)->Fptr)->driver = driver;            /*  driver number         */
+    strcpy(((*fptr)->Fptr)->filename, url);      /* full input filename    */
+    ((*fptr)->Fptr)->filesize = 0;               /* physical file size     */
+    ((*fptr)->Fptr)->logfilesize = 0;            /* logical file size      */
+    ((*fptr)->Fptr)->writemode = 1;              /* read-write mode        */
+    ((*fptr)->Fptr)->datastart = DATA_UNDEFINED; /* unknown start of data  */
+    ((*fptr)->Fptr)->curbuf = -1;         /* undefined current IO buffer   */
+    ((*fptr)->Fptr)->open_count = 1;      /* structure is currently used once */
+    ((*fptr)->Fptr)->validcode = VALIDSTRUC; /* flag denoting valid structure */
+
+    ffldrc(*fptr, 0, IGNORE_EOF, status);     /* initialize first record */
+
+    fits_store_Fptr( (*fptr)->Fptr, status);  /* store Fptr address */
+
+    /* if template file was given, use it to define structure of new file */
+
+    if (tmplfile[0])
+        ffoptplt(*fptr, tmplfile, status);
+
+    /* parse and save image compression specification, if given */
+    if (compspec[0])
+        ffparsecompspec(*fptr, compspec, status);
+
+    return(*status);                       /* successful return */
+}
+/*--------------------------------------------------------------------------*/
+/* ffimem == fits_create_memfile */
+
+int ffimem(fitsfile **fptr,      /* O - FITS file pointer                   */ 
+           void **buffptr,       /* I - address of memory pointer           */
+           size_t *buffsize,     /* I - size of buffer, in bytes            */
+           size_t deltasize,     /* I - increment for future realloc's      */
+           void *(*mem_realloc)(void *p, size_t newsize), /* function       */
+           int *status)          /* IO - error status                       */
+
+/*
+  Create and initialize a new FITS file in memory
+*/
+{
+    int ii, driver, slen;
+    char urltype[MAX_PREFIX_LEN];
+    int handle;
+
+    if (*status > 0)
+        return(*status);
+
+    *fptr = 0;              /* initialize null file pointer */
+
+    if (need_to_initialize)    {        /* this is called only once */
+       *status = fits_init_cfitsio();
+    }
+    
+    if (*status > 0)
+        return(*status);
+
+    strcpy(urltype, "memkeep://"); /* URL type for pre-existing memory file */
+
+    *status = urltype2driver(urltype, &driver);
+
+    if (*status > 0)
+    {
+        ffpmsg("could not find driver for pre-existing memory file: (ffimem)");
+        return(*status);
+    }
+
+    /* call driver routine to "open" the memory file */
+    FFLOCK;  /* lock this while searching for vacant handle */
+    *status =   mem_openmem( buffptr, buffsize, deltasize,
+                            mem_realloc,  &handle);
+    FFUNLOCK;
+
+    if (*status > 0)
+    {
+         ffpmsg("failed to open pre-existing memory file: (ffimem)");
+         return(*status);
+    }
+
+        /* allocate fitsfile structure and initialize = 0 */
+    *fptr = (fitsfile *) calloc(1, sizeof(fitsfile));
+
+    if (!(*fptr))
+    {
+        (*driverTable[driver].close)(handle);  /* close the file */
+        ffpmsg("failed to allocate structure for memory file: (ffimem)");
+        return(*status = MEMORY_ALLOCATION);
+    }
+
+        /* allocate FITSfile structure and initialize = 0 */
+    (*fptr)->Fptr = (FITSfile *) calloc(1, sizeof(FITSfile));
+
+    if (!((*fptr)->Fptr))
+    {
+        (*driverTable[driver].close)(handle);  /* close the file */
+        ffpmsg("failed to allocate structure for memory file: (ffimem)");
+        free(*fptr);
+        *fptr = 0;       
+        return(*status = MEMORY_ALLOCATION);
+    }
+
+    slen = 32; /* reserve at least 32 chars */ 
+    ((*fptr)->Fptr)->filename = (char *) malloc(slen); /* mem for file name */
+
+    if ( !(((*fptr)->Fptr)->filename) )
+    {
+        (*driverTable[driver].close)(handle);  /* close the file */
+        ffpmsg("failed to allocate memory for filename: (ffimem)");
+        free((*fptr)->Fptr);
+        free(*fptr);
+        *fptr = 0;              /* return null file pointer */
+        return(*status = MEMORY_ALLOCATION);
+    }
+
+    /* mem for headstart array */
+    ((*fptr)->Fptr)->headstart = (LONGLONG *) calloc(1001, sizeof(LONGLONG)); 
+
+    if ( !(((*fptr)->Fptr)->headstart) )
+    {
+        (*driverTable[driver].close)(handle);  /* close the file */
+        ffpmsg("failed to allocate memory for headstart array: (ffimem)");
+        free( ((*fptr)->Fptr)->filename);
+        free((*fptr)->Fptr);
+        free(*fptr);
+        *fptr = 0;              /* return null file pointer */
+        return(*status = MEMORY_ALLOCATION);
+    }
+
+    /* mem for file I/O buffers */
+    ((*fptr)->Fptr)->iobuffer = (char *) calloc(NIOBUF, IOBUFLEN);
+
+    if ( !(((*fptr)->Fptr)->iobuffer) )
+    {
+        (*driverTable[driver].close)(handle);  /* close the file */
+        ffpmsg("failed to allocate memory for iobuffer array: (ffimem)");
+        free( ((*fptr)->Fptr)->headstart);    /* free memory for headstart array */
+        free( ((*fptr)->Fptr)->filename);
+        free((*fptr)->Fptr);
+        free(*fptr);
+        *fptr = 0;              /* return null file pointer */
+        return(*status = MEMORY_ALLOCATION);
+    }
+
+    /* initialize the ageindex array (relative age of the I/O buffers) */
+    /* and initialize the bufrecnum array as being empty */
+    for (ii = 0; ii < NIOBUF; ii++)  {
+        ((*fptr)->Fptr)->ageindex[ii] = ii;
+        ((*fptr)->Fptr)->bufrecnum[ii] = -1;
+    }
+
+        /* store the parameters describing the file */
+    ((*fptr)->Fptr)->MAXHDU = 1000;              /* initial size of headstart */
+    ((*fptr)->Fptr)->filehandle = handle;        /* file handle */
+    ((*fptr)->Fptr)->driver = driver;            /* driver number */
+    strcpy(((*fptr)->Fptr)->filename, "memfile"); /* dummy filename */
+    ((*fptr)->Fptr)->filesize = *buffsize;        /* physical file size */
+    ((*fptr)->Fptr)->logfilesize = *buffsize;     /* logical file size */
+    ((*fptr)->Fptr)->writemode = 1;               /* read-write mode    */
+    ((*fptr)->Fptr)->datastart = DATA_UNDEFINED;  /* unknown start of data */
+    ((*fptr)->Fptr)->curbuf = -1;             /* undefined current IO buffer */
+    ((*fptr)->Fptr)->open_count = 1;     /* structure is currently used once */
+    ((*fptr)->Fptr)->validcode = VALIDSTRUC; /* flag denoting valid structure */
+
+    ffldrc(*fptr, 0, IGNORE_EOF, status);     /* initialize first record */
+    fits_store_Fptr( (*fptr)->Fptr, status);  /* store Fptr address */
+    return(*status); 
+}
+/*--------------------------------------------------------------------------*/
+int fits_init_cfitsio(void)
+/*
+  initialize anything that is required before using the CFITSIO routines
+*/
+{
+    int status;
+
+    union u_tag {
+      short ival;
+      char cval[2];
+    } u;
+
+    fitsio_init_lock();
+
+    FFLOCK;   /* lockout other threads while executing this critical */
+              /* section of code  */
+
+    if (need_to_initialize == 0) { /* already initialized? */
+      FFUNLOCK;
+      return(0);
+    }
+
+    /*   test for correct byteswapping.   */
+
+    u.ival = 1;
+    if  ((BYTESWAPPED && u.cval[0] != 1) ||
+         (BYTESWAPPED == FALSE && u.cval[1] != 1) )
+    {
+      printf ("\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n");
+      printf(" Byteswapping is not being done correctly on this system.\n");
+      printf(" Check the MACHINE and BYTESWAPPED definitions in fitsio2.h\n");
+      printf(" Please report this problem to the CFITSIO developers.\n");
+      printf(  "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n");
+      FFUNLOCK;
+      return(1);
+    }
+    
+    
+    /*  test that LONGLONG is an 8 byte integer */
+    
+    if (sizeof(LONGLONG) != 8)
+    {
+      printf ("\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n");
+      printf(" CFITSIO did not find an 8-byte long integer data type.\n");
+      printf("   sizeof(LONGLONG) = %d\n",(int)sizeof(LONGLONG));
+      printf(" Please report this problem to the CFITSIO developers.\n");
+      printf(  "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n");
+      FFUNLOCK;
+      return(1);
+    }
+
+    /* register the standard I/O drivers that are always available */
+
+    /* 1--------------------disk file driver-----------------------*/
+    status = fits_register_driver("file://", 
+            file_init,
+            file_shutdown,
+            file_setoptions,
+            file_getoptions, 
+            file_getversion,
+	    file_checkfile,
+            file_open,
+            file_create,
+#ifdef HAVE_FTRUNCATE
+            file_truncate,
+#else
+            NULL,   /* no file truncate function */
+#endif
+            file_close,
+            file_remove,
+            file_size,
+            file_flush,
+            file_seek,
+            file_read,
+            file_write);
+
+    if (status)
+    {
+        ffpmsg("failed to register the file:// driver (init_cfitsio)");
+        FFUNLOCK;
+        return(status);
+    }
+
+    /* 2------------ output temporary memory file driver ----------------*/
+    status = fits_register_driver("mem://", 
+            mem_init,
+            mem_shutdown,
+            mem_setoptions,
+            mem_getoptions, 
+            mem_getversion,
+            NULL,            /* checkfile not needed */
+            NULL,            /* open function not allowed */
+            mem_create, 
+            mem_truncate,
+            mem_close_free,
+            NULL,            /* remove function not required */
+            mem_size,
+            NULL,            /* flush function not required */
+            mem_seek,
+            mem_read,
+            mem_write);
+
+
+    if (status)
+    {
+        ffpmsg("failed to register the mem:// driver (init_cfitsio)");
+        FFUNLOCK;
+        return(status);
+    }
+
+    /* 3--------------input pre-existing memory file driver----------------*/
+    status = fits_register_driver("memkeep://", 
+            NULL,
+            mem_shutdown,
+            mem_setoptions,
+            mem_getoptions, 
+            mem_getversion,
+            NULL,            /* checkfile not needed */
+            NULL,            /* file open driver function is not used */
+            NULL,            /* create function not allowed */
+            mem_truncate,
+            mem_close_keep,
+            NULL,            /* remove function not required */
+            mem_size,
+            NULL,            /* flush function not required */
+            mem_seek,
+            mem_read,
+            mem_write);
+
+
+    if (status)
+    {
+        ffpmsg("failed to register the memkeep:// driver (init_cfitsio)");
+        FFUNLOCK;
+        return(status);
+    }
+
+   /* 4-------------------stdin stream driver----------------------*/
+   /*  the stdin stream is copied to memory then opened in memory */
+
+    status = fits_register_driver("stdin://", 
+            NULL,
+            mem_shutdown,
+            mem_setoptions,
+            mem_getoptions, 
+            mem_getversion,
+            stdin_checkfile, 
+            stdin_open,
+            NULL,            /* create function not allowed */
+            mem_truncate,
+            mem_close_free,
+            NULL,            /* remove function not required */
+            mem_size,
+            NULL,            /* flush function not required */
+            mem_seek,
+            mem_read,
+            mem_write);
+
+    if (status)
+    {
+        ffpmsg("failed to register the stdin:// driver (init_cfitsio)");
+        FFUNLOCK;
+        return(status);
+    }
+
+   /* 5-------------------stdin file stream driver----------------------*/
+   /*  the stdin stream is copied to a disk file then the disk file is opened */
+
+    status = fits_register_driver("stdinfile://", 
+            NULL,
+            mem_shutdown,
+            mem_setoptions,
+            mem_getoptions, 
+            mem_getversion,
+            NULL,            /* checkfile not needed */ 
+            stdin_open,
+            NULL,            /* create function not allowed */
+#ifdef HAVE_FTRUNCATE
+            file_truncate,
+#else
+            NULL,   /* no file truncate function */
+#endif
+            file_close,
+            file_remove,
+            file_size,
+            file_flush,
+            file_seek,
+            file_read,
+            file_write);
+
+    if (status)
+    {
+        ffpmsg("failed to register the stdinfile:// driver (init_cfitsio)");
+        FFUNLOCK;
+        return(status);
+    }
+
+
+    /* 6-----------------------stdout stream driver------------------*/
+    status = fits_register_driver("stdout://",
+            NULL,
+            mem_shutdown,
+            mem_setoptions,
+            mem_getoptions, 
+            mem_getversion,
+            NULL,            /* checkfile not needed */ 
+            NULL,            /* open function not required */
+            mem_create, 
+            mem_truncate,
+            stdout_close,
+            NULL,            /* remove function not required */
+            mem_size,
+            NULL,            /* flush function not required */
+            mem_seek,
+            mem_read,
+            mem_write);
+
+    if (status)
+    {
+        ffpmsg("failed to register the stdout:// driver (init_cfitsio)");
+        FFUNLOCK;
+        return(status);
+    }
+
+    /* 7------------------iraf disk file to memory driver -----------*/
+    status = fits_register_driver("irafmem://",
+            NULL,
+            mem_shutdown,
+            mem_setoptions,
+            mem_getoptions, 
+            mem_getversion,
+            NULL,            /* checkfile not needed */ 
+            mem_iraf_open,
+            NULL,            /* create function not required */
+            mem_truncate,
+            mem_close_free,
+            NULL,            /* remove function not required */
+            mem_size,
+            NULL,            /* flush function not required */
+            mem_seek,
+            mem_read,
+            mem_write);
+
+    if (status)
+    {
+        ffpmsg("failed to register the irafmem:// driver (init_cfitsio)");
+        FFUNLOCK;
+        return(status);
+    }
+
+    /* 8------------------raw binary file to memory driver -----------*/
+    status = fits_register_driver("rawfile://",
+            NULL,
+            mem_shutdown,
+            mem_setoptions,
+            mem_getoptions, 
+            mem_getversion,
+            NULL,            /* checkfile not needed */ 
+            mem_rawfile_open,
+            NULL,            /* create function not required */
+            mem_truncate,
+            mem_close_free,
+            NULL,            /* remove function not required */
+            mem_size,
+            NULL,            /* flush function not required */
+            mem_seek,
+            mem_read,
+            mem_write);
+
+    if (status)
+    {
+        ffpmsg("failed to register the rawfile:// driver (init_cfitsio)");
+        FFUNLOCK;
+        return(status);
+    }
+
+    /* 9------------------compressed disk file to memory driver -----------*/
+    status = fits_register_driver("compress://",
+            NULL,
+            mem_shutdown,
+            mem_setoptions,
+            mem_getoptions, 
+            mem_getversion,
+            NULL,            /* checkfile not needed */ 
+            mem_compress_open,
+            NULL,            /* create function not required */
+            mem_truncate,
+            mem_close_free,
+            NULL,            /* remove function not required */
+            mem_size,
+            NULL,            /* flush function not required */
+            mem_seek,
+            mem_read,
+            mem_write);
+
+    if (status)
+    {
+        ffpmsg("failed to register the compress:// driver (init_cfitsio)");
+        FFUNLOCK;
+        return(status);
+    }
+
+    /* 10------------------compressed disk file to memory driver -----------*/
+    /*  Identical to compress://, except it allows READWRITE access      */
+
+    status = fits_register_driver("compressmem://",
+            NULL,
+            mem_shutdown,
+            mem_setoptions,
+            mem_getoptions, 
+            mem_getversion,
+            NULL,            /* checkfile not needed */ 
+            mem_compress_openrw,
+            NULL,            /* create function not required */
+            mem_truncate,
+            mem_close_free,
+            NULL,            /* remove function not required */
+            mem_size,
+            NULL,            /* flush function not required */
+            mem_seek,
+            mem_read,
+            mem_write);
+
+    if (status)
+    {
+        ffpmsg("failed to register the compressmem:// driver (init_cfitsio)");
+        FFUNLOCK;
+        return(status);
+    }
+
+    /* 11------------------compressed disk file to disk file driver -------*/
+    status = fits_register_driver("compressfile://",
+            NULL,
+            file_shutdown,
+            file_setoptions,
+            file_getoptions, 
+            file_getversion,
+            NULL,            /* checkfile not needed */ 
+            file_compress_open,
+            file_create,
+#ifdef HAVE_FTRUNCATE
+            file_truncate,
+#else
+            NULL,   /* no file truncate function */
+#endif
+            file_close,
+            file_remove,
+            file_size,
+            file_flush,
+            file_seek,
+            file_read,
+            file_write);
+
+    if (status)
+    {
+        ffpmsg("failed to register the compressfile:// driver (init_cfitsio)");
+        FFUNLOCK;
+        return(status);
+    }
+
+    /* 12---create file in memory, then compress it to disk file on close--*/
+    status = fits_register_driver("compressoutfile://", 
+            NULL,
+            mem_shutdown,
+            mem_setoptions,
+            mem_getoptions, 
+            mem_getversion,
+            NULL,            /* checkfile not needed */
+            NULL,            /* open function not allowed */
+            mem_create_comp, 
+            mem_truncate,
+            mem_close_comp,
+            file_remove,     /* delete existing compressed disk file */
+            mem_size,
+            NULL,            /* flush function not required */
+            mem_seek,
+            mem_read,
+            mem_write);
+
+
+    if (status)
+    {
+        ffpmsg(
+        "failed to register the compressoutfile:// driver (init_cfitsio)");
+        FFUNLOCK;
+        return(status);
+    }
+
+    /* Register Optional drivers */
+
+#ifdef HAVE_NET_SERVICES
+
+    /* 13--------------------root driver-----------------------*/
+
+    status = fits_register_driver("root://",
+				  root_init,
+				  root_shutdown,
+				  root_setoptions,
+				  root_getoptions, 
+				  root_getversion,
+				  NULL,            /* checkfile not needed */ 
+				  root_open,
+				  root_create,
+				  NULL,  /* No truncate possible */
+				  root_close,
+				  NULL,  /* No remove possible */
+				  root_size,  /* no size possible */
+				  root_flush,
+				  root_seek, /* Though will always succeed */
+				  root_read,
+				  root_write);
+
+    if (status)
+    {
+        ffpmsg("failed to register the root:// driver (init_cfitsio)");
+        FFUNLOCK;
+        return(status);
+    }
+
+    /* 14--------------------http  driver-----------------------*/
+    status = fits_register_driver("http://";,
+            NULL,
+            mem_shutdown,
+            mem_setoptions,
+            mem_getoptions, 
+            mem_getversion,
+            http_checkfile,
+            http_open,
+            NULL,            /* create function not required */
+            mem_truncate,
+            mem_close_free,
+            NULL,            /* remove function not required */
+            mem_size,
+            NULL,            /* flush function not required */
+            mem_seek,
+            mem_read,
+            mem_write);
+
+    if (status)
+    {
+        ffpmsg("failed to register the http:// driver (init_cfitsio)");
+        FFUNLOCK;
+        return(status);
+    }
+
+    /* 15--------------------http file driver-----------------------*/
+
+    status = fits_register_driver("httpfile://",
+            NULL,
+            file_shutdown,
+            file_setoptions,
+            file_getoptions, 
+            file_getversion,
+            NULL,            /* checkfile not needed */ 
+            http_file_open,
+            file_create,
+#ifdef HAVE_FTRUNCATE
+            file_truncate,
+#else
+            NULL,   /* no file truncate function */
+#endif
+            file_close,
+            file_remove,
+            file_size,
+            file_flush,
+            file_seek,
+            file_read,
+            file_write);
+
+    if (status)
+    {
+        ffpmsg("failed to register the httpfile:// driver (init_cfitsio)");
+        FFUNLOCK;
+        return(status);
+    }
+
+    /* 16--------------------http memory driver-----------------------*/
+    /*  same as http:// driver, except memory file can be opened READWRITE */
+    status = fits_register_driver("httpmem://",
+            NULL,
+            mem_shutdown,
+            mem_setoptions,
+            mem_getoptions, 
+            mem_getversion,
+            http_checkfile,
+            http_file_open,  /* this will simply call http_open */
+            NULL,            /* create function not required */
+            mem_truncate,
+            mem_close_free,
+            NULL,            /* remove function not required */
+            mem_size,
+            NULL,            /* flush function not required */
+            mem_seek,
+            mem_read,
+            mem_write);
+
+    if (status)
+    {
+        ffpmsg("failed to register the httpmem:// driver (init_cfitsio)");
+        FFUNLOCK;
+        return(status);
+    }
+
+    /* 17--------------------httpcompress file driver-----------------------*/
+
+    status = fits_register_driver("httpcompress://",
+            NULL,
+            mem_shutdown,
+            mem_setoptions,
+            mem_getoptions, 
+            mem_getversion,
+            NULL,            /* checkfile not needed */ 
+            http_compress_open,
+            NULL,            /* create function not required */
+            mem_truncate,
+            mem_close_free,
+            NULL,            /* remove function not required */
+            mem_size,
+            NULL,            /* flush function not required */
+            mem_seek,
+            mem_read,
+            mem_write);
+
+    if (status)
+    {
+        ffpmsg("failed to register the httpcompress:// driver (init_cfitsio)");
+        FFUNLOCK;
+        return(status);
+    }
+
+
+    /* 18--------------------ftp driver-----------------------*/
+    status = fits_register_driver("ftp://";,
+            NULL,
+            mem_shutdown,
+            mem_setoptions,
+            mem_getoptions, 
+            mem_getversion,
+            ftp_checkfile,
+            ftp_open,
+            NULL,            /* create function not required */
+            mem_truncate,
+            mem_close_free,
+            NULL,            /* remove function not required */
+            mem_size,
+            NULL,            /* flush function not required */
+            mem_seek,
+            mem_read,
+            mem_write);
+
+    if (status)
+    {
+        ffpmsg("failed to register the ftp:// driver (init_cfitsio)");
+        FFUNLOCK;
+        return(status);
+    }
+
+    /* 19--------------------ftp file driver-----------------------*/
+    status = fits_register_driver("ftpfile://",
+            NULL,
+            file_shutdown,
+            file_setoptions,
+            file_getoptions, 
+            file_getversion,
+            NULL,            /* checkfile not needed */ 
+            ftp_file_open,
+            file_create,
+#ifdef HAVE_FTRUNCATE
+            file_truncate,
+#else
+            NULL,   /* no file truncate function */
+#endif
+            file_close,
+            file_remove,
+            file_size,
+            file_flush,
+            file_seek,
+            file_read,
+            file_write);
+
+    if (status)
+    {
+        ffpmsg("failed to register the ftpfile:// driver (init_cfitsio)");
+        FFUNLOCK;
+        return(status);
+    }
+
+    /* 20--------------------ftp mem driver-----------------------*/
+    /*  same as ftp:// driver, except memory file can be opened READWRITE */
+    status = fits_register_driver("ftpmem://",
+            NULL,
+            mem_shutdown,
+            mem_setoptions,
+            mem_getoptions, 
+            mem_getversion,
+            ftp_checkfile,
+            ftp_file_open,   /* this will simply call ftp_open */
+            NULL,            /* create function not required */
+            mem_truncate,
+            mem_close_free,
+            NULL,            /* remove function not required */
+            mem_size,
+            NULL,            /* flush function not required */
+            mem_seek,
+            mem_read,
+            mem_write);
+
+    if (status)
+    {
+        ffpmsg("failed to register the ftpmem:// driver (init_cfitsio)");
+        FFUNLOCK;
+        return(status);
+    }
+
+    /* 21--------------------ftp compressed file driver------------------*/
+    status = fits_register_driver("ftpcompress://",
+            NULL,
+            mem_shutdown,
+            mem_setoptions,
+            mem_getoptions, 
+            mem_getversion,
+            NULL,            /* checkfile not needed */ 
+            ftp_compress_open,
+            0,            /* create function not required */
+            mem_truncate,
+            mem_close_free,
+            0,            /* remove function not required */
+            mem_size,
+            0,            /* flush function not required */
+            mem_seek,
+            mem_read,
+            mem_write);
+
+    if (status)
+    {
+        ffpmsg("failed to register the ftpcompress:// driver (init_cfitsio)");
+        FFUNLOCK;
+        return(status);
+    }
+      /* === End of net drivers section === */  
+#endif
+
+/* ==================== SHARED MEMORY DRIVER SECTION ======================= */
+
+#ifdef HAVE_SHMEM_SERVICES
+
+    /* 22--------------------shared memory driver-----------------------*/
+    status = fits_register_driver("shmem://", 
+            smem_init,
+            smem_shutdown,
+            smem_setoptions,
+            smem_getoptions, 
+            smem_getversion,
+            NULL,            /* checkfile not needed */ 
+            smem_open,
+            smem_create,
+            NULL,            /* truncate file not supported yet */ 
+            smem_close,
+            smem_remove,
+            smem_size,
+            smem_flush,
+            smem_seek,
+            smem_read,
+            smem_write );
+
+    if (status)
+    {
+        ffpmsg("failed to register the shmem:// driver (init_cfitsio)");
+        FFUNLOCK;
+        return(status);
+    }
+
+#endif
+/* ==================== END OF SHARED MEMORY DRIVER SECTION ================ */
+
+
+#ifdef HAVE_GSIFTP
+    /* 23--------------------gsiftp driver-----------------------*/
+    status = fits_register_driver("gsiftp://";,
+            gsiftp_init,
+            gsiftp_shutdown,
+            gsiftp_setoptions,
+            gsiftp_getoptions, 
+            gsiftp_getversion,
+            gsiftp_checkfile,
+            gsiftp_open,
+            gsiftp_create,
+#ifdef HAVE_FTRUNCATE
+            gsiftp_truncate,
+#else
+            NULL,
+#endif
+            gsiftp_close,
+            NULL,            /* remove function not yet implemented */
+            gsiftp_size,
+            gsiftp_flush,
+            gsiftp_seek,
+            gsiftp_read,
+            gsiftp_write);
+
+    if (status)
+    {
+        ffpmsg("failed to register the gsiftp:// driver (init_cfitsio)");
+        FFUNLOCK;
+        return(status);
+    }
+
+#endif
+
+    /* 24---------------stdin and stdout stream driver-------------------*/
+    status = fits_register_driver("stream://", 
+            NULL,
+            NULL,
+            NULL,
+            NULL, 
+            NULL,
+	    NULL,
+            stream_open,
+            stream_create,
+            NULL,   /* no stream truncate function */
+            stream_close,
+            NULL,   /* no stream remove */
+            stream_size,
+            stream_flush,
+            stream_seek,
+            stream_read,
+            stream_write);
+
+    if (status)
+    {
+        ffpmsg("failed to register the stream:// driver (init_cfitsio)");
+        FFUNLOCK;
+        return(status);
+    }
+
+    /* reset flag.  Any other threads will now not need to call this routine */
+    need_to_initialize = 0;
+
+    FFUNLOCK;
+    return(status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_register_driver(char *prefix,
+	int (*init)(void),
+	int (*shutdown)(void),
+	int (*setoptions)(int option),
+	int (*getoptions)(int *options),
+	int (*getversion)(int *version),
+	int (*checkfile) (char *urltype, char *infile, char *outfile),
+	int (*open)(char *filename, int rwmode, int *driverhandle),
+	int (*create)(char *filename, int *driverhandle),
+	int (*truncate)(int driverhandle, LONGLONG filesize),
+	int (*close)(int driverhandle),
+	int (*fremove)(char *filename),
+        int (*size)(int driverhandle, LONGLONG *size),
+	int (*flush)(int driverhandle),
+	int (*seek)(int driverhandle, LONGLONG offset),
+	int (*read) (int driverhandle, void *buffer, long nbytes),
+	int (*write)(int driverhandle, void *buffer, long nbytes) )
+/*
+  register all the functions needed to support an I/O driver
+*/
+{
+    int status;
+ 
+    if (no_of_drivers < 0 ) {
+	  /* This is bad. looks like memory has been corrupted. */
+	  ffpmsg("Vital CFITSIO parameters held in memory have been corrupted!!");
+	  ffpmsg("Fatal condition detected in fits_register_driver.");
+	  return(TOO_MANY_DRIVERS);
+    }
+
+    if (no_of_drivers + 1 > MAX_DRIVERS)
+        return(TOO_MANY_DRIVERS);
+
+    if (prefix  == NULL)
+        return(BAD_URL_PREFIX);
+   
+
+    if (init != NULL)		
+    { 
+        status = (*init)();  /* initialize the driver */
+        if (status)
+            return(status);
+    }
+
+    	/*  fill in data in table */
+    strncpy(driverTable[no_of_drivers].prefix, prefix, MAX_PREFIX_LEN);
+    driverTable[no_of_drivers].prefix[MAX_PREFIX_LEN - 1] = 0;
+    driverTable[no_of_drivers].init = init;
+    driverTable[no_of_drivers].shutdown = shutdown;
+    driverTable[no_of_drivers].setoptions = setoptions;
+    driverTable[no_of_drivers].getoptions = getoptions;
+    driverTable[no_of_drivers].getversion = getversion;
+    driverTable[no_of_drivers].checkfile = checkfile;
+    driverTable[no_of_drivers].open = open;
+    driverTable[no_of_drivers].create = create;
+    driverTable[no_of_drivers].truncate = truncate;
+    driverTable[no_of_drivers].close = close;
+    driverTable[no_of_drivers].remove = fremove;
+    driverTable[no_of_drivers].size = size;
+    driverTable[no_of_drivers].flush = flush;
+    driverTable[no_of_drivers].seek = seek;
+    driverTable[no_of_drivers].read = read;
+    driverTable[no_of_drivers].write = write;
+
+    no_of_drivers++;      /* increment the number of drivers */
+    return(0);
+ }
+/*--------------------------------------------------------------------------*/
+/* fits_parse_input_url */
+int ffiurl(char *url,               /* input filename */
+           char *urltype,    /* e.g., 'file://', 'http://', 'mem://' */
+           char *infilex,    /* root filename (may be complete path) */
+           char *outfile,    /* optional output file name            */
+           char *extspec,    /* extension spec: +n or [extname, extver]  */
+           char *rowfilterx, /* boolean row filter expression */
+           char *binspec,    /* histogram binning specifier   */
+           char *colspec,    /* column or keyword modifier expression */
+           int *status)
+/*
+   parse the input URL into its basic components.
+   This routine is big and ugly and should be redesigned someday!
+*/
+{
+	return fits_parse_input_filename(url, urltype, infilex, outfile,
+               extspec, rowfilterx, binspec, colspec, 0, status);
+}
+
+/*--------------------------------------------------------------------------*/
+/* fits_parse_input_file */
+int ffifile(char *url,       /* input filename */
+           char *urltype,    /* e.g., 'file://', 'http://', 'mem://' */
+           char *infilex,    /* root filename (may be complete path) */
+           char *outfile,    /* optional output file name            */
+           char *extspec,    /* extension spec: +n or [extname, extver]  */
+           char *rowfilterx, /* boolean row filter expression */
+           char *binspec,    /* histogram binning specifier   */
+           char *colspec,    /* column or keyword modifier expression */
+           char *pixfilter,  /* pixel filter expression */
+           int *status)
+/*
+   fits_parse_input_filename
+   parse the input URL into its basic components.
+   This routine is big and ugly and should be redesigned someday!
+*/
+{ 
+    int ii, jj, slen, infilelen, plus_ext = 0, collen;
+    char *ptr1, *ptr2, *ptr3, *ptr4, *tmptr;
+    int hasAt, hasDot, hasOper, followingOper, spaceTerm, rowFilter;
+    int colStart, binStart, pixStart;
+
+
+    /* must have temporary variable for these, in case inputs are NULL */
+    char *infile;
+    char *rowfilter;
+    char *tmpstr;
+
+    if (*status > 0)
+        return(*status);
+
+    /* Initialize null strings */
+    if (infilex) *infilex  = '\0';
+    if (urltype) *urltype = '\0';
+    if (outfile) *outfile = '\0';
+    if (extspec) *extspec = '\0';
+    if (binspec) *binspec = '\0';
+    if (colspec) *colspec = '\0';
+    if (rowfilterx) *rowfilterx = '\0';
+    if (pixfilter) *pixfilter = '\0';
+ 
+    slen = strlen(url);
+
+    if (slen == 0)       /* blank filename ?? */
+        return(*status);
+
+    /* allocate memory for 3 strings, each as long as the input url */
+    infile = (char *) calloc(3,  slen + 1);
+    if (!infile)
+       return(*status = MEMORY_ALLOCATION);
+
+    rowfilter = &infile[slen + 1];
+    tmpstr = &rowfilter[slen + 1];
+
+    ptr1 = url;
+
+    /* -------------------------------------------------------- */
+    /*  get urltype (e.g., file://, ftp://, http://, etc.)  */
+    /* --------------------------------------------------------- */
+
+    if (*ptr1 == '-' && ( *(ptr1 +1) ==  0   || *(ptr1 +1) == ' '  || 
+                          *(ptr1 +1) == '['  || *(ptr1 +1) == '(' ) )
+    {
+        /* "-" means read file from stdin. Also support "- ",        */
+        /* "-[extname]" and '-(outfile.fits)" but exclude disk file  */
+        /* names that begin with a minus sign, e.g., "-55d33m.fits"  */
+
+        if (urltype)
+            strcat(urltype, "stdin://");
+        ptr1++;
+    }
+    else if (!strncasecmp(ptr1, "stdin", 5))
+    {
+        if (urltype)
+            strcat(urltype, "stdin://");
+        ptr1 = ptr1 + 5;
+    }
+    else
+    {
+        ptr2 = strstr(ptr1, "://");
+        ptr3 = strstr(ptr1, "(" );
+
+        if (ptr3 && (ptr3 < ptr2) )
+        {
+           /* the urltype follows a '(' character, so it must apply */
+           /* to the output file, and is not the urltype of the input file */
+           ptr2 = 0;   /* so reset pointer to zero */
+        }
+
+        if (ptr2)            /* copy the explicit urltype string */ 
+        {
+            if (urltype)
+                 strncat(urltype, ptr1, ptr2 - ptr1 + 3);
+            ptr1 = ptr2 + 3;
+        }
+        else if (!strncmp(ptr1, "ftp:", 4) )
+        {                              /* the 2 //'s are optional */
+            if (urltype)
+                strcat(urltype, "ftp://";);
+            ptr1 += 4;
+        }
+        else if (!strncmp(ptr1, "gsiftp:", 7) )
+        {                              /* the 2 //'s are optional */
+            if (urltype)
+                strcat(urltype, "gsiftp://";);
+            ptr1 += 7;
+        }
+        else if (!strncmp(ptr1, "http:", 5) )
+        {                              /* the 2 //'s are optional */
+            if (urltype)
+                strcat(urltype, "http://";);
+            ptr1 += 5;
+        }
+        else if (!strncmp(ptr1, "mem:", 4) )
+        {                              /* the 2 //'s are optional */
+            if (urltype)
+                strcat(urltype, "mem://");
+            ptr1 += 4;
+        }
+        else if (!strncmp(ptr1, "shmem:", 6) )
+        {                              /* the 2 //'s are optional */
+            if (urltype)
+                strcat(urltype, "shmem://");
+            ptr1 += 6;
+        }
+        else if (!strncmp(ptr1, "file:", 5) )
+        {                              /* the 2 //'s are optional */
+            if (urltype)
+                strcat(urltype, "file://");
+            ptr1 += 5;
+        }
+        else                       /* assume file driver    */
+        {
+            if (urltype)
+                strcat(urltype, "file://");
+        }
+    }
+
+    /* ----------------------------------------------------------    
+       If this is a http:// type file, then the cgi file name could
+       include the '[' character, which should not be interpreted
+       as part of CFITSIO's Extended File Name Syntax.  Test for this
+       case by seeing if the last character is a ']' or ')'.  If it 
+       is not, then just treat the whole input string as the file name
+       and do not attempt to interprete the name using the extended
+       filename syntax.
+     ----------------------------------------------------------- */
+
+    if (urltype && !strncmp(urltype, "http://";, 7) )
+    {
+        /* test for opening parenthesis or bracket in the file name */
+        if( strchr(ptr1, '(' ) || strchr(ptr1, '[' ) )
+        {
+            slen = strlen(ptr1);
+            ptr3 = ptr1 + slen - 1;
+            while (*ptr3 == ' ')    /* ignore trailing blanks */
+                ptr3--;
+
+            if (*ptr3 != ']' && *ptr3 != ')' )
+            {
+                /* name doesn't end with a ']' or ')' so don't try */
+                /* to parse this unusual string (may be cgi string)  */
+                if (infilex) {
+
+                    if (strlen(ptr1) > FLEN_FILENAME - 1) {
+                        ffpmsg("Name of file is too long.");
+                        return(*status = URL_PARSE_ERROR);
+                    }
+		    
+                    strcpy(infilex, ptr1);
+                }
+
+                free(infile);
+                return(*status);
+            }
+        }
+    }
+
+    /* ----------------------------------------------------------    
+       Look for VMS style filenames like: 
+            disk:[directory.subdirectory]filename.ext, or
+                 [directory.subdirectory]filename.ext
+
+       Check if the first character is a '[' and urltype != stdin
+       or if there is a ':[' string in the remaining url string. If
+       so, then need to move past this bracket character before
+       search for the opening bracket of a filter specification.
+     ----------------------------------------------------------- */
+
+    tmptr = ptr1;
+    if (*ptr1 == '[')
+    {
+      if (*url != '-') 
+        tmptr = ptr1 + 1; /* this bracket encloses a VMS directory name */
+    }
+    else
+    {
+       tmptr = strstr(ptr1, ":[");
+       if (tmptr) /* these 2 chars are part of the VMS disk and directory */
+          tmptr += 2; 
+       else
+          tmptr = ptr1;
+    }
+
+    /* ------------------------ */
+    /*  get the input file name */
+    /* ------------------------ */
+
+    ptr2 = strchr(tmptr, '(');   /* search for opening parenthesis ( */
+    ptr3 = strchr(tmptr, '[');   /* search for opening bracket [ */
+
+    if (ptr2 == ptr3)  /* simple case: no [ or ( in the file name */
+    {
+        strcat(infile, ptr1);
+    }
+    else if (!ptr3 ||         /* no bracket, so () enclose output file name */
+         (ptr2 && (ptr2 < ptr3)) ) /* () enclose output name before bracket */
+    {
+        strncat(infile, ptr1, ptr2 - ptr1);
+        ptr2++;
+
+        ptr1 = strchr(ptr2, ')' );   /* search for closing ) */
+        if (!ptr1)
+        {
+            free(infile);
+            return(*status = URL_PARSE_ERROR);  /* error, no closing ) */
+        }
+
+        if (outfile) {
+	
+	    if (ptr1 - ptr2 > FLEN_FILENAME - 1)
+	    {
+                 free(infile);
+                 return(*status = URL_PARSE_ERROR);
+            }
+
+            strncat(outfile, ptr2, ptr1 - ptr2);
+        }
+	
+        /* the opening [ could have been part of output name,    */
+        /*      e.g., file(out[compress])[3][#row > 5]           */
+        /* so search again for opening bracket following the closing ) */
+        ptr3 = strchr(ptr1, '[');
+
+    }
+    else    /*   bracket comes first, so there is no output name */
+    {
+        strncat(infile, ptr1, ptr3 - ptr1);
+    }
+
+   /* strip off any trailing blanks in the names */
+
+    slen = strlen(infile);
+    while ( (--slen) > 0  && infile[slen] == ' ') 
+         infile[slen] = '\0';
+
+    if (outfile)
+    {
+        slen = strlen(outfile);
+        while ( (--slen) > 0  && outfile[slen] == ' ') 
+            outfile[slen] = '\0';
+    }
+
+    /* --------------------------------------------- */
+    /* check if this is an IRAF file (.imh extension */
+    /* --------------------------------------------- */
+
+    ptr4 = strstr(infile, ".imh");
+
+    /* did the infile name end with ".imh" ? */
+    if (ptr4 && (*(ptr4 + 4) == '\0'))
+    {
+        if (urltype)
+            strcpy(urltype, "irafmem://");
+    }
+
+    /* --------------------------------------------- */
+    /* check if the 'filename+n' convention has been */
+    /* used to specifiy which HDU number to open     */ 
+    /* --------------------------------------------- */
+
+    jj = strlen(infile);
+
+    for (ii = jj - 1; ii >= 0; ii--)
+    {
+        if (infile[ii] == '+')    /* search backwards for '+' sign */
+            break;
+    }
+
+    if (ii > 0 && (jj - ii) < 7)  /* limit extension numbers to 5 digits */
+    {
+        infilelen = ii;
+        ii++;
+        ptr1 = infile+ii;   /* pointer to start of sequence */
+
+        for (; ii < jj; ii++)
+        {
+            if (!isdigit((int) infile[ii] ) ) /* are all the chars digits? */
+                break;
+        }
+
+        if (ii == jj)      
+        {
+             /* yes, the '+n' convention was used.  Copy */
+             /* the digits to the output extspec string. */
+             plus_ext = 1;
+
+             if (extspec) {
+	         if (jj - infilelen > FLEN_FILENAME - 1)
+	         {
+                     free(infile);
+                     return(*status = URL_PARSE_ERROR);
+                 }
+
+                 strncpy(extspec, ptr1, jj - infilelen);
+             }
+	     
+             infile[infilelen] = '\0'; /* delete the extension number */
+        }
+    }
+
+    /* -------------------------------------------------------------------- */
+    /* if '*' was given for the output name expand it to the root file name */
+    /* -------------------------------------------------------------------- */
+
+    if (outfile && outfile[0] == '*')
+    {
+        /* scan input name backwards to the first '/' character */
+        for (ii = jj - 1; ii >= 0; ii--)
+        {
+            if (infile[ii] == '/' || ii == 0)
+            {
+	      if (strlen(&infile[ii + 1]) > FLEN_FILENAME - 1)
+	      {
+                 free(infile);
+                 return(*status = URL_PARSE_ERROR);
+              }
+
+                strcpy(outfile, &infile[ii + 1]);
+                break;
+            }
+        }
+    }
+
+    /* ------------------------------------------ */
+    /* copy strings from local copy to the output */
+    /* ------------------------------------------ */
+    if (infilex) {
+	if (strlen(infile) > FLEN_FILENAME - 1)
+	{
+                 free(infile);
+                 return(*status = URL_PARSE_ERROR);
+        }
+
+        strcpy(infilex, infile);
+    }
+    /* ---------------------------------------------------------- */
+    /* if no '[' character in the input string, then we are done. */
+    /* ---------------------------------------------------------- */
+    if (!ptr3) 
+    {
+        free(infile);
+        return(*status);
+    }
+
+    /* ------------------------------------------- */
+    /* see if [ extension specification ] is given */
+    /* ------------------------------------------- */
+
+    if (!plus_ext) /* extension no. not already specified?  Then      */
+                   /* first brackets must enclose extension name or # */
+                   /* or it encloses a image subsection specification */
+                   /* or a raw binary image specifier */
+
+                   /* Or, the extension specification may have been */
+                   /* omitted and we have to guess what the user intended */
+    {
+       ptr1 = ptr3 + 1;    /* pointer to first char after the [ */
+
+       ptr2 = strchr(ptr1, ']' );   /* search for closing ] */
+       if (!ptr2)
+       {
+            ffpmsg("input file URL is missing closing bracket ']'");
+            free(infile);
+            return(*status = URL_PARSE_ERROR);  /* error, no closing ] */
+       }
+
+       /* ---------------------------------------------- */
+       /* First, test if this is a rawfile specifier     */
+       /* which looks something like: '[ib512,512:2880]' */
+       /* Test if first character is b,i,j,d,r,f, or u,  */
+       /* and optional second character is b or l,       */
+       /* followed by one or more digits,                */
+       /* finally followed by a ',', ':', or ']'         */
+       /* ---------------------------------------------- */
+
+       if (*ptr1 == 'b' || *ptr1 == 'B' || *ptr1 == 'i' || *ptr1 == 'I' ||
+           *ptr1 == 'j' || *ptr1 == 'J' || *ptr1 == 'd' || *ptr1 == 'D' ||
+           *ptr1 == 'r' || *ptr1 == 'R' || *ptr1 == 'f' || *ptr1 == 'F' ||
+           *ptr1 == 'u' || *ptr1 == 'U')
+       {
+           /* next optional character may be a b or l (for Big or Little) */
+           ptr1++;
+           if (*ptr1 == 'b' || *ptr1 == 'B' || *ptr1 == 'l' || *ptr1 == 'L')
+              ptr1++;
+
+           if (isdigit((int) *ptr1))  /* must have at least 1 digit */
+           {
+             while (isdigit((int) *ptr1))
+              ptr1++;             /* skip over digits */
+
+             if (*ptr1 == ',' || *ptr1 == ':' || *ptr1 == ']' )
+             {
+               /* OK, this looks like a rawfile specifier */
+
+               if (urltype)
+               {
+                 if (strstr(urltype, "stdin") )
+                   strcpy(urltype, "rawstdin://");
+                 else
+                   strcpy(urltype, "rawfile://");
+               }
+
+               /* append the raw array specifier to infilex */
+               if (infilex)
+               {
+
+	         if (strlen(infilex) + strlen(ptr3) > FLEN_FILENAME - 1)
+	         {
+                    free(infile);
+                    return(*status = URL_PARSE_ERROR);
+                 }
+
+                 strcat(infilex, ptr3);
+                 ptr1 = strchr(infilex, ']'); /* find the closing ] char */
+                 if (ptr1)
+                   *(ptr1 + 1) = '\0';  /* terminate string after the ] */
+               }
+
+               if (extspec)
+                  strcpy(extspec, "0"); /* the 0 ext number is implicit */
+
+               tmptr = strchr(ptr2 + 1, '[' ); /* search for another [ char */ 
+
+               /* copy any remaining characters into rowfilterx  */
+               if (tmptr && rowfilterx)
+               {
+
+
+	         if (strlen(rowfilterx) + strlen(tmptr + 1) > FLEN_FILENAME -1)
+	         {
+                    free(infile);
+                    return(*status = URL_PARSE_ERROR);
+                 }
+
+                 strcat(rowfilterx, tmptr + 1);
+
+                 tmptr = strchr(rowfilterx, ']' );   /* search for closing ] */
+                 if (tmptr)
+                   *tmptr = '\0'; /* overwrite the ] with null terminator */
+               }
+
+               free(infile);        /* finished parsing, so return */
+               return(*status);
+             }
+           }   
+       }        /* end of rawfile specifier test */
+
+       /* -------------------------------------------------------- */
+       /* Not a rawfile, so next, test if this is an image section */
+       /* i.e., an integer followed by a ':' or a '*' or '-*'      */
+       /* -------------------------------------------------------- */
+ 
+       ptr1 = ptr3 + 1;    /* reset pointer to first char after the [ */
+       tmptr = ptr1;
+
+       while (*tmptr == ' ')
+          tmptr++;   /* skip leading blanks */
+
+       while (isdigit((int) *tmptr))
+          tmptr++;             /* skip over leading digits */
+
+       if (*tmptr == ':' || *tmptr == '*' || *tmptr == '-')
+       {
+           /* this is an image section specifier */
+           strcat(rowfilter, ptr3);
+/*
+  don't want to assume 0 extension any more; may imply an image extension.
+           if (extspec)
+              strcpy(extspec, "0");
+*/
+       }
+       else
+       {
+       /* ----------------------------------------------------------------- 
+         Not an image section or rawfile spec so may be an extension spec. 
+
+         Examples of valid extension specifiers:
+            [3]                - 3rd extension; 0 = primary array
+            [events]           - events extension
+            [events, 2]        - events extension, with EXTVER = 2
+            [events,2]         - spaces are optional
+            [events, 3, b]     - same as above, plus XTENSION = 'BINTABLE'
+            [PICS; colName(12)] - an image in row 12 of the colName column
+                                      in the PICS table extension             
+            [PICS; colName(exposure > 1000)] - as above, but find image in
+                          first row with with exposure column value > 1000.
+            [Rate Table] - extension name can contain spaces!
+            [Rate Table;colName(exposure>1000)]
+
+         Examples of other types of specifiers (Not extension specifiers)
+
+            [bin]  !!! this is ambiguous, and can't be distinguished from
+                       a valid extension specifier
+            [bini X=1:512:16]  (also binb, binj, binr, and bind are allowed)
+            [binr (X,Y) = 5]
+            [bin @binfilter.txt]
+
+            [col Time;rate]
+            [col PI=PHA * 1.1]
+            [col -Time; status]
+
+            [X > 5]
+            [X>5]
+            [ filter txt]
+            [StatusCol]  !!! this is ambiguous, and can't be distinguished
+                       from a valid extension specifier
+            [StatusCol==0]
+            [StatusCol || x>6]
+            [gtifilter()]
+            [regfilter("region.reg")]
+
+         There will always be some ambiguity between an extension name and 
+         a boolean row filtering expression, (as in a couple of the above
+         examples).  If there is any doubt, the expression should be treated
+         as an extension specification;  The user can always add an explicit
+         expression specifier to override this interpretation.
+
+         The following decision logic will be used:
+
+         1) locate the first token, terminated with a space, comma, 
+            semi-colon, or closing bracket.
+
+         2) the token is not part of an extension specifier if any of
+            the following is true:
+
+            - if the token begins with '@' and contains a '.'
+            - if the token contains an operator: = > < || && 
+            - if the token begins with "gtifilter(" or "regfilter(" 
+            - if the token is terminated by a space and is followed by
+               additional characters (not a ']')  AND any of the following:
+                 - the token is 'col'
+                 - the token is 3 or 4 chars long and begins with 'bin'
+                 - the second token begins with an operator:
+                     ! = < > | & + - * / %
+                 
+
+         3) otherwise, the string is assumed to be an extension specifier
+
+         ----------------------------------------------------------------- */
+
+           tmptr = ptr1;
+           while(*tmptr == ' ')
+               tmptr++;
+
+           hasAt = 0;
+           hasDot = 0;
+           hasOper = 0;
+           followingOper = 0;
+           spaceTerm = 0;
+           rowFilter = 0;
+           colStart = 0;
+           binStart = 0;
+           pixStart = 0;
+
+           if (*tmptr == '@')  /* test for leading @ symbol */
+               hasAt = 1;
+
+           if ( !strncasecmp(tmptr, "col ", 4) )
+              colStart = 1;
+
+           if ( !strncasecmp(tmptr, "bin", 3) )
+              binStart = 1;
+
+           if ( !strncasecmp(tmptr, "pix", 3) )
+              pixStart = 1;
+
+           if ( !strncasecmp(tmptr, "gtifilter(", 10) ||
+                !strncasecmp(tmptr, "regfilter(", 10) )
+           {
+               rowFilter = 1;
+           }
+           else
+           {
+             /* parse the first token of the expression */
+             for (ii = 0; ii < ptr2 - ptr1 + 1; ii++, tmptr++)
+             {
+               if (*tmptr == '.')
+                   hasDot = 1;
+               else if (*tmptr == '=' || *tmptr == '>' || *tmptr == '<' ||
+                   (*tmptr == '|' && *(tmptr+1) == '|') ||
+                   (*tmptr == '&' && *(tmptr+1) == '&') )
+                   hasOper = 1;
+
+               else if (*tmptr == ',' || *tmptr == ';' || *tmptr == ']')
+               {
+                  break;
+               }
+               else if (*tmptr == ' ')   /* a space char? */
+               {
+                  while(*tmptr == ' ')  /* skip spaces */
+                    tmptr++;
+
+                  if (*tmptr == ']') /* is this the end? */
+                     break;  
+
+                  spaceTerm = 1; /* 1st token is terminated by space */
+
+                  /* test if this is a column or binning specifier */
+                  if (colStart || (ii <= 4 && (binStart || pixStart)) )
+                     rowFilter = 1;
+                  else
+                  {
+  
+                    /* check if next character is an operator */
+                    if (*tmptr == '=' || *tmptr == '>' || *tmptr == '<' ||
+                      *tmptr == '|' || *tmptr == '&' || *tmptr == '!' ||
+                      *tmptr == '+' || *tmptr == '-' || *tmptr == '*' ||
+                      *tmptr == '/' || *tmptr == '%')
+                       followingOper = 1;
+                  }
+                  break;
+               }
+             }
+           }
+
+           /* test if this is NOT an extension specifier */
+           if ( rowFilter || (pixStart && spaceTerm) ||
+                (hasAt && hasDot) ||
+                hasOper ||
+                (spaceTerm && followingOper) )
+           {
+               /* this is (probably) not an extension specifier */
+               /* so copy all chars to filter spec string */
+               strcat(rowfilter, ptr3);
+           }
+           else
+           {
+               /* this appears to be a legit extension specifier */
+               /* copy the extension specification */
+               if (extspec) {
+                   if (ptr2 - ptr1 > FLEN_FILENAME - 1) {
+                       free(infile);
+                       return(*status = URL_PARSE_ERROR);
+		   }
+                   strncat(extspec, ptr1, ptr2 - ptr1);
+               }
+
+               /* copy any remaining chars to filter spec string */
+               strcat(rowfilter, ptr2 + 1);
+           }
+       }
+    }      /* end of  if (!plus_ext)     */
+    else   
+    {
+      /* ------------------------------------------------------------------ */
+      /* already have extension, so this must be a filter spec of some sort */
+      /* ------------------------------------------------------------------ */
+
+        strcat(rowfilter, ptr3);
+    }
+
+    /* strip off any trailing blanks from filter */
+    slen = strlen(rowfilter);
+    while ( (--slen) >= 0  && rowfilter[slen] == ' ') 
+         rowfilter[slen] = '\0';
+
+    if (!rowfilter[0])
+    {
+        free(infile);
+        return(*status);      /* nothing left to parse */
+    }
+
+    /* ------------------------------------------------ */
+    /* does the filter contain a binning specification? */
+    /* ------------------------------------------------ */
+
+    ptr1 = strstr(rowfilter, "[bin");      /* search for "[bin" */
+    if (!ptr1)
+        ptr1 = strstr(rowfilter, "[BIN");      /* search for "[BIN" */
+    if (!ptr1)
+        ptr1 = strstr(rowfilter, "[Bin");      /* search for "[Bin" */
+
+    if (ptr1)
+    {
+      ptr2 = ptr1 + 4;     /* end of the '[bin' string */
+      if (*ptr2 == 'b' || *ptr2 == 'i' || *ptr2 == 'j' ||
+          *ptr2 == 'r' || *ptr2 == 'd')
+         ptr2++;  /* skip the datatype code letter */
+
+
+      if ( *ptr2 != ' ' && *ptr2 != ']')
+        ptr1 = NULL;   /* bin string must be followed by space or ] */
+    }
+
+    if (ptr1)
+    {
+        /* found the binning string */
+        if (binspec)
+        {
+	    if (strlen(ptr1 +1) > FLEN_FILENAME - 1)
+	    {
+                    free(infile);
+                    return(*status = URL_PARSE_ERROR);
+            }
+
+            strcpy(binspec, ptr1 + 1);       
+            ptr2 = strchr(binspec, ']');
+
+            if (ptr2)      /* terminate the binning filter */
+            {
+                *ptr2 = '\0';
+
+                if ( *(--ptr2) == ' ')  /* delete trailing spaces */
+                    *ptr2 = '\0';
+            }
+            else
+            {
+                ffpmsg("input file URL is missing closing bracket ']'");
+                ffpmsg(rowfilter);
+                free(infile);
+                return(*status = URL_PARSE_ERROR);  /* error, no closing ] */
+            }
+        }
+
+        /* delete the binning spec from the row filter string */
+        ptr2 = strchr(ptr1, ']');
+        strcpy(tmpstr, ptr2+1);  /* copy any chars after the binspec */
+        strcpy(ptr1, tmpstr);    /* overwrite binspec */
+    }
+
+    /* --------------------------------------------------------- */
+    /* does the filter contain a column selection specification? */
+    /* --------------------------------------------------------- */
+
+    ptr1 = strstr(rowfilter, "[col ");
+    if (!ptr1)
+    {
+        ptr1 = strstr(rowfilter, "[COL ");
+
+        if (!ptr1)
+            ptr1 = strstr(rowfilter, "[Col ");
+    }
+
+    if (ptr1)
+    {           /* find the end of the column specifier */
+        ptr2 = ptr1 + 5;
+        while (*ptr2 != ']')
+        {
+            if (*ptr2 == '\0')
+            {
+                ffpmsg("input file URL is missing closing bracket ']'");
+                free(infile);
+                return(*status = URL_PARSE_ERROR);  /* error, no closing ] */
+            }
+
+            if (*ptr2 == '\'')  /* start of a literal string */
+            {
+                ptr2 = strchr(ptr2 + 1, '\'');  /* find closing quote */
+                if (!ptr2)
+                {
+                  ffpmsg
+          ("literal string in input file URL is missing closing single quote");
+                  free(infile);
+                  return(*status = URL_PARSE_ERROR);  /* error, no closing ] */
+                }
+            }
+
+            if (*ptr2 == '[')  /* set of nested square brackets */
+            {
+                ptr2 = strchr(ptr2 + 1, ']');  /* find closing bracket */
+                if (!ptr2)
+                {
+                  ffpmsg
+          ("nested brackets in input file URL is missing closing bracket");
+                  free(infile);
+                  return(*status = URL_PARSE_ERROR);  /* error, no closing ] */
+                }
+            }
+
+            ptr2++;  /* continue search for the closing bracket character */
+        } 
+
+        collen = ptr2 - ptr1 - 1;
+
+        if (colspec)    /* copy the column specifier to output string */
+        {
+            if (collen > FLEN_FILENAME - 1) {
+                       free(infile);
+                       return(*status = URL_PARSE_ERROR);
+            }
+
+            strncpy(colspec, ptr1 + 1, collen);       
+            colspec[collen] = '\0';
+ 
+            while (colspec[--collen] == ' ')
+                colspec[collen] = '\0';  /* strip trailing blanks */
+        }
+
+        /* delete the column selection spec from the row filter string */
+        strcpy(tmpstr, ptr2 + 1);  /* copy any chars after the colspec */
+        strcpy(ptr1, tmpstr);      /* overwrite binspec */
+    }
+
+    /* --------------------------------------------------------- */
+    /* does the filter contain a pixel filter specification?     */
+    /* --------------------------------------------------------- */
+
+    ptr1 = strstr(rowfilter, "[pix");
+    if (!ptr1)
+    {
+        ptr1 = strstr(rowfilter, "[PIX");
+
+        if (!ptr1)
+            ptr1 = strstr(rowfilter, "[Pix");
+    }
+
+    if (ptr1)
+    {
+      ptr2 = ptr1 + 4;     /* end of the '[pix' string */
+      if (*ptr2 == 'b' || *ptr2 == 'i' || *ptr2 == 'j' || *ptr2 == 'B' ||
+          *ptr2 == 'I' || *ptr2 == 'J' || *ptr2 == 'r' || *ptr2 == 'd' ||
+           *ptr2 == 'R' || *ptr2 == 'D')
+         ptr2++;  /* skip the datatype code letter */
+
+      if (*ptr2 == '1')
+         ptr2++;   /* skip the single HDU indicator */
+
+      if ( *ptr2 != ' ')
+        ptr1 = NULL;   /* pix string must be followed by space */
+    }
+
+    if (ptr1)
+    {           /* find the end of the pixel filter */
+        while (*ptr2 != ']')
+        {
+            if (*ptr2 == '\0')
+            {
+                ffpmsg("input file URL is missing closing bracket ']'");
+                free(infile);
+                return(*status = URL_PARSE_ERROR);  /* error, no closing ] */
+            }
+
+            if (*ptr2 == '\'')  /* start of a literal string */
+            {
+                ptr2 = strchr(ptr2 + 1, '\'');  /* find closing quote */
+                if (!ptr2)
+                {
+                  ffpmsg
+          ("literal string in input file URL is missing closing single quote");
+                  free(infile);
+                  return(*status = URL_PARSE_ERROR);  /* error, no closing ] */
+                }
+            }
+
+            if (*ptr2 == '[')  /* set of nested square brackets */
+            {
+                ptr2 = strchr(ptr2 + 1, ']');  /* find closing bracket */
+                if (!ptr2)
+                {
+                  ffpmsg
+          ("nested brackets in input file URL is missing closing bracket");
+                  free(infile);
+                  return(*status = URL_PARSE_ERROR);  /* error, no closing ] */
+                }
+            }
+
+            ptr2++;  /* continue search for the closing bracket character */
+        } 
+
+        collen = ptr2 - ptr1 - 1;
+
+        if (pixfilter)    /* copy the column specifier to output string */
+        {
+            if (collen > FLEN_FILENAME - 1) {
+                       free(infile);
+                       return(*status = URL_PARSE_ERROR);
+            }
+
+            strncpy(pixfilter, ptr1 + 1, collen);       
+            pixfilter[collen] = '\0';
+ 
+            while (pixfilter[--collen] == ' ')
+                pixfilter[collen] = '\0';  /* strip trailing blanks */
+        }
+
+        /* delete the pixel filter from the row filter string */
+        strcpy(tmpstr, ptr2 + 1);  /* copy any chars after the pixel filter */
+        strcpy(ptr1, tmpstr);      /* overwrite binspec */
+    }
+
+
+    /* copy the remaining string to the rowfilter output... should only */
+    /* contain a rowfilter expression of the form "[expr]"              */
+
+    if (rowfilterx && rowfilter[0]) {
+       ptr2 = rowfilter + strlen(rowfilter) - 1;
+       if( rowfilter[0]=='[' && *ptr2==']' ) {
+          *ptr2 = '\0';
+
+	   if (strlen(rowfilter + 1)  > FLEN_FILENAME - 1)
+	   {
+                    free(infile);
+                    return(*status = URL_PARSE_ERROR);
+           }
+
+          strcpy(rowfilterx, rowfilter+1);
+       } else {
+          ffpmsg("input file URL lacks valid row filter expression");
+          *status = URL_PARSE_ERROR;
+       }
+    }
+
+    free(infile);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffexist(const char *infile, /* I - input filename or URL */
+            int *exists,        /* O -  2 = a compressed version of file exists */
+	                        /*      1 = yes, disk file exists               */
+	                        /*      0 = no, disk file could not be found    */
+				/*     -1 = infile is not a disk file (could    */
+				/*   be a http, ftp, gsiftp, smem, or stdin file) */
+            int *status)        /* I/O  status  */
+
+/*
+   test if the input file specifier is an existing file on disk
+   If the specified file can't be found, it then searches for a 
+   compressed version of the file.
+*/
+{
+    FILE *diskfile;
+    char rootname[FLEN_FILENAME];
+    char *ptr1;
+    
+    if (*status > 0)
+        return(*status);
+
+    /* strip off any extname or filters from the name */
+    ffrtnm( (char *)infile, rootname, status);
+
+    ptr1 = strstr(rootname, "://");
+    
+    if (ptr1 || *rootname == '-') {
+        if (!strncmp(rootname, "file", 4) ) {
+	    ptr1 = ptr1 + 3;   /* pointer to start of the disk file name */
+	} else {
+	    *exists = -1;   /* this is not a disk file */
+	    return (*status);
+	}
+    } else {
+        ptr1 = rootname;
+    }
+    
+    /* see if the disk file exists */
+    if (file_openfile(ptr1, 0, &diskfile)) {
+    
+        /* no, couldn't open file, so see if there is a compressed version */
+        if (file_is_compressed(ptr1) ) {
+           *exists = 2;  /* a compressed version of the file exists */
+        } else {
+	   *exists = 0;  /* neither file nor compressed version exist */
+	}
+	
+    } else {
+    
+        /* yes, file exists */
+        *exists = 1; 
+	fclose(diskfile);
+    }
+    	   
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffrtnm(char *url, 
+           char *rootname,
+           int *status)
+/*
+   parse the input URL, returning the root name (filetype://basename).
+*/
+
+{ 
+    int ii, jj, slen, infilelen;
+    char *ptr1, *ptr2, *ptr3;
+    char urltype[MAX_PREFIX_LEN];
+    char infile[FLEN_FILENAME];
+
+    if (*status > 0)
+        return(*status);
+
+    ptr1 = url;
+    *rootname = '\0';
+    *urltype = '\0';
+    *infile  = '\0';
+
+    /*  get urltype (e.g., file://, ftp://, http://, etc.)  */
+    if (*ptr1 == '-')        /* "-" means read file from stdin */
+    {
+        strcat(urltype, "-");
+        ptr1++;
+    }
+    else if (!strncmp(ptr1, "stdin", 5) || !strncmp(ptr1, "STDIN", 5))
+    {
+        strcat(urltype, "-");
+        ptr1 = ptr1 + 5;
+    }
+    else
+    {
+        ptr2 = strstr(ptr1, "://");
+        ptr3 = strstr(ptr1, "(" );
+
+        if (ptr3 && (ptr3 < ptr2) )
+        {
+           /* the urltype follows a '(' character, so it must apply */
+           /* to the output file, and is not the urltype of the input file */
+           ptr2 = 0;   /* so reset pointer to zero */
+        }
+
+
+        if (ptr2)                  /* copy the explicit urltype string */ 
+        {
+
+	   if (ptr2 - ptr1 + 3 > MAX_PREFIX_LEN - 1)
+	   {
+               return(*status = URL_PARSE_ERROR);
+           }
+            strncat(urltype, ptr1, ptr2 - ptr1 + 3);
+            ptr1 = ptr2 + 3;
+        }
+        else if (!strncmp(ptr1, "ftp:", 4) )
+        {                              /* the 2 //'s are optional */
+            strcat(urltype, "ftp://";);
+            ptr1 += 4;
+        }
+        else if (!strncmp(ptr1, "gsiftp:", 7) )
+        {                              /* the 2 //'s are optional */
+            strcat(urltype, "gsiftp://";);
+            ptr1 += 7;
+        }
+        else if (!strncmp(ptr1, "http:", 5) )
+        {                              /* the 2 //'s are optional */
+            strcat(urltype, "http://";);
+            ptr1 += 5;
+        }
+        else if (!strncmp(ptr1, "mem:", 4) )
+        {                              /* the 2 //'s are optional */
+            strcat(urltype, "mem://");
+            ptr1 += 4;
+        }
+        else if (!strncmp(ptr1, "shmem:", 6) )
+        {                              /* the 2 //'s are optional */
+            strcat(urltype, "shmem://");
+            ptr1 += 6;
+        }
+        else if (!strncmp(ptr1, "file:", 5) )
+        {                              /* the 2 //'s are optional */
+            ptr1 += 5;
+        }
+
+        /* else assume file driver    */
+    }
+ 
+       /*  get the input file name  */
+    ptr2 = strchr(ptr1, '(');   /* search for opening parenthesis ( */
+    ptr3 = strchr(ptr1, '[');   /* search for opening bracket [ */
+
+    if (ptr2 == ptr3)  /* simple case: no [ or ( in the file name */
+    {
+
+	if (strlen(ptr1) > FLEN_FILENAME - 1)
+        {
+            return(*status = URL_PARSE_ERROR);
+        }
+
+        strcat(infile, ptr1);
+    }
+    else if (!ptr3)     /* no bracket, so () enclose output file name */
+    {
+
+	if (ptr2 - ptr1 > FLEN_FILENAME - 1)
+        {
+            return(*status = URL_PARSE_ERROR);
+        }
+
+        strncat(infile, ptr1, ptr2 - ptr1);
+        ptr2++;
+
+        ptr1 = strchr(ptr2, ')' );   /* search for closing ) */
+        if (!ptr1)
+            return(*status = URL_PARSE_ERROR);  /* error, no closing ) */
+
+    }
+    else if (ptr2 && (ptr2 < ptr3)) /* () enclose output name before bracket */
+    {
+
+	if (ptr2 - ptr1 > FLEN_FILENAME - 1)
+        {
+            return(*status = URL_PARSE_ERROR); 
+        }
+
+        strncat(infile, ptr1, ptr2 - ptr1);
+        ptr2++;
+
+        ptr1 = strchr(ptr2, ')' );   /* search for closing ) */
+        if (!ptr1)
+            return(*status = URL_PARSE_ERROR);  /* error, no closing ) */
+    }
+    else    /*   bracket comes first, so there is no output name */
+    {
+	if (ptr3 - ptr1 > FLEN_FILENAME - 1)
+        {
+            return(*status = URL_PARSE_ERROR); 
+        }
+
+        strncat(infile, ptr1, ptr3 - ptr1);
+    }
+
+       /* strip off any trailing blanks in the names */
+    slen = strlen(infile);
+    for (ii = slen - 1; ii > 0; ii--)   
+    {
+        if (infile[ii] == ' ')
+            infile[ii] = '\0';
+        else
+            break;
+    }
+
+    /* --------------------------------------------- */
+    /* check if the 'filename+n' convention has been */
+    /* used to specifiy which HDU number to open     */ 
+    /* --------------------------------------------- */
+
+    jj = strlen(infile);
+
+    for (ii = jj - 1; ii >= 0; ii--)
+    {
+        if (infile[ii] == '+')    /* search backwards for '+' sign */
+            break;
+    }
+
+    if (ii > 0 && (jj - ii) < 5)  /* limit extension numbers to 4 digits */
+    {
+        infilelen = ii;
+        ii++;
+
+
+        for (; ii < jj; ii++)
+        {
+            if (!isdigit((int) infile[ii] ) ) /* are all the chars digits? */
+                break;
+        }
+
+        if (ii == jj)      
+        {
+             /* yes, the '+n' convention was used.  */
+
+             infile[infilelen] = '\0'; /* delete the extension number */
+        }
+    }
+
+    if (strlen(urltype) + strlen(infile) > FLEN_FILENAME - 1)
+    {
+            return(*status = URL_PARSE_ERROR); 
+    }
+
+    strcat(rootname, urltype);  /* construct the root name */
+    strcat(rootname, infile);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffourl(char *url,             /* I - full input URL   */
+           char *urltype,          /* O - url type         */
+           char *outfile,          /* O - base file name   */
+           char *tpltfile,         /* O - template file name, if any */
+           char *compspec,         /* O - compression specification, if any */
+           int *status)
+/*
+   parse the output URL into its basic components.
+*/
+
+{ 
+    char *ptr1, *ptr2, *ptr3;
+
+    if (*status > 0)
+        return(*status);
+
+    if (urltype)
+      *urltype = '\0';
+    if (outfile)
+      *outfile = '\0';
+    if (tpltfile)
+      *tpltfile = '\0';
+    if (compspec)
+      *compspec = '\0';
+
+    ptr1 = url;
+    while (*ptr1 == ' ')    /* ignore leading blanks */
+           ptr1++;
+
+    if ( ( (*ptr1 == '-') &&  ( *(ptr1 +1) ==  0   || *(ptr1 +1) == ' ' ) )
+         ||  !strcmp(ptr1, "stdout")
+         ||  !strcmp(ptr1, "STDOUT"))
+
+         /* "-" means write to stdout;  also support "- "            */
+         /* but exclude disk file names that begin with a minus sign */
+         /* e.g., "-55d33m.fits"   */
+    {
+      if (urltype)
+        strcpy(urltype, "stdout://");
+    }
+    else
+    {
+        /* not writing to stdout */
+        /*  get urltype (e.g., file://, ftp://, http://, etc.)  */
+
+        ptr2 = strstr(ptr1, "://");
+        if (ptr2)                  /* copy the explicit urltype string */ 
+        {
+          if (urltype) {
+	    if (ptr2 - ptr1 + 3 > MAX_PREFIX_LEN - 1)
+	    {
+                return(*status = URL_PARSE_ERROR); 
+            }
+
+            strncat(urltype, ptr1, ptr2 - ptr1 + 3);
+          }
+
+          ptr1 = ptr2 + 3;
+        }
+        else                       /* assume file driver    */
+        {
+          if (urltype)
+             strcat(urltype, "file://");
+        }
+
+        /* look for template file name, enclosed in parenthesis */
+        ptr2 = strchr(ptr1, '('); 
+
+        /* look for image compression parameters, enclosed in sq. brackets */
+        ptr3 = strchr(ptr1, '['); 
+
+        if (outfile)
+        {
+          if (ptr2) {  /* template file was specified  */
+	     if (ptr2 - ptr1 > FLEN_FILENAME - 1)
+	     {
+                return(*status = URL_PARSE_ERROR); 
+             }
+ 
+             strncat(outfile, ptr1, ptr2 - ptr1);
+          } else if (ptr3) {  /* compression was specified  */
+	     if (ptr3 - ptr1 > FLEN_FILENAME - 1)
+	     {
+                return(*status = URL_PARSE_ERROR); 
+             }
+             strncat(outfile, ptr1, ptr3 - ptr1);
+
+          } else { /* no template file or compression */
+	     if (strlen(ptr1) > FLEN_FILENAME - 1)
+	     {
+                return(*status = URL_PARSE_ERROR); 
+             }
+             strcpy(outfile, ptr1);
+          }
+        }
+
+
+        if (ptr2)   /* template file was specified  */
+        {
+            ptr2++;
+
+            ptr1 = strchr(ptr2, ')' );   /* search for closing ) */
+
+            if (!ptr1)
+            {
+                return(*status = URL_PARSE_ERROR);  /* error, no closing ) */
+            }
+
+            if (tpltfile) {
+	        if (ptr1 - ptr2 > FLEN_FILENAME - 1)
+	        {
+                   return(*status = URL_PARSE_ERROR); 
+                }
+                 strncat(tpltfile, ptr2, ptr1 - ptr2);
+            }
+        }
+        
+        if (ptr3)   /* compression was specified  */
+        {
+            ptr3++;
+
+            ptr1 = strchr(ptr3, ']' );   /* search for closing ] */
+
+            if (!ptr1)
+            {
+                return(*status = URL_PARSE_ERROR);  /* error, no closing ] */
+            }
+
+            if (compspec) {
+
+	        if (ptr1 - ptr3 > FLEN_FILENAME - 1)
+	        {
+                   return(*status = URL_PARSE_ERROR); 
+                }
+ 
+                strncat(compspec, ptr3, ptr1 - ptr3);
+            }
+        }
+
+        /* check if a .gz compressed output file is to be created */
+        /* by seeing if the filename ends in '.gz'   */
+        if (urltype && outfile)
+        {
+            if (!strcmp(urltype, "file://") )
+            {
+                ptr1 = strstr(outfile, ".gz");
+                if (ptr1)
+                {    /* make sure the ".gz" is at the end of the file name */
+                   ptr1 += 3;
+                   if (*ptr1 ==  0  || *ptr1 == ' '  )
+                      strcpy(urltype, "compressoutfile://");
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffexts(char *extspec, 
+                       int *extnum, 
+                       char *extname,
+                       int *extvers,
+                       int *hdutype,
+                       char *imagecolname,
+                       char *rowexpress,
+                       int *status)
+{
+/*
+   Parse the input extension specification string, returning either the
+   extension number or the values of the EXTNAME, EXTVERS, and XTENSION
+   keywords in desired extension. Also return the name of the column containing
+   an image, and an expression to be used to determine which row to use,
+   if present.
+*/
+    char *ptr1, *ptr2;
+    int slen, nvals;
+    int notint = 1; /* initially assume specified extname is not an integer */
+    char tmpname[FLEN_VALUE], *loc;
+
+    *extnum = 0;
+    *extname = '\0';
+    *extvers = 0;
+    *hdutype = ANY_HDU;
+    *imagecolname = '\0';
+    *rowexpress = '\0';
+
+    if (*status > 0)
+        return(*status);
+
+    ptr1 = extspec;       /* pointer to first char */
+
+    while (*ptr1 == ' ')  /* skip over any leading blanks */
+        ptr1++;
+
+    if (isdigit((int) *ptr1))  /* is the extension specification a number? */
+    {
+        notint = 0;  /* looks like extname may actually be the ext. number */
+        errno = 0;  /* reset this prior to calling strtol */
+        *extnum = strtol(ptr1, &loc, 10);  /* read the string as an integer */
+
+        while (*loc == ' ')  /* skip over trailing blanks */
+           loc++;
+
+        /* check for read error, or junk following the integer */
+        if ((*loc != '\0' && *loc != ';' ) || (errno == ERANGE) )
+        {
+           *extnum = 0;
+           notint = 1;  /* no, extname was not a simple integer after all */
+           errno = 0;  /* reset error condition flag if it was set */
+        }
+
+        if ( *extnum < 0 || *extnum > 99999)
+        {
+            *extnum = 0;   /* this is not a reasonable extension number */
+            ffpmsg("specified extension number is out of range:");
+            ffpmsg(extspec);
+            return(*status = URL_PARSE_ERROR); 
+        }
+    }
+
+
+/*  This logic was too simple, and failed on extnames like '1000TEMP' 
+    where it would try to move to the 1000th extension
+
+    if (isdigit((int) *ptr1))  
+    {
+        sscanf(ptr1, "%d", extnum);
+        if (*extnum < 0 || *extnum > 9999)
+        {
+            *extnum = 0;   
+            ffpmsg("specified extension number is out of range:");
+            ffpmsg(extspec);
+            return(*status = URL_PARSE_ERROR); 
+        }
+    }
+*/
+
+    if (notint)
+    {
+           /* not a number, so EXTNAME must be specified, followed by */
+           /* optional EXTVERS and XTENSION  values */
+
+           /* don't use space char as end indicator, because there */
+           /* may be imbedded spaces in the EXTNAME value */
+           slen = strcspn(ptr1, ",:;");   /* length of EXTNAME */
+
+	   if (slen > FLEN_VALUE - 1)
+	   {
+                return(*status = URL_PARSE_ERROR); 
+           }
+ 
+           strncat(extname, ptr1, slen);  /* EXTNAME value */
+
+           /* now remove any trailing blanks */
+           while (slen > 0 && *(extname + slen -1) == ' ')
+           {
+               *(extname + slen -1) = '\0';
+               slen--;
+           }
+
+           ptr1 += slen;
+           slen = strspn(ptr1, " ,:");  /* skip delimiter characters */
+           ptr1 += slen;
+
+           slen = strcspn(ptr1, " ,:;");   /* length of EXTVERS */
+           if (slen)
+           {
+               nvals = sscanf(ptr1, "%d", extvers);  /* EXTVERS value */
+               if (nvals != 1)
+               {
+                   ffpmsg("illegal EXTVER value in input URL:");
+                   ffpmsg(extspec);
+                   return(*status = URL_PARSE_ERROR);
+               }
+
+               ptr1 += slen;
+               slen = strspn(ptr1, " ,:");  /* skip delimiter characters */
+               ptr1 += slen;
+
+               slen = strcspn(ptr1, ";");   /* length of HDUTYPE */
+               if (slen)
+               {
+                 if (*ptr1 == 'b' || *ptr1 == 'B')
+                     *hdutype = BINARY_TBL;  
+                 else if (*ptr1 == 't' || *ptr1 == 'T' ||
+                          *ptr1 == 'a' || *ptr1 == 'A')
+                     *hdutype = ASCII_TBL;
+                 else if (*ptr1 == 'i' || *ptr1 == 'I')
+                     *hdutype = IMAGE_HDU;
+                 else
+                 {
+                     ffpmsg("unknown type of HDU in input URL:");
+                     ffpmsg(extspec);
+                     return(*status = URL_PARSE_ERROR);
+                 }
+               }
+           }
+           else
+           {
+                strcpy(tmpname, extname);
+                ffupch(tmpname);
+                if (!strcmp(tmpname, "PRIMARY") || !strcmp(tmpname, "P") )
+                    *extname = '\0';  /* return extnum = 0 */
+           }
+    }
+
+    ptr1 = strchr(ptr1, ';');
+    if (ptr1)
+    {
+        /* an image is to be opened; the image is contained in a single */
+        /* cell of a binary table.  A column name and an expression to  */
+        /* determine which row to use has been entered.                 */
+
+        ptr1++;  /* skip over the ';' delimiter */
+        while (*ptr1 == ' ')  /* skip over any leading blanks */
+            ptr1++;
+
+        ptr2 = strchr(ptr1, '(');
+        if (!ptr2)
+        {
+            ffpmsg("illegal specification of image in table cell in input URL:");
+            ffpmsg(" did not find a row expression enclosed in ( )");
+            ffpmsg(extspec);
+            return(*status = URL_PARSE_ERROR);
+        }
+
+	if (ptr2 - ptr1 > FLEN_FILENAME - 1)
+	{
+            return(*status = URL_PARSE_ERROR); 
+        }
+
+        strncat(imagecolname, ptr1, ptr2 - ptr1); /* copy column name */
+
+        ptr2++;  /* skip over the '(' delimiter */
+        while (*ptr2 == ' ')  /* skip over any leading blanks */
+            ptr2++;
+
+
+        ptr1 = strchr(ptr2, ')');
+        if (!ptr2)
+        {
+            ffpmsg("illegal specification of image in table cell in input URL:");
+            ffpmsg(" missing closing ')' character in row expression");
+            ffpmsg(extspec);
+            return(*status = URL_PARSE_ERROR);
+        }
+
+	if (ptr1 - ptr2 > FLEN_FILENAME - 1)
+        {
+                return(*status = URL_PARSE_ERROR); 
+        }
+ 
+        strncat(rowexpress, ptr2, ptr1 - ptr2); /* row expression */
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffextn(char *url,           /* I - input filename/URL  */
+           int *extension_num,  /* O - returned extension number */
+           int *status)
+{
+/*
+   Parse the input url string and return the number of the extension that
+   CFITSIO would automatically move to if CFITSIO were to open this input URL.
+   The extension numbers are one's based, so 1 = the primary array, 2 = the
+   first extension, etc.
+
+   The extension number that gets returned is determined by the following 
+   algorithm:
+
+   1. If the input URL includes a binning specification (e.g.
+   'myfile.fits[3][bin X,Y]') then the returned extension number
+   will always = 1, since CFITSIO would create a temporary primary
+   image on the fly in this case.  The same is true if an image
+   within a single cell of a binary table is opened.
+
+   2.  Else if the input URL specifies an extension number (e.g.,
+   'myfile.fits[3]' or 'myfile.fits+3') then the specified extension
+   number (+ 1) is returned.  
+
+   3.  Else if the extension name is specified in brackets
+   (e.g., this 'myfile.fits[EVENTS]') then the file will be opened and searched
+   for the extension number.  If the input URL is '-'  (reading from the stdin
+   file stream) this is not possible and an error will be returned.
+
+   4.  Else if the URL does not specify an extension (e.g. 'myfile.fits') then
+   a special extension number = -99 will be returned to signal that no
+   extension was specified.  This feature is mainly for compatibility with
+   existing FTOOLS software.  CFITSIO would open the primary array by default
+   (extension_num = 1) in this case.
+
+*/
+    fitsfile *fptr;
+    char urltype[20];
+    char infile[FLEN_FILENAME];
+    char outfile[FLEN_FILENAME]; 
+    char extspec[FLEN_FILENAME];
+    char extname[FLEN_FILENAME];
+    char rowfilter[FLEN_FILENAME];
+    char binspec[FLEN_FILENAME];
+    char colspec[FLEN_FILENAME];
+    char imagecolname[FLEN_VALUE], rowexpress[FLEN_FILENAME];
+    char *cptr;
+    int extnum, extvers, hdutype, tstatus = 0;
+
+    if (*status > 0)
+        return(*status);
+
+    /*  parse the input URL into its basic components  */
+    fits_parse_input_url(url, urltype, infile, outfile,
+             extspec, rowfilter,binspec, colspec, status);
+
+    if (*status > 0)
+        return(*status);
+
+    if (*binspec)   /* is there a binning specification? */
+    {
+       *extension_num = 1; /* a temporary primary array image is created */
+       return(*status);
+    }
+
+    if (*extspec)   /* is an extension specified? */
+    {
+       ffexts(extspec, &extnum, 
+         extname, &extvers, &hdutype, imagecolname, rowexpress, status);
+
+      if (*status > 0)
+        return(*status);
+
+      if (*imagecolname)   /* is an image within a table cell being opened? */
+      {
+         *extension_num = 1; /* a temporary primary array image is created */
+         return(*status);
+      }
+
+      if (*extname)
+      {
+         /* have to open the file to search for the extension name (curses!) */
+
+         if (!strcmp(urltype, "stdin://"))
+            /* opening stdin would destroying it! */
+            return(*status = URL_PARSE_ERROR); 
+
+         /* First, strip off any filtering specification */
+         infile[0] = '\0';
+	 strncat(infile, url, FLEN_FILENAME -1);
+	 
+         cptr = strchr(infile, ']');  /* locate the closing bracket */
+         if (!cptr)
+         {
+             return(*status = URL_PARSE_ERROR);
+         }
+         else
+         {
+             cptr++;
+             *cptr = '\0'; /* terminate URl after the extension spec */
+         }
+
+         if (ffopen(&fptr, infile, READONLY, status) > 0) /* open the file */
+         {
+            ffclos(fptr, &tstatus);
+            return(*status);
+         }
+
+         ffghdn(fptr, &extnum);    /* where am I in the file? */
+         *extension_num = extnum;
+         ffclos(fptr, status);
+
+         return(*status);
+      }
+      else
+      {
+         *extension_num = extnum + 1;  /* return the specified number (+ 1) */
+         return(*status);
+      }
+    }
+    else
+    {
+         *extension_num = -99;  /* no specific extension was specified */
+                                /* defaults to primary array */
+         return(*status);
+    }
+}
+/*--------------------------------------------------------------------------*/
+
+int ffurlt(fitsfile *fptr, char *urlType, int *status)
+/*
+   return the prefix string associated with the driver in use by the
+   fitsfile pointer fptr
+*/
+
+{ 
+  strcpy(urlType, driverTable[fptr->Fptr->driver].prefix);
+  return(*status);
+}
+
+/*--------------------------------------------------------------------------*/
+int ffimport_file( char *filename,   /* Text file to read                   */
+                   char **contents,  /* Pointer to pointer to hold file     */
+                   int *status )     /* CFITSIO error code                  */
+/*
+   Read and concatenate all the lines from the given text file.  User
+   must free the pointer returned in contents.  Pointer is guaranteed
+   to hold 2 characters more than the length of the text... allows the
+   calling routine to append (or prepend) a newline (or quotes?) without
+   reallocating memory.
+*/
+{
+   int allocLen, totalLen, llen, eoline;
+   char *lines,line[256];
+   FILE *aFile;
+
+   if( *status > 0 ) return( *status );
+
+   totalLen =    0;
+   allocLen = 1024;
+   lines    = (char *)malloc( allocLen * sizeof(char) );
+   if( !lines ) {
+      ffpmsg("Couldn't allocate memory to hold ASCII file contents.");
+      return(*status = MEMORY_ALLOCATION );
+   }
+   lines[0] = '\0';
+
+   if( (aFile = fopen( filename, "r" ))==NULL ) {
+      sprintf(line,"Could not open ASCII file %s.",filename);
+      ffpmsg(line);
+      free( lines );
+      return(*status = FILE_NOT_OPENED);
+   }
+
+   while( fgets(line,256,aFile)!=NULL ) {
+      llen = strlen(line);
+      if ((llen > 1) && (line[0] == '/' && line[1] == '/'))
+          continue;       /* skip comment lines begging with // */
+
+      eoline = 0;
+
+      /* replace CR and newline chars at end of line with nulls */
+      if ((llen > 0) && (line[llen-1]=='\n' || line[llen-1] == '\r')) {
+          line[--llen] = '\0';
+          eoline = 1;   /* found an end of line character */
+
+          if ((llen > 0) && (line[llen-1]=='\n' || line[llen-1] == '\r')) {
+                 line[--llen] = '\0';
+          }
+      }
+
+      if( totalLen + llen + 3 >= allocLen ) {
+         allocLen += 256;
+         lines = (char *)realloc(lines, allocLen * sizeof(char) );
+         if( ! lines ) {
+            ffpmsg("Couldn't allocate memory to hold ASCII file contents.");
+            *status = MEMORY_ALLOCATION;
+            break;
+         }
+      }
+      strcpy( lines+totalLen, line );
+      totalLen += llen;
+
+      if (eoline) {
+         strcpy( lines+totalLen, " "); /* add a space between lines */
+         totalLen += 1;
+      }
+   }
+   fclose(aFile);
+
+   *contents = lines;
+   return( *status );
+}
+
+/*--------------------------------------------------------------------------*/
+int fits_get_token(char **ptr, 
+                   char *delimiter,
+                   char *token,
+                   int *isanumber)   /* O - is this token a number? */
+/*
+   parse off the next token, delimited by a character in 'delimiter',
+   from the input ptr string;  increment *ptr to the end of the token.
+   Returns the length of the token, not including the delimiter char;
+*/
+{
+    char *loc, tval[73];
+    int slen;
+    double dval;
+    
+    *token = '\0';
+
+    while (**ptr == ' ')  /* skip over leading blanks */
+        (*ptr)++;
+
+    slen = strcspn(*ptr, delimiter);  /* length of next token */
+    if (slen)
+    {
+        strncat(token, *ptr, slen);       /* copy token */
+
+        (*ptr) += slen;                   /* skip over the token */
+
+        if (isanumber)  /* check if token is a number */
+        {
+            *isanumber = 1;
+
+	    if (strchr(token, 'D'))  {
+	        strcpy(tval, token);
+
+	        /*  The C language does not support a 'D'; replace with 'E' */
+	        if (loc = strchr(tval, 'D')) *loc = 'E';
+
+	        dval =  strtod(tval, &loc);
+	    } else {
+	        dval =  strtod(token, &loc);
+ 	    }
+
+	    /* check for read error, or junk following the value */
+	    if (*loc != '\0' && *loc != ' ' ) *isanumber = 0;
+	    if (errno == ERANGE) *isanumber = 0;
+        }
+    }
+
+    return(slen);
+}
+/*---------------------------------------------------------------------------*/
+char *fits_split_names(
+   char *list)   /* I   - input list of names */
+{
+/*  
+   A sequence of calls to fits_split_names will split the input string
+   into name tokens.  The string typically contains a list of file or
+   column names.  The names must be delimited by a comma and/or spaces.
+   This routine ignores spaces and commas that occur within parentheses,
+   brackets, or curly brackets.  It also strips any leading and trailing
+   blanks from the returned name.
+
+   This routine is similar to the ANSI C 'strtok' function:
+
+   The first call to fits_split_names has a non-null input string.
+   It finds the first name in the string and terminates it by
+   overwriting the next character of the string with a '\0' and returns
+   a pointer to the name.  Each subsequent call, indicated by a NULL
+   value of the input string, returns the next name, searching from
+   just past the end of the previous name.  It returns NULL when no
+   further names are found.
+
+   The following line illustrates how a string would be split into 3 names:
+    myfile[1][bin (x,y)=4], file2.fits  file3.fits
+    ^^^^^^^^^^^^^^^^^^^^^^  ^^^^^^^^^^  ^^^^^^^^^^
+      1st name               2nd name    3rd name
+
+
+NOTE:  This routine is not thread-safe.  
+This routine is simply provided as a utility routine for other external
+software. It is not used by any CFITSIO routine.
+
+*/
+    int depth = 0;
+    char *start;
+    static char *ptr;
+
+    if (list)  /* reset ptr if a string is given */
+        ptr = list;
+
+    while (*ptr == ' ')ptr++;  /* skip leading white space */
+
+    if (*ptr == '\0')return(0);  /* no remaining file names */
+
+    start = ptr;
+
+    while (*ptr != '\0') {
+       if ((*ptr == '[') || (*ptr == '(') || (*ptr == '{')) depth ++;
+       else if ((*ptr == '}') || (*ptr == ')') || (*ptr == ']')) depth --;
+       else if ((depth == 0) && (*ptr == ','  || *ptr == ' ')) {
+          *ptr = '\0';  /* terminate the filename here */
+          ptr++;  /* save pointer to start of next filename */
+          break;  
+       }
+       ptr++;
+    }
+    
+    return(start);
+}
+/*--------------------------------------------------------------------------*/
+int urltype2driver(char *urltype, int *driver)
+/*
+   compare input URL with list of known drivers, returning the
+   matching driver numberL.
+*/
+
+{ 
+    int ii;
+
+       /* find matching driver; search most recent drivers first */
+
+    for (ii=no_of_drivers - 1; ii >= 0; ii--)
+    {
+        if (0 == strcmp(driverTable[ii].prefix, urltype))
+        { 
+             *driver = ii;
+             return(0);
+        }
+    }
+
+    return(NO_MATCHING_DRIVER);   
+}
+/*--------------------------------------------------------------------------*/
+int ffclos(fitsfile *fptr,      /* I - FITS file pointer */
+           int *status)         /* IO - error status     */
+/*
+  close the FITS file by completing the current HDU, flushing it to disk,
+  then calling the system dependent routine to physically close the FITS file
+*/   
+{
+    int tstatus = NO_CLOSE_ERROR, zerostatus = 0;
+
+    if (!fptr)
+        return(*status = NULL_INPUT_PTR);
+    else if ((fptr->Fptr)->validcode != VALIDSTRUC) /* check for magic value */
+        return(*status = BAD_FILEPTR); 
+
+    /* close and flush the current HDU */
+    if (*status > 0)
+       ffchdu(fptr, &tstatus);  /* turn off the error message from ffchdu */
+    else
+       ffchdu(fptr, status);         
+
+    ((fptr->Fptr)->open_count)--;           /* decrement usage counter */
+
+    if ((fptr->Fptr)->open_count == 0)  /* if no other files use structure */
+    {
+        ffflsh(fptr, TRUE, status);   /* flush and disassociate IO buffers */
+
+        /* call driver function to actually close the file */
+        if ((*driverTable[(fptr->Fptr)->driver].close)((fptr->Fptr)->filehandle))
+        {
+            if (*status <= 0)
+            {
+              *status = FILE_NOT_CLOSED;  /* report if no previous error */
+
+              ffpmsg("failed to close the following file: (ffclos)");
+              ffpmsg((fptr->Fptr)->filename);
+            }
+        }
+
+        fits_clear_Fptr( fptr->Fptr, status);  /* clear Fptr address */
+        free((fptr->Fptr)->iobuffer);    /* free memory for I/O buffers */
+        free((fptr->Fptr)->headstart);    /* free memory for headstart array */
+        free((fptr->Fptr)->filename);     /* free memory for the filename */
+        (fptr->Fptr)->filename = 0;
+        (fptr->Fptr)->validcode = 0; /* magic value to indicate invalid fptr */
+        free(fptr->Fptr);         /* free memory for the FITS file structure */
+        free(fptr);               /* free memory for the FITS file structure */
+    }
+    else
+    {
+        /*
+           to minimize the fallout from any previous error (e.g., trying to 
+           open a non-existent extension in a already opened file), 
+           always call ffflsh with status = 0.
+        */
+        /* just flush the buffers, don't disassociate them */
+        if (*status > 0)
+            ffflsh(fptr, FALSE, &zerostatus); 
+        else
+            ffflsh(fptr, FALSE, status); 
+
+        free(fptr);               /* free memory for the FITS file structure */
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffdelt(fitsfile *fptr,      /* I - FITS file pointer */
+           int *status)         /* IO - error status     */
+/*
+  close and DELETE the FITS file. 
+*/
+{
+    char *basename;
+    int slen, tstatus = 0;
+
+    if (!fptr)
+        return(*status = NULL_INPUT_PTR);
+    else if ((fptr->Fptr)->validcode != VALIDSTRUC) /* check for magic value */
+        return(*status = BAD_FILEPTR); 
+
+    ffchdu(fptr, status);    /* close the current HDU, ignore any errors */
+    ffflsh(fptr, TRUE, status);     /* flush and disassociate IO buffers */
+
+        /* call driver function to actually close the file */
+    if ( (*driverTable[(fptr->Fptr)->driver].close)((fptr->Fptr)->filehandle) )
+    {
+        if (*status <= 0)
+        {
+            *status = FILE_NOT_CLOSED;  /* report error if no previous error */
+
+            ffpmsg("failed to close the following file: (ffdelt)");
+            ffpmsg((fptr->Fptr)->filename);
+        }
+    }
+
+    /* call driver function to actually delete the file */
+    if ( (driverTable[(fptr->Fptr)->driver].remove) )
+    {
+        /* parse the input URL to get the base filename */
+        slen = strlen((fptr->Fptr)->filename);
+        basename = (char *) malloc(slen +1);
+        if (!basename)
+            return(*status = MEMORY_ALLOCATION);
+    
+        fits_parse_input_url((fptr->Fptr)->filename, NULL, basename, NULL, NULL, NULL, NULL,
+               NULL, &tstatus);
+
+       if ((*driverTable[(fptr->Fptr)->driver].remove)(basename))
+        {
+            ffpmsg("failed to delete the following file: (ffdelt)");
+            ffpmsg((fptr->Fptr)->filename);
+            if (!(*status))
+                *status = FILE_NOT_CLOSED;
+        }
+        free(basename);
+    }
+
+    fits_clear_Fptr( fptr->Fptr, status);  /* clear Fptr address */
+    free((fptr->Fptr)->iobuffer);    /* free memory for I/O buffers */
+    free((fptr->Fptr)->headstart);    /* free memory for headstart array */
+    free((fptr->Fptr)->filename);     /* free memory for the filename */
+    (fptr->Fptr)->filename = 0;
+    (fptr->Fptr)->validcode = 0;      /* magic value to indicate invalid fptr */
+    free(fptr->Fptr);              /* free memory for the FITS file structure */
+    free(fptr);                    /* free memory for the FITS file structure */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fftrun( fitsfile *fptr,    /* I - FITS file pointer           */
+             LONGLONG filesize,   /* I - size to truncate the file   */
+             int *status)      /* O - error status                */
+/*
+  low level routine to truncate a file to a new smaller size.
+*/
+{
+  if (driverTable[(fptr->Fptr)->driver].truncate)
+  {
+    ffflsh(fptr, FALSE, status);  /* flush all the buffers first */
+    (fptr->Fptr)->filesize = filesize;
+    (fptr->Fptr)->io_pos = filesize;
+    (fptr->Fptr)->logfilesize = filesize;
+    (fptr->Fptr)->bytepos = filesize;
+    ffbfeof(fptr, status);   /* eliminate any buffers beyond current EOF */
+    return (*status = 
+     (*driverTable[(fptr->Fptr)->driver].truncate)((fptr->Fptr)->filehandle,
+     filesize) );
+  }
+  else
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffflushx( FITSfile *fptr)     /* I - FITS file pointer                  */
+/*
+  low level routine to flush internal file buffers to the file.
+*/
+{
+    if (driverTable[fptr->driver].flush)
+        return ( (*driverTable[fptr->driver].flush)(fptr->filehandle) );
+    else
+        return(0);    /* no flush function defined for this driver */
+}
+/*--------------------------------------------------------------------------*/
+int ffseek( FITSfile *fptr,   /* I - FITS file pointer              */
+            LONGLONG position)   /* I - byte position to seek to       */
+/*
+  low level routine to seek to a position in a file.
+*/
+{
+    return( (*driverTable[fptr->driver].seek)(fptr->filehandle, position) );
+}
+/*--------------------------------------------------------------------------*/
+int ffwrite( FITSfile *fptr,   /* I - FITS file pointer              */
+             long nbytes,      /* I - number of bytes to write       */
+             void *buffer,     /* I - buffer to write                */
+             int *status)      /* O - error status                   */
+/*
+  low level routine to write bytes to a file.
+*/
+{
+    if ( (*driverTable[fptr->driver].write)(fptr->filehandle, buffer, nbytes) )
+    {
+        ffpmsg("Error writing data buffer to file:");
+	ffpmsg(fptr->filename);
+
+        *status = WRITE_ERROR;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffread( FITSfile *fptr,   /* I - FITS file pointer              */
+            long nbytes,      /* I - number of bytes to read        */
+            void *buffer,     /* O - buffer to read into            */
+            int *status)      /* O - error status                   */
+/*
+  low level routine to read bytes from a file.
+*/
+{
+    int readstatus;
+
+    readstatus = (*driverTable[fptr->driver].read)(fptr->filehandle, 
+        buffer, nbytes);
+
+    if (readstatus == END_OF_FILE)
+        *status = END_OF_FILE;
+    else if (readstatus > 0)
+    {
+        ffpmsg("Error reading data buffer from file:");
+	ffpmsg(fptr->filename);
+
+        *status = READ_ERROR;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fftplt(fitsfile **fptr,      /* O - FITS file pointer                   */
+           const char *filename, /* I - name of file to create              */
+           const char *tempname, /* I - name of template file               */
+           int *status)          /* IO - error status                       */
+/*
+  Create and initialize a new FITS file  based on a template file.
+  Uses C fopen and fgets functions.
+*/
+{
+    if (*status > 0)
+        return(*status);
+
+    if ( ffinit(fptr, filename, status) )  /* create empty file */
+        return(*status);
+
+    ffoptplt(*fptr, tempname, status);  /* open and use template */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffoptplt(fitsfile *fptr,      /* O - FITS file pointer                   */
+            const char *tempname, /* I - name of template file               */
+            int *status)          /* IO - error status                       */
+/*
+  open template file and use it to create new file
+*/
+{
+    fitsfile *tptr;
+    int tstatus = 0, nkeys, nadd, ii;
+    char card[FLEN_CARD];
+
+    if (*status > 0)
+        return(*status);
+
+    if (tempname == NULL || *tempname == '\0')     /* no template file? */
+        return(*status);
+
+    /* try opening template */
+    ffopen(&tptr, (char *) tempname, READONLY, &tstatus); 
+
+    if (tstatus)  /* not a FITS file, so treat it as an ASCII template */
+    {
+        ffxmsg(2, card);  /* clear the  error message */
+        fits_execute_template(fptr, (char *) tempname, status);
+
+        ffmahd(fptr, 1, 0, status);   /* move back to the primary array */
+        return(*status);
+    }
+    else  /* template is a valid FITS file */
+    {
+        ffmahd(tptr, 1, NULL, status); /* make sure we are at the beginning */
+        while (*status <= 0)
+        {
+           ffghsp(tptr, &nkeys, &nadd, status); /* get no. of keywords */
+
+           for (ii = 1; ii <= nkeys; ii++)   /* copy keywords */
+           {
+              ffgrec(tptr,  ii, card, status);
+
+              /* must reset the PCOUNT keyword to zero in the new output file */
+              if (strncmp(card, "PCOUNT  ",8) == 0) { /* the PCOUNT keyword? */
+	         if (strncmp(card+25, "    0", 5)) {  /* non-zero value? */
+		    strncpy(card, "PCOUNT  =                    0", 30);
+		 }
+	      }   
+ 
+              ffprec(fptr, card, status);
+           }
+
+           ffmrhd(tptr, 1, 0, status); /* move to next HDU until error */
+           ffcrhd(fptr, status);  /* create empty new HDU in output file */
+        }
+
+        if (*status == END_OF_FILE)
+        {
+           *status = 0;              /* expected error condition */
+        }
+        ffclos(tptr, status);       /* close the template file */
+    }
+
+    ffmahd(fptr, 1, 0, status);   /* move to the primary array */
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+void ffrprt( FILE *stream, int status)
+/* 
+   Print out report of cfitsio error status and messages on the error stack.
+   Uses C FILE stream.
+*/
+{
+    char status_str[FLEN_STATUS], errmsg[FLEN_ERRMSG];
+  
+    if (status)
+    {
+
+      fits_get_errstatus(status, status_str);  /* get the error description */
+      fprintf(stream, "\nFITSIO status = %d: %s\n", status, status_str);
+
+      while ( fits_read_errmsg(errmsg) )  /* get error stack messages */
+             fprintf(stream, "%s\n", errmsg);
+    }
+    return; 
+}
+/*--------------------------------------------------------------------------*/
+int pixel_filter_helper(
+           fitsfile **fptr,  /* IO - pointer to input image; on output it  */
+                             /*      points to the new image */
+           char *outfile,    /* I - name for output file        */
+           char *expr,       /* I - Image filter expression    */
+           int *status)
+{
+	PixelFilter filter = { 0 };
+	char * DEFAULT_TAG = "X";
+	int ii, hdunum;
+        int singleHDU = 0;
+
+	filter.count = 1;
+	filter.ifptr = fptr;
+	filter.tag = &DEFAULT_TAG;
+
+    /* create new empty file for result */
+    if (ffinit(&filter.ofptr, outfile, status) > 0)
+    {
+        ffpmsg("failed to create output file for pixel filter:");
+        ffpmsg(outfile);
+        return(*status);
+    }
+
+    fits_get_hdu_num(*fptr, &hdunum);  /* current HDU number in input file */
+
+    expr += 3; /* skip 'pix' */
+    switch (expr[0]) {
+       case 'b': 
+       case 'B': filter.bitpix = BYTE_IMG; break;
+       case 'i':
+       case 'I': filter.bitpix = SHORT_IMG; break;
+       case 'j':
+       case 'J': filter.bitpix = LONG_IMG; break;
+       case 'r':
+       case 'R': filter.bitpix = FLOAT_IMG; break;
+       case 'd':
+       case 'D': filter.bitpix = DOUBLE_IMG; break;
+    }
+    if (filter.bitpix) /* skip bitpix indicator */
+       ++expr;
+
+    if (*expr == '1') {
+       ++expr;
+       singleHDU = 1;
+    }
+
+    if (((*fptr)->Fptr)->only_one)
+       singleHDU = 1;
+
+    if (*expr != ' ') {
+       ffpmsg("pixel filtering expression not space separated:");
+       ffpmsg(expr);
+    }
+    while (*expr == ' ')
+       ++expr;
+
+    /* copy all preceding extensions to the output file */
+    for (ii = 1; !singleHDU && ii < hdunum; ii++)
+    {
+        fits_movabs_hdu(*fptr, ii, NULL, status);
+        if (fits_copy_hdu(*fptr, filter.ofptr, 0, status) > 0)
+        {
+            ffclos(filter.ofptr, status);
+            return(*status);
+        }
+    }
+
+    /* move back to the original HDU position */
+    fits_movabs_hdu(*fptr, hdunum, NULL, status);
+
+	filter.expression = expr;
+    if (fits_pixel_filter(&filter, status)) {
+        ffpmsg("failed to execute image filter:");
+        ffpmsg(expr);
+        ffclos(filter.ofptr, status);
+        return(*status);
+    }
+
+
+    /* copy any remaining HDUs to the output file */
+
+    for (ii = hdunum + 1; !singleHDU; ii++)
+    {
+        if (fits_movabs_hdu(*fptr, ii, NULL, status) > 0)
+            break;
+
+        fits_copy_hdu(*fptr, filter.ofptr, 0, status);
+    }
+
+    if (*status == END_OF_FILE)   
+        *status = 0;              /* got the expected EOF error; reset = 0  */
+    else if (*status > 0)
+    {
+        ffclos(filter.ofptr, status);
+        return(*status);
+    }
+
+    /* close the original file and return ptr to the new image */
+    ffclos(*fptr, status);
+
+    *fptr = filter.ofptr; /* reset the pointer to the new table */
+
+    /* move back to the image subsection */
+    if (ii - 1 != hdunum)
+        fits_movabs_hdu(*fptr, hdunum, NULL, status);
+
+    return(*status);
+}
diff --git a/src/plugins/cfitsio/cfortran.h b/src/plugins/cfitsio/cfortran.h
new file mode 100644
index 0000000..703a41b
--- /dev/null
+++ b/src/plugins/cfitsio/cfortran.h
@@ -0,0 +1,2515 @@
+/* cfortran.h  4.4 */
+/* http://www-zeus.desy.de/~burow/cfortran/                   */
+/* Burkhard Burow  burow desy de                 1990 - 2002. */
+
+#ifndef __CFORTRAN_LOADED
+#define __CFORTRAN_LOADED
+
+/* 
+   THIS FILE IS PROPERTY OF BURKHARD BUROW. IF YOU ARE USING THIS FILE YOU
+   SHOULD ALSO HAVE ACCESS TO CFORTRAN.DOC WHICH PROVIDES TERMS FOR USING,
+   MODIFYING, COPYING AND DISTRIBUTING THE CFORTRAN.H PACKAGE.
+*/
+
+/* The following modifications were made by the authors of CFITSIO or by me. 
+ * They are flagged below with CFITSIO, the author's initials, or KMCCARTY.
+ * PDW = Peter Wilson
+ * DM  = Doug Mink
+ * LEB = Lee E Brotzman
+ * MR  = Martin Reinecke
+ * WDP = William D Pence
+ * -- Kevin McCarty, for Debian (19 Dec. 2005) */
+
+/*******
+   Modifications:
+      Oct 1997: Changed symbol name extname to appendus (PDW/HSTX)
+                (Conflicted with a common variable name in FTOOLS)
+      Nov 1997: If g77Fortran defined, also define f2cFortran (PDW/HSTX)
+      Feb 1998: Let VMS see the NUM_ELEMS code. Lets programs treat
+                single strings as vectors with single elements
+      Nov 1999: If macintoxh defined, also define f2cfortran (for Mac OS-X)
+      Apr 2000: If WIN32 defined, also define PowerStationFortran and
+                VISUAL_CPLUSPLUS (Visual C++)
+      Jun 2000: If __GNUC__ and linux defined, also define f2cFortran
+                (linux/gcc environment detection)
+      Apr 2002: If __CYGWIN__ is defined, also define f2cFortran
+      Nov 2002: If __APPLE__ defined, also define f2cfortran (for Mac OS-X)
+
+      Nov 2003: If __INTEL_COMPILER or INTEL_COMPILER defined, also define
+                f2cFortran (KMCCARTY)
+      Dec 2005: If f2cFortran is defined, enforce REAL functions in FORTRAN
+                returning "double" in C.  This was one of the items on
+		Burkhard's TODO list. (KMCCARTY)
+      Dec 2005: Modifications to support 8-byte integers. (MR)
+		USE AT YOUR OWN RISK!
+      Feb 2006  Added logic to typedef the symbol 'LONGLONG' to an appropriate
+                intrinsic 8-byte integer datatype  (WDP)
+      Apr 2006: Modifications to support gfortran (and g77 with -fno-f2c flag)
+                since by default it returns "float" for FORTRAN REAL function.
+                (KMCCARTY)
+      May 2008: Revert commenting out of "extern" in COMMON_BLOCK_DEF macro.
+		Add braces around do-nothing ";" in 3 empty while blocks to
+		get rid of compiler warnings.  Thanks to ROOT developers
+		Jacek Holeczek and Rene Brun for these suggestions. (KMCCARTY)
+      Dec 2008  Added typedef for LONGLONG to support Borland compiler (WDP)
+ *******/
+
+/* 
+  Avoid symbols already used by compilers and system *.h:
+  __ - OSF1 zukal06 V3.0 347 alpha, cc -c -std1 cfortest.c
+
+*/
+
+/* 
+   Determine what 8-byte integer data type is available.
+  'long long' is now supported by most compilers, but older
+  MS Visual C++ compilers before V7.0 use '__int64' instead. (WDP)
+*/
+
+#ifndef LONGLONG_TYPE   /* this may have been previously defined */
+#if defined(_MSC_VER)   /* Microsoft Visual C++ */
+
+#if (_MSC_VER < 1300)   /* versions earlier than V7.0 do not have 'long long' */
+    typedef __int64 LONGLONG;
+#else                   /* newer versions do support 'long long' */
+    typedef long long LONGLONG; 
+#endif
+
+#elif defined( __BORLANDC__)  /* (WDP) for the free Borland compiler, in particular */
+    typedef __int64 LONGLONG;
+#else
+    typedef long long LONGLONG; 
+#endif
+
+#define LONGLONG_TYPE
+#endif  
+
+
+/* First prepare for the C compiler. */
+
+#ifndef ANSI_C_preprocessor /* i.e. user can override. */
+#ifdef __CF__KnR
+#define ANSI_C_preprocessor 0
+#else
+#ifdef __STDC__
+#define ANSI_C_preprocessor 1
+#else
+#define _cfleft             1
+#define _cfright 
+#define _cfleft_cfright     0
+#define ANSI_C_preprocessor _cfleft/**/_cfright
+#endif
+#endif
+#endif
+
+#if ANSI_C_preprocessor
+#define _0(A,B)   A##B
+#define  _(A,B)   _0(A,B)  /* see cat,xcat of K&R ANSI C p. 231 */
+#define _2(A,B)   A##B     /* K&R ANSI C p.230: .. identifier is not replaced */
+#define _3(A,B,C) _(A,_(B,C))
+#else                      /* if it turns up again during rescanning.         */
+#define  _(A,B)   A/**/B
+#define _2(A,B)   A/**/B
+#define _3(A,B,C) A/**/B/**/C
+#endif
+
+#if (defined(vax)&&defined(unix)) || (defined(__vax__)&&defined(__unix__))
+#define VAXUltrix
+#endif
+
+#include <stdio.h>     /* NULL [in all machines stdio.h]                      */
+#include <string.h>    /* strlen, memset, memcpy, memchr.                     */
+#if !( defined(VAXUltrix) || defined(sun) || (defined(apollo)&&!defined(__STDCPP__)) )
+#include <stdlib.h>    /* malloc,free                                         */
+#else
+#include <malloc.h>    /* Had to be removed for DomainOS h105 10.4 sys5.3 425t*/
+#ifdef apollo
+#define __CF__APOLLO67 /* __STDCPP__ is in Apollo 6.8 (i.e. ANSI) and onwards */
+#endif
+#endif
+
+#if !defined(__GNUC__) && !defined(__sun) && (defined(sun)||defined(VAXUltrix)||defined(lynx))
+#define __CF__KnR     /* Sun, LynxOS and VAX Ultrix cc only supports K&R.     */
+                      /* Manually define __CF__KnR for HP if desired/required.*/
+#endif                /*       i.e. We will generate Kernighan and Ritchie C. */
+/* Note that you may define __CF__KnR before #include cfortran.h, in order to
+generate K&R C instead of the default ANSI C. The differences are mainly in the
+function prototypes and declarations. All machines, except the Apollo, work
+with either style. The Apollo's argument promotion rules require ANSI or use of
+the obsolete std_$call which we have not implemented here. Hence on the Apollo,
+only C calling FORTRAN subroutines will work using K&R style.*/
+
+
+/* Remainder of cfortran.h depends on the Fortran compiler. */
+
+/* 11/29/2003 (KMCCARTY): add *INTEL_COMPILER symbols here */
+/* 04/05/2006 (KMCCARTY): add gFortran symbol here */
+#if defined(CLIPPERFortran) || defined(pgiFortran) || defined(__INTEL_COMPILER) || defined(INTEL_COMPILER) || defined(gFortran)
+#define f2cFortran
+#endif
+
+/* VAX/VMS does not let us \-split long #if lines. */ 
+/* Split #if into 2 because some HP-UX can't handle long #if */
+#if !(defined(NAGf90Fortran)||defined(f2cFortran)||defined(hpuxFortran)||defined(apolloFortran)||defined(sunFortran)||defined(IBMR2Fortran)||defined(CRAYFortran))
+#if !(defined(mipsFortran)||defined(DECFortran)||defined(vmsFortran)||defined(CONVEXFortran)||defined(PowerStationFortran)||defined(AbsoftUNIXFortran)||defined(AbsoftProFortran)||defined(SXFortran))
+/* If no Fortran compiler is given, we choose one for the machines we know.   */
+#if defined(lynx) || defined(VAXUltrix)
+#define f2cFortran    /* Lynx:      Only support f2c at the moment.
+                         VAXUltrix: f77 behaves like f2c.
+                           Support f2c or f77 with gcc, vcc with f2c. 
+                           f77 with vcc works, missing link magic for f77 I/O.*/
+#endif
+/* 04/13/00 DM (CFITSIO): Add these lines for NT */
+/*   with PowerStationFortran and and Visual C++ */
+#if defined(WIN32) && !defined(__CYGWIN__)
+#define PowerStationFortran   
+#define VISUAL_CPLUSPLUS
+#endif
+#if defined(g77Fortran)                        /* 11/03/97 PDW (CFITSIO) */
+#define f2cFortran
+#endif
+#if        defined(__CYGWIN__)                 /* 04/11/02 LEB (CFITSIO) */
+#define       f2cFortran 
+#endif
+#if        defined(__GNUC__) && defined(linux) /* 06/21/00 PDW (CFITSIO) */
+#define       f2cFortran 
+#endif
+#if defined(macintosh)                         /* 11/1999 (CFITSIO) */
+#define f2cFortran
+#endif
+#if defined(__APPLE__)                         /* 11/2002 (CFITSIO) */
+#define f2cFortran
+#endif
+#if defined(__hpux)             /* 921107: Use __hpux instead of __hp9000s300 */
+#define       hpuxFortran       /*         Should also allow hp9000s7/800 use.*/
+#endif
+#if       defined(apollo)
+#define           apolloFortran /* __CF__APOLLO67 also defines some behavior. */
+#endif
+#if          defined(sun) || defined(__sun) 
+#define              sunFortran
+#endif
+#if       defined(_IBMR2)
+#define            IBMR2Fortran
+#endif
+#if        defined(_CRAY)
+#define             CRAYFortran /*       _CRAYT3E also defines some behavior. */
+#endif
+#if        defined(_SX)
+#define               SXFortran
+#endif
+#if         defined(mips) || defined(__mips)
+#define             mipsFortran
+#endif
+#if          defined(vms) || defined(__vms)
+#define              vmsFortran
+#endif
+#if      defined(__alpha) && defined(__unix__)
+#define              DECFortran
+#endif
+#if   defined(__convex__)
+#define           CONVEXFortran
+#endif
+#if   defined(VISUAL_CPLUSPLUS)
+#define     PowerStationFortran
+#endif
+#endif /* ...Fortran */
+#endif /* ...Fortran */
+
+/* Split #if into 2 because some HP-UX can't handle long #if */
+#if !(defined(NAGf90Fortran)||defined(f2cFortran)||defined(hpuxFortran)||defined(apolloFortran)||defined(sunFortran)||defined(IBMR2Fortran)||defined(CRAYFortran))
+#if !(defined(mipsFortran)||defined(DECFortran)||defined(vmsFortran)||defined(CONVEXFortran)||defined(PowerStationFortran)||defined(AbsoftUNIXFortran)||defined(AbsoftProFortran)||defined(SXFortran))
+/* If your compiler barfs on ' #error', replace # with the trigraph for #     */
+ #error "cfortran.h:  Can't find your environment among:\
+    - GNU gcc (g77) on Linux.                                            \
+    - MIPS cc and f77 2.0. (e.g. Silicon Graphics, DECstations, ...)     \
+    - IBM AIX XL C and FORTRAN Compiler/6000 Version 01.01.0000.0000     \
+    - VAX   VMS CC 3.1 and FORTRAN 5.4.                                  \
+    - Alpha VMS DEC C 1.3 and DEC FORTRAN 6.0.                           \
+    - Alpha OSF DEC C and DEC Fortran for OSF/1 AXP Version 1.2          \
+    - Apollo DomainOS 10.2 (sys5.3) with f77 10.7 and cc 6.7.            \
+    - CRAY                                                               \
+    - NEC SX-4 SUPER-UX                                                  \
+    - CONVEX                                                             \
+    - Sun                                                                \
+    - PowerStation Fortran with Visual C++                               \
+    - HP9000s300/s700/s800 Latest test with: HP-UX A.08.07 A 9000/730    \
+    - LynxOS: cc or gcc with f2c.                                        \
+    - VAXUltrix: vcc,cc or gcc with f2c. gcc or cc with f77.             \
+    -            f77 with vcc works; but missing link magic for f77 I/O. \
+    -            NO fort. None of gcc, cc or vcc generate required names.\
+    - f2c/g77:   Use #define    f2cFortran, or cc -Df2cFortran           \
+    - gfortran:  Use #define    gFortran,   or cc -DgFortran             \
+                 (also necessary for g77 with -fno-f2c option)           \
+    - NAG f90: Use #define NAGf90Fortran, or cc -DNAGf90Fortran          \
+    - Absoft UNIX F77: Use #define AbsoftUNIXFortran or cc -DAbsoftUNIXFortran \
+    - Absoft Pro Fortran: Use #define AbsoftProFortran \
+    - Portland Group Fortran: Use #define pgiFortran \
+    - Intel Fortran: Use #define INTEL_COMPILER"
+/* Compiler must throw us out at this point! */
+#endif
+#endif
+
+
+#if defined(VAXC) && !defined(__VAXC)
+#define OLD_VAXC
+#pragma nostandard                       /* Prevent %CC-I-PARAMNOTUSED.       */
+#endif
+
+/* Throughout cfortran.h we use: UN = Uppercase Name.  LN = Lowercase Name.   */
+
+/* "extname" changed to "appendus" below (CFITSIO) */
+#if defined(f2cFortran) || defined(NAGf90Fortran) || defined(DECFortran) || defined(mipsFortran) || defined(apolloFortran) || defined(sunFortran) || defined(CONVEXFortran) || defined(SXFortran) || defined(appendus)
+#define CFC_(UN,LN)            _(LN,_)      /* Lowercase FORTRAN symbols.     */
+#define orig_fcallsc(UN,LN)    CFC_(UN,LN)
+#else 
+#if defined(CRAYFortran) || defined(PowerStationFortran) || defined(AbsoftProFortran)
+#ifdef _CRAY          /* (UN), not UN, circumvents CRAY preprocessor bug.     */
+#define CFC_(UN,LN)            (UN)         /* Uppercase FORTRAN symbols.     */
+#else                 /* At least VISUAL_CPLUSPLUS barfs on (UN), so need UN. */
+#define CFC_(UN,LN)            UN           /* Uppercase FORTRAN symbols.     */
+#endif
+#define orig_fcallsc(UN,LN)    CFC_(UN,LN)  /* CRAY insists on arg.'s here.   */
+#else  /* For following machines one may wish to change the fcallsc default.  */
+#define CF_SAME_NAMESPACE
+#ifdef vmsFortran
+#define CFC_(UN,LN)            LN           /* Either case FORTRAN symbols.   */
+     /* BUT we usually use UN for C macro to FORTRAN routines, so use LN here,*/
+     /* because VAX/VMS doesn't do recursive macros.                          */
+#define orig_fcallsc(UN,LN)    UN
+#else      /* HP-UX without +ppu or IBMR2 without -qextname. NOT reccomended. */
+#define CFC_(UN,LN)            LN           /* Lowercase FORTRAN symbols.     */
+#define orig_fcallsc(UN,LN)    CFC_(UN,LN)
+#endif /*  vmsFortran */
+#endif /* CRAYFortran PowerStationFortran */
+#endif /* ....Fortran */
+
+#define fcallsc(UN,LN)               orig_fcallsc(UN,LN)
+#define preface_fcallsc(P,p,UN,LN)   CFC_(_(P,UN),_(p,LN))
+#define  append_fcallsc(P,p,UN,LN)   CFC_(_(UN,P),_(LN,p))
+
+#define C_FUNCTION(UN,LN)            fcallsc(UN,LN)      
+#define FORTRAN_FUNCTION(UN,LN)      CFC_(UN,LN)
+
+#ifndef COMMON_BLOCK
+#ifndef CONVEXFortran
+#ifndef CLIPPERFortran
+#if     !(defined(AbsoftUNIXFortran)||defined(AbsoftProFortran))
+#define COMMON_BLOCK(UN,LN)          CFC_(UN,LN)
+#else
+#define COMMON_BLOCK(UN,LN)          _(_C,LN)
+#endif  /* AbsoftUNIXFortran or AbsoftProFortran */
+#else
+#define COMMON_BLOCK(UN,LN)          _(LN,__)
+#endif  /* CLIPPERFortran */
+#else
+#define COMMON_BLOCK(UN,LN)          _3(_,LN,_)
+#endif  /* CONVEXFortran */
+#endif  /* COMMON_BLOCK */
+
+#ifndef DOUBLE_PRECISION
+#if defined(CRAYFortran) && !defined(_CRAYT3E)
+#define DOUBLE_PRECISION long double
+#else
+#define DOUBLE_PRECISION double
+#endif
+#endif
+
+#ifndef FORTRAN_REAL
+#if defined(CRAYFortran) &&  defined(_CRAYT3E)
+#define FORTRAN_REAL double
+#else
+#define FORTRAN_REAL float
+#endif
+#endif
+
+#ifdef CRAYFortran
+#ifdef _CRAY
+#include <fortran.h>
+#else
+#include "fortran.h"  /* i.e. if crosscompiling assume user has file. */
+#endif
+#define FLOATVVVVVVV_cfPP (FORTRAN_REAL *)   /* Used for C calls FORTRAN.     */
+/* CRAY's double==float but CRAY says pointers to doubles and floats are diff.*/
+#define VOIDP  (void *)  /* When FORTRAN calls C, we don't know if C routine 
+                            arg.'s have been declared float *, or double *.   */
+#else
+#define FLOATVVVVVVV_cfPP
+#define VOIDP
+#endif
+
+#ifdef vmsFortran
+#if    defined(vms) || defined(__vms)
+#include <descrip.h>
+#else
+#include "descrip.h"  /* i.e. if crosscompiling assume user has file. */
+#endif
+#endif
+
+#ifdef sunFortran
+#if defined(sun) || defined(__sun)
+#include <math.h>     /* Sun's FLOATFUNCTIONTYPE, ASSIGNFLOAT, RETURNFLOAT.  */
+#else
+#include "math.h"     /* i.e. if crosscompiling assume user has file. */
+#endif
+/* At least starting with the default C compiler SC3.0.1 of SunOS 5.3,
+ * FLOATFUNCTIONTYPE, ASSIGNFLOAT, RETURNFLOAT are not required and not in
+ * <math.h>, since sun C no longer promotes C float return values to doubles.
+ * Therefore, only use them if defined.
+ * Even if gcc is being used, assume that it exhibits the Sun C compiler
+ * behavior in order to be able to use *.o from the Sun C compiler.
+ * i.e. If FLOATFUNCTIONTYPE, etc. are in math.h, they required by gcc.
+ */
+#endif
+
+#ifndef apolloFortran
+#define COMMON_BLOCK_DEF(DEFINITION, NAME) extern DEFINITION NAME
+#define CF_NULL_PROTO
+#else                                         /* HP doesn't understand #elif. */
+/* Without ANSI prototyping, Apollo promotes float functions to double.    */
+/* Note that VAX/VMS, IBM, Mips choke on 'type function(...);' prototypes. */
+#define CF_NULL_PROTO ...
+#ifndef __CF__APOLLO67
+#define COMMON_BLOCK_DEF(DEFINITION, NAME) \
+ DEFINITION NAME __attribute((__section(NAME)))
+#else
+#define COMMON_BLOCK_DEF(DEFINITION, NAME) \
+ DEFINITION NAME #attribute[section(NAME)]
+#endif
+#endif
+
+#ifdef __cplusplus
+#undef  CF_NULL_PROTO
+#define CF_NULL_PROTO  ...
+#endif
+
+
+#ifndef USE_NEW_DELETE
+#ifdef __cplusplus
+#define USE_NEW_DELETE 1
+#else
+#define USE_NEW_DELETE 0
+#endif
+#endif
+#if USE_NEW_DELETE
+#define _cf_malloc(N) new char[N]
+#define _cf_free(P)   delete[] P
+#else
+#define _cf_malloc(N) (char *)malloc(N)
+#define _cf_free(P)   free(P)
+#endif
+
+#ifdef mipsFortran
+#define CF_DECLARE_GETARG         int f77argc; char **f77argv
+#define CF_SET_GETARG(ARGC,ARGV)  f77argc = ARGC; f77argv = ARGV
+#else
+#define CF_DECLARE_GETARG
+#define CF_SET_GETARG(ARGC,ARGV)
+#endif
+
+#ifdef OLD_VAXC                          /* Allow %CC-I-PARAMNOTUSED.         */
+#pragma standard                         
+#endif
+
+#define AcfCOMMA ,
+#define AcfCOLON ;
+
+/*-------------------------------------------------------------------------*/
+
+/*               UTILITIES USED WITHIN CFORTRAN.H                          */
+
+#define _cfMIN(A,B) (A<B?A:B)
+
+/* 970211 - XIX.145:
+   firstindexlength  - better name is all_but_last_index_lengths
+   secondindexlength - better name is         last_index_length
+ */
+#define  firstindexlength(A) (sizeof(A[0])==1 ? 1 : (sizeof(A) / sizeof(A[0])) )
+#define secondindexlength(A) (sizeof(A[0])==1 ?      sizeof(A) : sizeof(A[0])  )
+
+/* Behavior of FORTRAN LOGICAL. All machines' LOGICAL is same size as C's int.
+Conversion is automatic except for arrays which require F2CLOGICALV/C2FLOGICALV.
+f2c, MIPS f77 [DECstation, SGI], VAX Ultrix f77,
+HP-UX f77                                        : as in C.
+VAX/VMS FORTRAN, VAX Ultrix fort,
+Absoft Unix Fortran, IBM RS/6000 xlf             : LS Bit = 0/1 = TRUE/FALSE.
+Apollo                                           : neg.   = TRUE, else FALSE. 
+[Apollo accepts -1 as TRUE for function values, but NOT all other neg. values.]
+[DECFortran for Ultrix RISC is also called f77 but is the same as VAX/VMS.]   
+[MIPS f77 treats .eqv./.neqv. as .eq./.ne. and hence requires LOGICAL_STRICT.]*/
+
+#if defined(NAGf90Fortran) || defined(f2cFortran) || defined(mipsFortran) || defined(PowerStationFortran) || defined(hpuxFortran800) || defined(AbsoftUNIXFortran) || defined(AbsoftProFortran) || defined(SXFortran)
+/* SX/PowerStationFortran have 0 and 1 defined, others are neither T nor F.   */
+/* hpuxFortran800 has 0 and 0x01000000 defined. Others are unknown.           */
+#define LOGICAL_STRICT      /* Other Fortran have .eqv./.neqv. == .eq./.ne.   */
+#endif
+
+#define C2FLOGICALV(A,I) \
+ do {int __i; for(__i=0;__i<I;__i++) A[__i]=C2FLOGICAL(A[__i]); } while (0)
+#define F2CLOGICALV(A,I) \
+ do {int __i; for(__i=0;__i<I;__i++) A[__i]=F2CLOGICAL(A[__i]); } while (0)
+
+#if defined(apolloFortran)
+#define C2FLOGICAL(L) ((L)?-1:(L)&~((unsigned)1<<sizeof(int)*8-1))
+#define F2CLOGICAL(L) ((L)<0?(L):0) 
+#else
+#if defined(CRAYFortran)
+#define C2FLOGICAL(L) _btol(L)
+#define F2CLOGICAL(L) _ltob(&(L))     /* Strangely _ltob() expects a pointer. */
+#else
+#if defined(IBMR2Fortran) || defined(vmsFortran) || defined(DECFortran) || defined(AbsoftUNIXFortran)
+/* How come no AbsoftProFortran ? */
+#define C2FLOGICAL(L) ((L)?(L)|1:(L)&~(int)1)
+#define F2CLOGICAL(L) ((L)&1?(L):0)
+#else
+#if defined(CONVEXFortran)
+#define C2FLOGICAL(L) ((L) ? ~0 : 0 )
+#define F2CLOGICAL(L) (L)
+#else   /* others evaluate LOGICALs as for C. */
+#define C2FLOGICAL(L) (L)
+#define F2CLOGICAL(L) (L)
+#ifndef LOGICAL_STRICT
+#undef  C2FLOGICALV
+#undef  F2CLOGICALV
+#define C2FLOGICALV(A,I)
+#define F2CLOGICALV(A,I)
+#endif  /* LOGICAL_STRICT                     */
+#endif  /* CONVEXFortran || All Others        */
+#endif  /* IBMR2Fortran vmsFortran DECFortran AbsoftUNIXFortran */
+#endif  /* CRAYFortran                        */
+#endif  /* apolloFortran                      */
+
+/* 970514 - In addition to CRAY, there may be other machines
+            for which LOGICAL_STRICT makes no sense. */
+#if defined(LOGICAL_STRICT) && !defined(CRAYFortran)
+/* Force C2FLOGICAL to generate only the values for either .TRUE. or .FALSE.
+   SX/PowerStationFortran only have 0 and 1 defined.
+   Elsewhere, only needed if you want to do:
+     logical lvariable
+     if (lvariable .eq.  .true.) then       ! (1)
+   instead of
+     if (lvariable .eqv. .true.) then       ! (2)
+   - (1) may not even be FORTRAN/77 and that Apollo's f77 and IBM's xlf
+     refuse to compile (1), so you are probably well advised to stay away from 
+     (1) and from LOGICAL_STRICT.
+   - You pay a (slight) performance penalty for using LOGICAL_STRICT. */
+#undef  C2FLOGICAL
+#ifdef hpuxFortran800
+#define C2FLOGICAL(L) ((L)?0x01000000:0)
+#else
+#if defined(apolloFortran) || defined(vmsFortran) || defined(DECFortran)
+#define C2FLOGICAL(L) ((L)?-1:0) /* These machines use -1/0 for .true./.false.*/
+#else
+#define C2FLOGICAL(L) ((L)? 1:0) /* All others     use +1/0 for .true./.false.*/
+#endif
+#endif
+#endif /* LOGICAL_STRICT */
+
+/* Convert a vector of C strings into FORTRAN strings. */
+#ifndef __CF__KnR
+static char *c2fstrv(char* cstr, char *fstr, int elem_len, int sizeofcstr)
+#else
+static char *c2fstrv(      cstr,       fstr,     elem_len,     sizeofcstr)
+                     char* cstr; char *fstr; int elem_len; int sizeofcstr;
+#endif
+{ int i,j;
+/* elem_len includes \0 for C strings. Fortran strings don't have term. \0.
+   Useful size of string must be the same in both languages. */
+for (i=0; i<sizeofcstr/elem_len; i++) {
+  for (j=1; j<elem_len && *cstr; j++) *fstr++ = *cstr++;
+  cstr += 1+elem_len-j;
+  for (; j<elem_len; j++) *fstr++ = ' ';
+} /* 95109 - Seems to be returning the original fstr. */
+return fstr-sizeofcstr+sizeofcstr/elem_len; }
+
+/* Convert a vector of FORTRAN strings into C strings. */
+#ifndef __CF__KnR
+static char *f2cstrv(char *fstr, char* cstr, int elem_len, int sizeofcstr)
+#else
+static char *f2cstrv(      fstr,       cstr,     elem_len,     sizeofcstr)
+                     char *fstr; char* cstr; int elem_len; int sizeofcstr; 
+#endif
+{ int i,j;
+/* elem_len includes \0 for C strings. Fortran strings don't have term. \0.
+   Useful size of string must be the same in both languages. */
+cstr += sizeofcstr;
+fstr += sizeofcstr - sizeofcstr/elem_len;
+for (i=0; i<sizeofcstr/elem_len; i++) {
+  *--cstr = '\0';
+  for (j=1; j<elem_len; j++) *--cstr = *--fstr;
+} return cstr; }
+
+/* kill the trailing char t's in string s. */
+#ifndef __CF__KnR
+static char *kill_trailing(char *s, char t)
+#else
+static char *kill_trailing(      s,      t) char *s; char t;
+#endif
+{char *e; 
+e = s + strlen(s);
+if (e>s) {                           /* Need this to handle NULL string.*/
+  while (e>s && *--e==t) {;}         /* Don't follow t's past beginning. */
+  e[*e==t?0:1] = '\0';               /* Handle s[0]=t correctly.       */
+} return s; }
+
+/* kill_trailingn(s,t,e) will kill the trailing t's in string s. e normally 
+points to the terminating '\0' of s, but may actually point to anywhere in s.
+s's new '\0' will be placed at e or earlier in order to remove any trailing t's.
+If e<s string s is left unchanged. */ 
+#ifndef __CF__KnR
+static char *kill_trailingn(char *s, char t, char *e)
+#else
+static char *kill_trailingn(      s,      t,       e) char *s; char t; char *e;
+#endif
+{ 
+if (e==s) *e = '\0';                 /* Kill the string makes sense here.*/
+else if (e>s) {                      /* Watch out for neg. length string.*/
+  while (e>s && *--e==t){;}          /* Don't follow t's past beginning. */
+  e[*e==t?0:1] = '\0';               /* Handle s[0]=t correctly.       */
+} return s; }
+
+/* Note the following assumes that any element which has t's to be chopped off,
+does indeed fill the entire element. */
+#ifndef __CF__KnR
+static char *vkill_trailing(char* cstr, int elem_len, int sizeofcstr, char t)
+#else
+static char *vkill_trailing(      cstr,     elem_len,     sizeofcstr,      t)
+                            char* cstr; int elem_len; int sizeofcstr; char t;
+#endif
+{ int i;
+for (i=0; i<sizeofcstr/elem_len; i++) /* elem_len includes \0 for C strings. */
+  kill_trailingn(cstr+elem_len*i,t,cstr+elem_len*(i+1)-1);
+return cstr; }
+
+#ifdef vmsFortran
+typedef struct dsc$descriptor_s fstring;
+#define DSC$DESCRIPTOR_A(DIMCT)  		                               \
+struct {                                                                       \
+  unsigned short dsc$w_length;	        unsigned char	 dsc$b_dtype;	       \
+  unsigned char	 dsc$b_class;	                 char	*dsc$a_pointer;	       \
+           char	 dsc$b_scale;	        unsigned char	 dsc$b_digits;         \
+  struct {                                                                     \
+    unsigned		       : 3;	  unsigned dsc$v_fl_binscale : 1;      \
+    unsigned dsc$v_fl_redim    : 1;       unsigned dsc$v_fl_column   : 1;      \
+    unsigned dsc$v_fl_coeff    : 1;       unsigned dsc$v_fl_bounds   : 1;      \
+  } dsc$b_aflags;	                                                       \
+  unsigned char	 dsc$b_dimct;	        unsigned long	 dsc$l_arsize;	       \
+           char	*dsc$a_a0;	                 long	 dsc$l_m [DIMCT];      \
+  struct {                                                                     \
+    long dsc$l_l;                         long dsc$l_u;                        \
+  } dsc$bounds [DIMCT];                                                        \
+}
+typedef DSC$DESCRIPTOR_A(1) fstringvector;
+/*typedef DSC$DESCRIPTOR_A(2) fstringarrarr;
+  typedef DSC$DESCRIPTOR_A(3) fstringarrarrarr;*/
+#define initfstr(F,C,ELEMNO,ELEMLEN)                                           \
+( (F).dsc$l_arsize=  ( (F).dsc$w_length                        =(ELEMLEN) )    \
+                    *( (F).dsc$l_m[0]=(F).dsc$bounds[0].dsc$l_u=(ELEMNO)  ),   \
+  (F).dsc$a_a0    =  ( (F).dsc$a_pointer=(C) ) - (F).dsc$w_length          ,(F))
+
+#endif      /* PDW: 2/10/98 (CFITSIO) -- Let VMS see NUM_ELEMS definitions */
+#define _NUM_ELEMS      -1
+#define _NUM_ELEM_ARG   -2
+#define NUM_ELEMS(A)    A,_NUM_ELEMS
+#define NUM_ELEM_ARG(B) *_2(A,B),_NUM_ELEM_ARG
+#define TERM_CHARS(A,B) A,B
+#ifndef __CF__KnR
+static int num_elem(char *strv, unsigned elem_len, int term_char, int num_term)
+#else
+static int num_elem(      strv,          elem_len,     term_char,     num_term)
+                    char *strv; unsigned elem_len; int term_char; int num_term;
+#endif
+/* elem_len is the number of characters in each element of strv, the FORTRAN
+vector of strings. The last element of the vector must begin with at least
+num_term term_char characters, so that this routine can determine how 
+many elements are in the vector. */
+{
+unsigned num,i;
+if (num_term == _NUM_ELEMS || num_term == _NUM_ELEM_ARG) 
+  return term_char;
+if (num_term <=0) num_term = (int)elem_len;
+for (num=0; ; num++) {
+  for (i=0; i<(unsigned)num_term && *strv==term_char; i++,strv++){;}
+  if (i==(unsigned)num_term) break;
+  else strv += elem_len-i;
+}
+if (0) {  /* to prevent not used warnings in gcc (added by ROOT) */
+   c2fstrv(0, 0, 0, 0); f2cstrv(0, 0, 0, 0); kill_trailing(0, 0);
+   vkill_trailing(0, 0, 0, 0); num_elem(0, 0, 0, 0);
+}
+return (int)num;
+}
+/* #endif removed 2/10/98 (CFITSIO) */
+
+/*-------------------------------------------------------------------------*/
+
+/*           UTILITIES FOR C TO USE STRINGS IN FORTRAN COMMON BLOCKS       */
+
+/* C string TO Fortran Common Block STRing. */
+/* DIM is the number of DIMensions of the array in terms of strings, not
+   characters. e.g. char a[12] has DIM = 0, char a[12][4] has DIM = 1, etc. */
+#define C2FCBSTR(CSTR,FSTR,DIM)                                                \
+ c2fstrv((char *)CSTR, (char *)FSTR, sizeof(FSTR)/cfelementsof(FSTR,DIM)+1,    \
+         sizeof(FSTR)+cfelementsof(FSTR,DIM))
+
+/* Fortran Common Block string TO C STRing. */
+#define FCB2CSTR(FSTR,CSTR,DIM)                                                \
+ vkill_trailing(f2cstrv((char *)FSTR, (char *)CSTR,                            \
+                        sizeof(FSTR)/cfelementsof(FSTR,DIM)+1,                 \
+                        sizeof(FSTR)+cfelementsof(FSTR,DIM)),                  \
+                sizeof(FSTR)/cfelementsof(FSTR,DIM)+1,                         \
+                sizeof(FSTR)+cfelementsof(FSTR,DIM), ' ')
+
+#define cfDEREFERENCE0
+#define cfDEREFERENCE1 *
+#define cfDEREFERENCE2 **
+#define cfDEREFERENCE3 ***
+#define cfDEREFERENCE4 ****
+#define cfDEREFERENCE5 *****
+#define cfelementsof(A,D) (sizeof(A)/sizeof(_(cfDEREFERENCE,D)(A)))
+
+/*-------------------------------------------------------------------------*/
+
+/*               UTILITIES FOR C TO CALL FORTRAN SUBROUTINES               */
+
+/* Define lookup tables for how to handle the various types of variables.  */
+
+#ifdef OLD_VAXC                                /* Prevent %CC-I-PARAMNOTUSED. */
+#pragma nostandard
+#endif
+
+#define ZTRINGV_NUM(I)       I
+#define ZTRINGV_ARGFP(I) (*(_2(A,I))) /* Undocumented. For PINT, etc. */
+#define ZTRINGV_ARGF(I) _2(A,I)
+#ifdef CFSUBASFUN
+#define ZTRINGV_ARGS(I) ZTRINGV_ARGF(I)
+#else
+#define ZTRINGV_ARGS(I) _2(B,I)
+#endif
+
+#define    PBYTE_cfVP(A,B) PINT_cfVP(A,B)
+#define  PDOUBLE_cfVP(A,B)
+#define   PFLOAT_cfVP(A,B)
+#ifdef ZTRINGV_ARGS_allows_Pvariables
+/* This allows Pvariables for ARGS. ARGF machinery is above ARGFP.
+ * B is not needed because the variable may be changed by the Fortran routine,
+ * but because B is the only way to access an arbitrary macro argument.       */
+#define     PINT_cfVP(A,B) int  B = (int)A;              /* For ZSTRINGV_ARGS */
+#else
+#define     PINT_cfVP(A,B)
+#endif
+#define PLOGICAL_cfVP(A,B) int *B;      /* Returning LOGICAL in FUNn and SUBn */
+#define    PLONG_cfVP(A,B) PINT_cfVP(A,B)
+#define   PSHORT_cfVP(A,B) PINT_cfVP(A,B)
+
+#define        VCF_INT_S(T,A,B) _(T,VVVVVVV_cfTYPE) B = A;
+#define        VCF_INT_F(T,A,B) _(T,_cfVCF)(A,B)
+/* _cfVCF table is directly mapped to _cfCCC table. */
+#define     BYTE_cfVCF(A,B)
+#define   DOUBLE_cfVCF(A,B)
+#if !defined(__CF__KnR)
+#define    FLOAT_cfVCF(A,B)
+#else
+#define    FLOAT_cfVCF(A,B) FORTRAN_REAL B = A;
+#endif
+#define      INT_cfVCF(A,B)
+#define  LOGICAL_cfVCF(A,B)
+#define     LONG_cfVCF(A,B)
+#define    SHORT_cfVCF(A,B)
+
+/* 980416
+   Cast (void (*)(CF_NULL_PROTO)) causes SunOS CC 4.2 occasionally to barf,
+   while the following equivalent typedef is fine.
+   For consistency use the typedef on all machines.
+ */
+typedef void (*cfCAST_FUNCTION)(CF_NULL_PROTO);
+
+#define VCF(TN,I)       _Icf4(4,V,TN,_(A,I),_(B,I),F)
+#define VVCF(TN,AI,BI)  _Icf4(4,V,TN,AI,BI,S)
+#define        INT_cfV(T,A,B,F) _(VCF_INT_,F)(T,A,B)
+#define       INTV_cfV(T,A,B,F)
+#define      INTVV_cfV(T,A,B,F)
+#define     INTVVV_cfV(T,A,B,F)
+#define    INTVVVV_cfV(T,A,B,F)
+#define   INTVVVVV_cfV(T,A,B,F)
+#define  INTVVVVVV_cfV(T,A,B,F)
+#define INTVVVVVVV_cfV(T,A,B,F)
+#define PINT_cfV(      T,A,B,F) _(T,_cfVP)(A,B)
+#define PVOID_cfV(     T,A,B,F)
+#if defined(apolloFortran) || defined(hpuxFortran800) || defined(AbsoftUNIXFortran) || defined(AbsoftProFortran)
+#define    ROUTINE_cfV(T,A,B,F) void (*B)(CF_NULL_PROTO) = (cfCAST_FUNCTION)A;
+#else
+#define    ROUTINE_cfV(T,A,B,F)
+#endif
+#define     SIMPLE_cfV(T,A,B,F)
+#ifdef vmsFortran
+#define     STRING_cfV(T,A,B,F) static struct {fstring f; unsigned clen;} B =  \
+                                       {{0,DSC$K_DTYPE_T,DSC$K_CLASS_S,NULL},0};
+#define    PSTRING_cfV(T,A,B,F) static fstring B={0,DSC$K_DTYPE_T,DSC$K_CLASS_S,NULL};
+#define    STRINGV_cfV(T,A,B,F) static fstringvector B =                       \
+  {sizeof(A),DSC$K_DTYPE_T,DSC$K_CLASS_A,NULL,0,0,{0,0,1,1,1},1,0,NULL,0,{1,0}};
+#define   PSTRINGV_cfV(T,A,B,F) static fstringvector B =                       \
+          {0,DSC$K_DTYPE_T,DSC$K_CLASS_A,NULL,0,0,{0,0,1,1,1},1,0,NULL,0,{1,0}};
+#else
+#define     STRING_cfV(T,A,B,F) struct {unsigned int clen, flen; char *nombre;} B;
+#define    STRINGV_cfV(T,A,B,F) struct {char *s, *fs; unsigned flen; char *nombre;} B;
+#define    PSTRING_cfV(T,A,B,F) int     B;
+#define   PSTRINGV_cfV(T,A,B,F) struct{char *fs; unsigned int sizeofA,flen;}B;
+#endif
+#define    ZTRINGV_cfV(T,A,B,F)  STRINGV_cfV(T,A,B,F)
+#define   PZTRINGV_cfV(T,A,B,F) PSTRINGV_cfV(T,A,B,F)
+
+/* Note that the actions of the A table were performed inside the AA table.
+   VAX Ultrix vcc, and HP-UX cc, didn't evaluate arguments to functions left to
+   right, so we had to split the original table into the current robust two. */
+#define ACF(NAME,TN,AI,I)      _(TN,_cfSTR)(4,A,NAME,I,AI,_(B,I),0)
+#define   DEFAULT_cfA(M,I,A,B)
+#define   LOGICAL_cfA(M,I,A,B) B=C2FLOGICAL(B);
+#define  PLOGICAL_cfA(M,I,A,B) A=C2FLOGICAL(A);
+#define    STRING_cfA(M,I,A,B)  STRING_cfC(M,I,A,B,sizeof(A))
+#define   PSTRING_cfA(M,I,A,B) PSTRING_cfC(M,I,A,B,sizeof(A))
+#ifdef vmsFortran
+#define  AATRINGV_cfA(    A,B, sA,filA,silA)                                   \
+ initfstr(B,_cf_malloc((sA)-(filA)),(filA),(silA)-1),                          \
+          c2fstrv(A,B.dsc$a_pointer,(silA),(sA));
+#define APATRINGV_cfA(    A,B, sA,filA,silA)                                   \
+ initfstr(B,A,(filA),(silA)-1),c2fstrv(A,A,(silA),(sA));
+#else
+#define  AATRINGV_cfA(    A,B, sA,filA,silA)                                   \
+     (B.s=_cf_malloc((sA)-(filA)),B.fs=c2fstrv(A,B.s,(B.flen=(silA)-1)+1,(sA)));
+#define APATRINGV_cfA(    A,B, sA,filA,silA)                                   \
+ B.fs=c2fstrv(A,A,(B.flen=(silA)-1)+1,B.sizeofA=(sA));
+#endif
+#define   STRINGV_cfA(M,I,A,B)                                                 \
+    AATRINGV_cfA((char *)A,B,sizeof(A),firstindexlength(A),secondindexlength(A))
+#define  PSTRINGV_cfA(M,I,A,B)                                                 \
+   APATRINGV_cfA((char *)A,B,sizeof(A),firstindexlength(A),secondindexlength(A))
+#define   ZTRINGV_cfA(M,I,A,B)  AATRINGV_cfA( (char *)A,B,                     \
+                    (_3(M,_ELEMS_,I))*(( _3(M,_ELEMLEN_,I))+1),                \
+                              (_3(M,_ELEMS_,I)),(_3(M,_ELEMLEN_,I))+1)
+#define  PZTRINGV_cfA(M,I,A,B) APATRINGV_cfA( (char *)A,B,                     \
+                    (_3(M,_ELEMS_,I))*(( _3(M,_ELEMLEN_,I))+1),                \
+                              (_3(M,_ELEMS_,I)),(_3(M,_ELEMLEN_,I))+1)
+
+#define    PBYTE_cfAAP(A,B) &A
+#define  PDOUBLE_cfAAP(A,B) &A
+#define   PFLOAT_cfAAP(A,B) FLOATVVVVVVV_cfPP &A
+#define     PINT_cfAAP(A,B) &A
+#define PLOGICAL_cfAAP(A,B) B= &A         /* B used to keep a common W table. */
+#define    PLONG_cfAAP(A,B) &A
+#define   PSHORT_cfAAP(A,B) &A
+
+#define AACF(TN,AI,I,C) _SEP_(TN,C,cfCOMMA) _Icf(3,AA,TN,AI,_(B,I))
+#define        INT_cfAA(T,A,B) &B
+#define       INTV_cfAA(T,A,B) _(T,VVVVVV_cfPP) A
+#define      INTVV_cfAA(T,A,B) _(T,VVVVV_cfPP)  A[0]
+#define     INTVVV_cfAA(T,A,B) _(T,VVVV_cfPP)   A[0][0]
+#define    INTVVVV_cfAA(T,A,B) _(T,VVV_cfPP)    A[0][0][0]
+#define   INTVVVVV_cfAA(T,A,B) _(T,VV_cfPP)     A[0][0][0][0]
+#define  INTVVVVVV_cfAA(T,A,B) _(T,V_cfPP)      A[0][0][0][0][0]
+#define INTVVVVVVV_cfAA(T,A,B) _(T,_cfPP)       A[0][0][0][0][0][0]
+#define       PINT_cfAA(T,A,B) _(T,_cfAAP)(A,B)
+#define      PVOID_cfAA(T,A,B) (void *) A
+#if defined(apolloFortran) || defined(hpuxFortran800) || defined(AbsoftUNIXFortran)
+#define    ROUTINE_cfAA(T,A,B) &B
+#else
+#define    ROUTINE_cfAA(T,A,B) (cfCAST_FUNCTION)A
+#endif
+#define     STRING_cfAA(T,A,B)  STRING_cfCC(T,A,B)
+#define    PSTRING_cfAA(T,A,B) PSTRING_cfCC(T,A,B)
+#ifdef vmsFortran
+#define    STRINGV_cfAA(T,A,B) &B
+#else
+#ifdef CRAYFortran
+#define    STRINGV_cfAA(T,A,B) _cptofcd(B.fs,B.flen)
+#else
+#define    STRINGV_cfAA(T,A,B) B.fs
+#endif
+#endif
+#define   PSTRINGV_cfAA(T,A,B) STRINGV_cfAA(T,A,B)
+#define    ZTRINGV_cfAA(T,A,B) STRINGV_cfAA(T,A,B)
+#define   PZTRINGV_cfAA(T,A,B) STRINGV_cfAA(T,A,B)
+
+#if defined(vmsFortran) || defined(CRAYFortran)
+#define JCF(TN,I)
+#define KCF(TN,I)
+#else
+#define JCF(TN,I)    _(TN,_cfSTR)(1,J,_(B,I), 0,0,0,0)
+#if defined(AbsoftUNIXFortran)
+#define  DEFAULT_cfJ(B) ,0
+#else
+#define  DEFAULT_cfJ(B)
+#endif
+#define  LOGICAL_cfJ(B) DEFAULT_cfJ(B)
+#define PLOGICAL_cfJ(B) DEFAULT_cfJ(B)
+#define   STRING_cfJ(B) ,B.flen
+#define  PSTRING_cfJ(B) ,B
+#define  STRINGV_cfJ(B) STRING_cfJ(B)
+#define PSTRINGV_cfJ(B) STRING_cfJ(B)
+#define  ZTRINGV_cfJ(B) STRING_cfJ(B)
+#define PZTRINGV_cfJ(B) STRING_cfJ(B)
+
+/* KCF is identical to DCF, except that KCF ZTRING is not empty. */
+#define KCF(TN,I)    _(TN,_cfSTR)(1,KK,_(B,I), 0,0,0,0)
+#if defined(AbsoftUNIXFortran)
+#define  DEFAULT_cfKK(B) , unsigned B
+#else
+#define  DEFAULT_cfKK(B)
+#endif
+#define  LOGICAL_cfKK(B) DEFAULT_cfKK(B)
+#define PLOGICAL_cfKK(B) DEFAULT_cfKK(B)
+#define   STRING_cfKK(B) , unsigned B
+#define  PSTRING_cfKK(B) STRING_cfKK(B)
+#define  STRINGV_cfKK(B) STRING_cfKK(B)
+#define PSTRINGV_cfKK(B) STRING_cfKK(B)
+#define  ZTRINGV_cfKK(B) STRING_cfKK(B)
+#define PZTRINGV_cfKK(B) STRING_cfKK(B)
+#endif
+
+#define WCF(TN,AN,I)      _(TN,_cfSTR)(2,W,AN,_(B,I), 0,0,0)
+#define  DEFAULT_cfW(A,B)
+#define  LOGICAL_cfW(A,B)
+#define PLOGICAL_cfW(A,B) *B=F2CLOGICAL(*B);
+#define   STRING_cfW(A,B) (B.nombre=A,B.nombre[B.clen]!='\0'?B.nombre[B.clen]='\0':0); /* A?="constnt"*/
+#define  PSTRING_cfW(A,B) kill_trailing(A,' ');
+#ifdef vmsFortran
+#define  STRINGV_cfW(A,B) _cf_free(B.dsc$a_pointer);
+#define PSTRINGV_cfW(A,B)                                                      \
+  vkill_trailing(f2cstrv((char*)A, (char*)A,                                   \
+                           B.dsc$w_length+1, B.dsc$l_arsize+B.dsc$l_m[0]),     \
+                   B.dsc$w_length+1, B.dsc$l_arsize+B.dsc$l_m[0], ' ');
+#else
+#define  STRINGV_cfW(A,B) _cf_free(B.s);
+#define PSTRINGV_cfW(A,B) vkill_trailing(                                      \
+         f2cstrv((char*)A,(char*)A,B.flen+1,B.sizeofA), B.flen+1,B.sizeofA,' ');
+#endif
+#define  ZTRINGV_cfW(A,B)      STRINGV_cfW(A,B)
+#define PZTRINGV_cfW(A,B)     PSTRINGV_cfW(A,B)
+
+#define   NCF(TN,I,C)       _SEP_(TN,C,cfCOMMA) _Icf(2,N,TN,_(A,I),0) 
+#define  NNCF(TN,I,C)        UUCF(TN,I,C)
+#define NNNCF(TN,I,C)       _SEP_(TN,C,cfCOLON) _Icf(2,N,TN,_(A,I),0) 
+#define        INT_cfN(T,A) _(T,VVVVVVV_cfTYPE) * A
+#define       INTV_cfN(T,A) _(T,VVVVVV_cfTYPE)  * A
+#define      INTVV_cfN(T,A) _(T,VVVVV_cfTYPE)   * A
+#define     INTVVV_cfN(T,A) _(T,VVVV_cfTYPE)    * A
+#define    INTVVVV_cfN(T,A) _(T,VVV_cfTYPE)     * A
+#define   INTVVVVV_cfN(T,A) _(T,VV_cfTYPE)      * A
+#define  INTVVVVVV_cfN(T,A) _(T,V_cfTYPE)       * A
+#define INTVVVVVVV_cfN(T,A) _(T,_cfTYPE)        * A
+#define       PINT_cfN(T,A) _(T,_cfTYPE)        * A
+#define      PVOID_cfN(T,A) void *                A
+#if defined(apolloFortran) || defined(hpuxFortran800) || defined(AbsoftUNIXFortran)
+#define    ROUTINE_cfN(T,A) void (**A)(CF_NULL_PROTO)
+#else
+#define    ROUTINE_cfN(T,A) void ( *A)(CF_NULL_PROTO)
+#endif
+#ifdef vmsFortran
+#define     STRING_cfN(T,A) fstring *             A
+#define    STRINGV_cfN(T,A) fstringvector *       A
+#else
+#ifdef CRAYFortran
+#define     STRING_cfN(T,A) _fcd                  A
+#define    STRINGV_cfN(T,A) _fcd                  A
+#else
+#define     STRING_cfN(T,A) char *                A
+#define    STRINGV_cfN(T,A) char *                A
+#endif
+#endif
+#define    PSTRING_cfN(T,A)   STRING_cfN(T,A) /* CRAY insists on arg.'s here. */
+#define   PNSTRING_cfN(T,A)   STRING_cfN(T,A) /* CRAY insists on arg.'s here. */
+#define   PPSTRING_cfN(T,A)   STRING_cfN(T,A) /* CRAY insists on arg.'s here. */
+#define   PSTRINGV_cfN(T,A)  STRINGV_cfN(T,A)
+#define    ZTRINGV_cfN(T,A)  STRINGV_cfN(T,A)
+#define   PZTRINGV_cfN(T,A) PSTRINGV_cfN(T,A)
+
+
+/* Apollo 6.7, CRAY, old Sun, VAX/Ultrix vcc/cc and new ultrix
+   can't hack more than 31 arg's.
+   e.g. ultrix >= 4.3 gives message:
+       zow35> cc -c -DDECFortran cfortest.c
+       cfe: Fatal: Out of memory: cfortest.c
+       zow35>
+   Old __hpux had the problem, but new 'HP-UX A.09.03 A 9000/735' is fine
+   if using -Aa, otherwise we have a problem.
+ */
+#ifndef MAX_PREPRO_ARGS
+#if !defined(__GNUC__) && (defined(VAXUltrix) || defined(__CF__APOLLO67) || (defined(sun)&&!defined(__sun)) || defined(_CRAY) || defined(__ultrix__) || (defined(__hpux)&&defined(__CF__KnR)))
+#define MAX_PREPRO_ARGS 31
+#else
+#define MAX_PREPRO_ARGS 99
+#endif
+#endif
+
+#if defined(AbsoftUNIXFortran) || defined(AbsoftProFortran)
+/* In addition to explicit Absoft stuff, only Absoft requires:
+   - DEFAULT coming from _cfSTR.
+     DEFAULT could have been called e.g. INT, but keep it for clarity.
+   - M term in CFARGT14 and CFARGT14FS.
+ */
+#define ABSOFT_cf1(T0) _(T0,_cfSTR)(0,ABSOFT1,0,0,0,0,0)
+#define ABSOFT_cf2(T0) _(T0,_cfSTR)(0,ABSOFT2,0,0,0,0,0)
+#define ABSOFT_cf3(T0) _(T0,_cfSTR)(0,ABSOFT3,0,0,0,0,0)
+#define DEFAULT_cfABSOFT1
+#define LOGICAL_cfABSOFT1
+#define  STRING_cfABSOFT1 ,MAX_LEN_FORTRAN_FUNCTION_STRING
+#define DEFAULT_cfABSOFT2
+#define LOGICAL_cfABSOFT2
+#define  STRING_cfABSOFT2 ,unsigned D0
+#define DEFAULT_cfABSOFT3
+#define LOGICAL_cfABSOFT3
+#define  STRING_cfABSOFT3 ,D0
+#else
+#define ABSOFT_cf1(T0)
+#define ABSOFT_cf2(T0)
+#define ABSOFT_cf3(T0)
+#endif
+
+/* _Z introduced to cicumvent IBM and HP silly preprocessor warning.
+   e.g. "Macro CFARGT14 invoked with a null argument."
+ */
+#define _Z
+
+#define  CFARGT14S(S,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)                \
+ S(T1,1)   S(T2,2)   S(T3,3)    S(T4,4)    S(T5,5)    S(T6,6)    S(T7,7)       \
+ S(T8,8)   S(T9,9)   S(TA,10)   S(TB,11)   S(TC,12)   S(TD,13)   S(TE,14)
+#define  CFARGT27S(S,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR) \
+ S(T1,1)   S(T2,2)   S(T3,3)    S(T4,4)    S(T5,5)    S(T6,6)    S(T7,7)       \
+ S(T8,8)   S(T9,9)   S(TA,10)   S(TB,11)   S(TC,12)   S(TD,13)   S(TE,14)      \
+ S(TF,15)  S(TG,16)  S(TH,17)   S(TI,18)   S(TJ,19)   S(TK,20)   S(TL,21)      \
+ S(TM,22)  S(TN,23)  S(TO,24)   S(TP,25)   S(TQ,26)   S(TR,27)
+
+#define  CFARGT14FS(F,S,M,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)           \
+ F(T1,1,0) F(T2,2,1) F(T3,3,1)  F(T4,4,1)  F(T5,5,1)  F(T6,6,1)  F(T7,7,1)     \
+ F(T8,8,1) F(T9,9,1) F(TA,10,1) F(TB,11,1) F(TC,12,1) F(TD,13,1) F(TE,14,1)    \
+ M       CFARGT14S(S,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)
+#define  CFARGT27FS(F,S,M,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR) \
+ F(T1,1,0)  F(T2,2,1)  F(T3,3,1)  F(T4,4,1)  F(T5,5,1)  F(T6,6,1)  F(T7,7,1)   \
+ F(T8,8,1)  F(T9,9,1)  F(TA,10,1) F(TB,11,1) F(TC,12,1) F(TD,13,1) F(TE,14,1)  \
+ F(TF,15,1) F(TG,16,1) F(TH,17,1) F(TI,18,1) F(TJ,19,1) F(TK,20,1) F(TL,21,1)  \
+ F(TM,22,1) F(TN,23,1) F(TO,24,1) F(TP,25,1) F(TQ,26,1) F(TR,27,1)             \
+ M       CFARGT27S(S,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR)
+
+#if !(defined(PowerStationFortran)||defined(hpuxFortran800))
+/*  Old CFARGT14 -> CFARGT14FS as seen below, for Absoft cross-compile yields:
+      SunOS> cc -c -Xa -DAbsoftUNIXFortran c.c
+      "c.c", line 406: warning: argument mismatch
+    Haven't checked if this is ANSI C or a SunOS bug. SunOS -Xs works ok.
+    Behavior is most clearly seen in example:
+      #define A 1 , 2
+      #define  C(X,Y,Z) x=X. y=Y. z=Z.
+      #define  D(X,Y,Z) C(X,Y,Z)
+      D(x,A,z)
+    Output from preprocessor is: x = x . y = 1 . z = 2 .
+ #define CFARGT14(F,S,M,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE) \
+       CFARGT14FS(F,S,M,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)
+*/
+#define  CFARGT14(F,S,M,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)             \
+ F(T1,1,0) F(T2,2,1) F(T3,3,1)  F(T4,4,1)  F(T5,5,1)  F(T6,6,1)  F(T7,7,1)     \
+ F(T8,8,1) F(T9,9,1) F(TA,10,1) F(TB,11,1) F(TC,12,1) F(TD,13,1) F(TE,14,1)    \
+ M       CFARGT14S(S,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)
+#define  CFARGT27(F,S,M,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR) \
+ F(T1,1,0)  F(T2,2,1)  F(T3,3,1)  F(T4,4,1)  F(T5,5,1)  F(T6,6,1)  F(T7,7,1)   \
+ F(T8,8,1)  F(T9,9,1)  F(TA,10,1) F(TB,11,1) F(TC,12,1) F(TD,13,1) F(TE,14,1)  \
+ F(TF,15,1) F(TG,16,1) F(TH,17,1) F(TI,18,1) F(TJ,19,1) F(TK,20,1) F(TL,21,1)  \
+ F(TM,22,1) F(TN,23,1) F(TO,24,1) F(TP,25,1) F(TQ,26,1) F(TR,27,1)             \
+ M       CFARGT27S(S,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR)
+
+#define  CFARGT20(F,S,M,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK) \
+ F(T1,1,0)  F(T2,2,1)  F(T3,3,1)  F(T4,4,1)  F(T5,5,1)  F(T6,6,1)  F(T7,7,1)   \
+ F(T8,8,1)  F(T9,9,1)  F(TA,10,1) F(TB,11,1) F(TC,12,1) F(TD,13,1) F(TE,14,1)  \
+ F(TF,15,1) F(TG,16,1) F(TH,17,1) F(TI,18,1) F(TJ,19,1) F(TK,20,1)             \
+ S(T1,1)    S(T2,2)    S(T3,3)    S(T4,4)    S(T5,5)    S(T6,6)    S(T7,7)     \
+ S(T8,8)    S(T9,9)    S(TA,10)   S(TB,11)   S(TC,12)   S(TD,13)   S(TE,14)    \
+ S(TF,15)   S(TG,16)   S(TH,17)   S(TI,18)   S(TJ,19)   S(TK,20)
+#define CFARGTA14(F,S,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE) \
+ F(T1,A1,1,0)  F(T2,A2,2,1)  F(T3,A3,3,1) F(T4,A4,4,1)  F(T5,A5,5,1)  F(T6,A6,6,1)  \
+ F(T7,A7,7,1)  F(T8,A8,8,1)  F(T9,A9,9,1) F(TA,AA,10,1) F(TB,AB,11,1) F(TC,AC,12,1) \
+ F(TD,AD,13,1) F(TE,AE,14,1) S(T1,1)      S(T2,2)       S(T3,3)       S(T4,4)       \
+ S(T5,5)       S(T6,6)       S(T7,7)      S(T8,8)       S(T9,9)       S(TA,10)      \
+ S(TB,11)      S(TC,12)      S(TD,13)     S(TE,14)
+#if MAX_PREPRO_ARGS>31
+#define CFARGTA20(F,S,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK) \
+ F(T1,A1,1,0)  F(T2,A2,2,1)  F(T3,A3,3,1)  F(T4,A4,4,1)  F(T5,A5,5,1)  F(T6,A6,6,1)  \
+ F(T7,A7,7,1)  F(T8,A8,8,1)  F(T9,A9,9,1)  F(TA,AA,10,1) F(TB,AB,11,1) F(TC,AC,12,1) \
+ F(TD,AD,13,1) F(TE,AE,14,1) F(TF,AF,15,1) F(TG,AG,16,1) F(TH,AH,17,1) F(TI,AI,18,1) \
+ F(TJ,AJ,19,1) F(TK,AK,20,1) S(T1,1)       S(T2,2)       S(T3,3)       S(T4,4)       \
+ S(T5,5)       S(T6,6)       S(T7,7)       S(T8,8)       S(T9,9)       S(TA,10)      \
+ S(TB,11)      S(TC,12)      S(TD,13)      S(TE,14)      S(TF,15)      S(TG,16)      \
+ S(TH,17)      S(TI,18)      S(TJ,19)      S(TK,20)
+#define CFARGTA27(F,S,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL,AM,AN,AO,AP,AQ,AR) \
+ F(T1,A1,1,0)  F(T2,A2,2,1)  F(T3,A3,3,1)  F(T4,A4,4,1)  F(T5,A5,5,1)  F(T6,A6,6,1)  \
+ F(T7,A7,7,1)  F(T8,A8,8,1)  F(T9,A9,9,1)  F(TA,AA,10,1) F(TB,AB,11,1) F(TC,AC,12,1) \
+ F(TD,AD,13,1) F(TE,AE,14,1) F(TF,AF,15,1) F(TG,AG,16,1) F(TH,AH,17,1) F(TI,AI,18,1) \
+ F(TJ,AJ,19,1) F(TK,AK,20,1) F(TL,AL,21,1) F(TM,AM,22,1) F(TN,AN,23,1) F(TO,AO,24,1) \
+ F(TP,AP,25,1) F(TQ,AQ,26,1) F(TR,AR,27,1) S(T1,1)       S(T2,2)       S(T3,3)       \
+ S(T4,4)       S(T5,5)       S(T6,6)       S(T7,7)       S(T8,8)       S(T9,9)       \
+ S(TA,10)      S(TB,11)      S(TC,12)      S(TD,13)      S(TE,14)      S(TF,15)      \
+ S(TG,16)      S(TH,17)      S(TI,18)      S(TJ,19)      S(TK,20)      S(TL,21)      \
+ S(TM,22)      S(TN,23)      S(TO,24)      S(TP,25)      S(TQ,26)      S(TR,27)
+#endif
+#else
+#define  CFARGT14(F,S,M,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)             \
+ F(T1,1,0) S(T1,1) F(T2,2,1)  S(T2,2)  F(T3,3,1)  S(T3,3)  F(T4,4,1)  S(T4,4)  \
+ F(T5,5,1) S(T5,5) F(T6,6,1)  S(T6,6)  F(T7,7,1)  S(T7,7)  F(T8,8,1)  S(T8,8)  \
+ F(T9,9,1) S(T9,9) F(TA,10,1) S(TA,10) F(TB,11,1) S(TB,11) F(TC,12,1) S(TC,12) \
+ F(TD,13,1) S(TD,13) F(TE,14,1) S(TE,14)
+#define  CFARGT27(F,S,M,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR) \
+ F(T1,1,0)  S(T1,1)  F(T2,2,1)  S(T2,2)  F(T3,3,1)  S(T3,3)  F(T4,4,1)  S(T4,4)  \
+ F(T5,5,1)  S(T5,5)  F(T6,6,1)  S(T6,6)  F(T7,7,1)  S(T7,7)  F(T8,8,1)  S(T8,8)  \
+ F(T9,9,1)  S(T9,9)  F(TA,10,1) S(TA,10) F(TB,11,1) S(TB,11) F(TC,12,1) S(TC,12) \
+ F(TD,13,1) S(TD,13) F(TE,14,1) S(TE,14) F(TF,15,1) S(TF,15) F(TG,16,1) S(TG,16) \
+ F(TH,17,1) S(TH,17) F(TI,18,1) S(TI,18) F(TJ,19,1) S(TJ,19) F(TK,20,1) S(TK,20) \
+ F(TL,21,1) S(TL,21) F(TM,22,1) S(TM,22) F(TN,23,1) S(TN,23) F(TO,24,1) S(TO,24) \
+ F(TP,25,1) S(TP,25) F(TQ,26,1) S(TQ,26) F(TR,27,1) S(TR,27)
+
+#define  CFARGT20(F,S,M,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK) \
+ F(T1,1,0)  S(T1,1)  F(T2,2,1)  S(T2,2)  F(T3,3,1)  S(T3,3)  F(T4,4,1)  S(T4,4)  \
+ F(T5,5,1)  S(T5,5)  F(T6,6,1)  S(T6,6)  F(T7,7,1)  S(T7,7)  F(T8,8,1)  S(T8,8)  \
+ F(T9,9,1)  S(T9,9)  F(TA,10,1) S(TA,10) F(TB,11,1) S(TB,11) F(TC,12,1) S(TC,12) \
+ F(TD,13,1) S(TD,13) F(TE,14,1) S(TE,14) F(TF,15,1) S(TF,15) F(TG,16,1) S(TG,16) \
+ F(TH,17,1) S(TH,17) F(TI,18,1) S(TI,18) F(TJ,19,1) S(TJ,19) F(TK,20,1) S(TK,20)
+#define CFARGTA14(F,S,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE) \
+ F(T1,A1,1,0)  S(T1,1)  F(T2,A2,2,1)  S(T2,2)  F(T3,A3,3,1)  S(T3,3)           \
+ F(T4,A4,4,1)  S(T4,4)  F(T5,A5,5,1)  S(T5,5)  F(T6,A6,6,1)  S(T6,6)           \
+ F(T7,A7,7,1)  S(T7,7)  F(T8,A8,8,1)  S(T8,8)  F(T9,A9,9,1)  S(T9,9)           \
+ F(TA,AA,10,1) S(TA,10) F(TB,AB,11,1) S(TB,11) F(TC,AC,12,1) S(TC,12)          \
+ F(TD,AD,13,1) S(TD,13) F(TE,AE,14,1) S(TE,14)
+#if MAX_PREPRO_ARGS>31
+#define CFARGTA20(F,S,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK) \
+ F(T1,A1,1,0)  S(T1,1)  F(T2,A2,2,1)  S(T2,2)  F(T3,A3,3,1)  S(T3,3)           \
+ F(T4,A4,4,1)  S(T4,4)  F(T5,A5,5,1)  S(T5,5)  F(T6,A6,6,1)  S(T6,6)           \
+ F(T7,A7,7,1)  S(T7,7)  F(T8,A8,8,1)  S(T8,8)  F(T9,A9,9,1)  S(T9,9)           \
+ F(TA,AA,10,1) S(TA,10) F(TB,AB,11,1) S(TB,11) F(TC,AC,12,1) S(TC,12)          \
+ F(TD,AD,13,1) S(TD,13) F(TE,AE,14,1) S(TE,14) F(TF,AF,15,1) S(TF,15)          \
+ F(TG,AG,16,1) S(TG,16) F(TH,AH,17,1) S(TH,17) F(TI,AI,18,1) S(TI,18)          \
+ F(TJ,AJ,19,1) S(TJ,19) F(TK,AK,20,1) S(TK,20)                
+#define CFARGTA27(F,S,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL,AM,AN,AO,AP,AQ,AR) \
+ F(T1,A1,1,0)  S(T1,1)  F(T2,A2,2,1)  S(T2,2)  F(T3,A3,3,1)  S(T3,3)           \
+ F(T4,A4,4,1)  S(T4,4)  F(T5,A5,5,1)  S(T5,5)  F(T6,A6,6,1)  S(T6,6)           \
+ F(T7,A7,7,1)  S(T7,7)  F(T8,A8,8,1)  S(T8,8)  F(T9,A9,9,1)  S(T9,9)           \
+ F(TA,AA,10,1) S(TA,10) F(TB,AB,11,1) S(TB,11) F(TC,AC,12,1) S(TC,12)          \
+ F(TD,AD,13,1) S(TD,13) F(TE,AE,14,1) S(TE,14) F(TF,AF,15,1) S(TF,15)          \
+ F(TG,AG,16,1) S(TG,16) F(TH,AH,17,1) S(TH,17) F(TI,AI,18,1) S(TI,18)          \
+ F(TJ,AJ,19,1) S(TJ,19) F(TK,AK,20,1) S(TK,20) F(TL,AL,21,1) S(TL,21)          \
+ F(TM,AM,22,1) S(TM,22) F(TN,AN,23,1) S(TN,23) F(TO,AO,24,1) S(TO,24)          \
+ F(TP,AP,25,1) S(TP,25) F(TQ,AQ,26,1) S(TQ,26) F(TR,AR,27,1) S(TR,27)
+#endif
+#endif
+
+
+#define PROTOCCALLSFSUB1( UN,LN,T1) \
+        PROTOCCALLSFSUB14(UN,LN,T1,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0)
+#define PROTOCCALLSFSUB2( UN,LN,T1,T2) \
+        PROTOCCALLSFSUB14(UN,LN,T1,T2,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0)
+#define PROTOCCALLSFSUB3( UN,LN,T1,T2,T3) \
+        PROTOCCALLSFSUB14(UN,LN,T1,T2,T3,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0)
+#define PROTOCCALLSFSUB4( UN,LN,T1,T2,T3,T4) \
+        PROTOCCALLSFSUB14(UN,LN,T1,T2,T3,T4,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0)
+#define PROTOCCALLSFSUB5( UN,LN,T1,T2,T3,T4,T5) \
+        PROTOCCALLSFSUB14(UN,LN,T1,T2,T3,T4,T5,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0)
+#define PROTOCCALLSFSUB6( UN,LN,T1,T2,T3,T4,T5,T6) \
+        PROTOCCALLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0)
+#define PROTOCCALLSFSUB7( UN,LN,T1,T2,T3,T4,T5,T6,T7) \
+        PROTOCCALLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,T7,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0)
+#define PROTOCCALLSFSUB8( UN,LN,T1,T2,T3,T4,T5,T6,T7,T8) \
+        PROTOCCALLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0)
+#define PROTOCCALLSFSUB9( UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9) \
+        PROTOCCALLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,CF_0,CF_0,CF_0,CF_0,CF_0)
+#define PROTOCCALLSFSUB10(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA) \
+        PROTOCCALLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,CF_0,CF_0,CF_0,CF_0)
+#define PROTOCCALLSFSUB11(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB) \
+        PROTOCCALLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,CF_0,CF_0,CF_0)
+#define PROTOCCALLSFSUB12(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC) \
+        PROTOCCALLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,CF_0,CF_0)
+#define PROTOCCALLSFSUB13(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD) \
+        PROTOCCALLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,CF_0)
+
+
+#define PROTOCCALLSFSUB15(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF) \
+        PROTOCCALLSFSUB20(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,CF_0,CF_0,CF_0,CF_0,CF_0)
+#define PROTOCCALLSFSUB16(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG) \
+        PROTOCCALLSFSUB20(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,CF_0,CF_0,CF_0,CF_0)
+#define PROTOCCALLSFSUB17(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH) \
+        PROTOCCALLSFSUB20(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,CF_0,CF_0,CF_0)
+#define PROTOCCALLSFSUB18(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI) \
+        PROTOCCALLSFSUB20(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,CF_0,CF_0)
+#define PROTOCCALLSFSUB19(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ) \
+        PROTOCCALLSFSUB20(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,CF_0)
+
+#define PROTOCCALLSFSUB21(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL) \
+        PROTOCCALLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0)
+#define PROTOCCALLSFSUB22(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM) \
+        PROTOCCALLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,CF_0,CF_0,CF_0,CF_0,CF_0)
+#define PROTOCCALLSFSUB23(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN) \
+        PROTOCCALLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,CF_0,CF_0,CF_0,CF_0)
+#define PROTOCCALLSFSUB24(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO) \
+        PROTOCCALLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,CF_0,CF_0,CF_0)
+#define PROTOCCALLSFSUB25(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP) \
+        PROTOCCALLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,CF_0,CF_0)
+#define PROTOCCALLSFSUB26(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ) \
+        PROTOCCALLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,CF_0)
+
+
+#ifndef FCALLSC_QUALIFIER
+#ifdef VISUAL_CPLUSPLUS
+#define FCALLSC_QUALIFIER __stdcall
+#else
+#define FCALLSC_QUALIFIER
+#endif
+#endif
+
+#ifdef __cplusplus
+#define CFextern extern "C"
+#else
+#define CFextern extern
+#endif
+
+
+#ifdef CFSUBASFUN
+#define PROTOCCALLSFSUB0(UN,LN) \
+   PROTOCCALLSFFUN0( VOID,UN,LN)
+#define PROTOCCALLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE) \
+   PROTOCCALLSFFUN14(VOID,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)
+#define PROTOCCALLSFSUB20(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK)\
+   PROTOCCALLSFFUN20(VOID,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK)
+#define PROTOCCALLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR)\
+   PROTOCCALLSFFUN27(VOID,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR)
+#else
+/* Note: Prevent compiler warnings, null #define PROTOCCALLSFSUB14/20 after 
+   #include-ing cfortran.h if calling the FORTRAN wrapper within the same 
+   source code where the wrapper is created. */
+#define PROTOCCALLSFSUB0(UN,LN)     _(VOID,_cfPU)(CFC_(UN,LN))();
+#ifndef __CF__KnR
+#define PROTOCCALLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE) \
+ _(VOID,_cfPU)(CFC_(UN,LN))( CFARGT14(NCF,KCF,_Z,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE) );
+#define PROTOCCALLSFSUB20(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK)\
+ _(VOID,_cfPU)(CFC_(UN,LN))( CFARGT20(NCF,KCF,_Z,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK) );
+#define PROTOCCALLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR)\
+ _(VOID,_cfPU)(CFC_(UN,LN))( CFARGT27(NCF,KCF,_Z,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR) );
+#else
+#define PROTOCCALLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)     \
+         PROTOCCALLSFSUB0(UN,LN)
+#define PROTOCCALLSFSUB20(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK) \
+         PROTOCCALLSFSUB0(UN,LN)
+#define PROTOCCALLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR) \
+         PROTOCCALLSFSUB0(UN,LN)
+#endif
+#endif
+
+
+#ifdef OLD_VAXC                                  /* Allow %CC-I-PARAMNOTUSED. */
+#pragma standard
+#endif
+
+
+#define CCALLSFSUB1( UN,LN,T1,                        A1)         \
+        CCALLSFSUB5 (UN,LN,T1,CF_0,CF_0,CF_0,CF_0,A1,0,0,0,0)
+#define CCALLSFSUB2( UN,LN,T1,T2,                     A1,A2)      \
+        CCALLSFSUB5 (UN,LN,T1,T2,CF_0,CF_0,CF_0,A1,A2,0,0,0)
+#define CCALLSFSUB3( UN,LN,T1,T2,T3,                  A1,A2,A3)   \
+        CCALLSFSUB5 (UN,LN,T1,T2,T3,CF_0,CF_0,A1,A2,A3,0,0)
+#define CCALLSFSUB4( UN,LN,T1,T2,T3,T4,               A1,A2,A3,A4)\
+        CCALLSFSUB5 (UN,LN,T1,T2,T3,T4,CF_0,A1,A2,A3,A4,0)
+#define CCALLSFSUB5( UN,LN,T1,T2,T3,T4,T5,            A1,A2,A3,A4,A5)          \
+        CCALLSFSUB10(UN,LN,T1,T2,T3,T4,T5,CF_0,CF_0,CF_0,CF_0,CF_0,A1,A2,A3,A4,A5,0,0,0,0,0)
+#define CCALLSFSUB6( UN,LN,T1,T2,T3,T4,T5,T6,         A1,A2,A3,A4,A5,A6)       \
+        CCALLSFSUB10(UN,LN,T1,T2,T3,T4,T5,T6,CF_0,CF_0,CF_0,CF_0,A1,A2,A3,A4,A5,A6,0,0,0,0)
+#define CCALLSFSUB7( UN,LN,T1,T2,T3,T4,T5,T6,T7,      A1,A2,A3,A4,A5,A6,A7)    \
+        CCALLSFSUB10(UN,LN,T1,T2,T3,T4,T5,T6,T7,CF_0,CF_0,CF_0,A1,A2,A3,A4,A5,A6,A7,0,0,0)
+#define CCALLSFSUB8( UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,   A1,A2,A3,A4,A5,A6,A7,A8) \
+        CCALLSFSUB10(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,CF_0,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,0,0)
+#define CCALLSFSUB9( UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,A1,A2,A3,A4,A5,A6,A7,A8,A9)\
+        CCALLSFSUB10(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,0)
+#define CCALLSFSUB10(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA)\
+        CCALLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,CF_0,CF_0,CF_0,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,0,0,0,0)
+#define CCALLSFSUB11(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB)\
+        CCALLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,CF_0,CF_0,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,0,0,0)
+#define CCALLSFSUB12(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC)\
+        CCALLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,CF_0,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,0,0)
+#define CCALLSFSUB13(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD)\
+        CCALLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,0)
+
+#ifdef __cplusplus
+#define CPPPROTOCLSFSUB0( UN,LN)
+#define CPPPROTOCLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)
+#define CPPPROTOCLSFSUB20(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK)
+#define CPPPROTOCLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR)
+#else
+#define CPPPROTOCLSFSUB0(UN,LN) \
+        PROTOCCALLSFSUB0(UN,LN)
+#define CPPPROTOCLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)     \
+        PROTOCCALLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)
+#define CPPPROTOCLSFSUB20(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK) \
+        PROTOCCALLSFSUB20(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK)
+#define CPPPROTOCLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR) \
+        PROTOCCALLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR)
+#endif
+
+#ifdef CFSUBASFUN
+#define CCALLSFSUB0(UN,LN) CCALLSFFUN0(UN,LN)
+#define CCALLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE)\
+        CCALLSFFUN14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE)
+#else
+/* do{...}while(0) allows if(a==b) FORT(); else BORT(); */
+#define CCALLSFSUB0( UN,LN) do{CPPPROTOCLSFSUB0(UN,LN) CFC_(UN,LN)();}while(0)
+#define CCALLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE)\
+do{VVCF(T1,A1,B1) VVCF(T2,A2,B2) VVCF(T3,A3,B3) VVCF(T4,A4,B4) VVCF(T5,A5,B5)  \
+   VVCF(T6,A6,B6) VVCF(T7,A7,B7) VVCF(T8,A8,B8) VVCF(T9,A9,B9) VVCF(TA,AA,B10) \
+   VVCF(TB,AB,B11) VVCF(TC,AC,B12) VVCF(TD,AD,B13) VVCF(TE,AE,B14)             \
+   CPPPROTOCLSFSUB14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)          \
+   ACF(LN,T1,A1,1)  ACF(LN,T2,A2,2)  ACF(LN,T3,A3,3)                           \
+   ACF(LN,T4,A4,4)  ACF(LN,T5,A5,5)  ACF(LN,T6,A6,6)  ACF(LN,T7,A7,7)          \
+   ACF(LN,T8,A8,8)  ACF(LN,T9,A9,9)  ACF(LN,TA,AA,10) ACF(LN,TB,AB,11)         \
+   ACF(LN,TC,AC,12) ACF(LN,TD,AD,13) ACF(LN,TE,AE,14)                          \
+   CFC_(UN,LN)( CFARGTA14(AACF,JCF,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE) );\
+   WCF(T1,A1,1)  WCF(T2,A2,2)  WCF(T3,A3,3)  WCF(T4,A4,4)  WCF(T5,A5,5)        \
+   WCF(T6,A6,6)  WCF(T7,A7,7)  WCF(T8,A8,8)  WCF(T9,A9,9)  WCF(TA,AA,10)       \
+   WCF(TB,AB,11) WCF(TC,AC,12) WCF(TD,AD,13) WCF(TE,AE,14)      }while(0)
+#endif
+
+
+#if MAX_PREPRO_ARGS>31
+#define CCALLSFSUB15(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF)\
+        CCALLSFSUB20(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,CF_0,CF_0,CF_0,CF_0,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,0,0,0,0,0)
+#define CCALLSFSUB16(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG)\
+        CCALLSFSUB20(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,CF_0,CF_0,CF_0,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,0,0,0,0)
+#define CCALLSFSUB17(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH)\
+        CCALLSFSUB20(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,CF_0,CF_0,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,0,0,0)
+#define CCALLSFSUB18(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI)\
+        CCALLSFSUB20(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,CF_0,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,0,0)
+#define CCALLSFSUB19(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ)\
+        CCALLSFSUB20(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,0)
+
+#ifdef CFSUBASFUN
+#define CCALLSFSUB20(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH, \
+        TI,TJ,TK, A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK) \
+        CCALLSFFUN20(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH, \
+        TI,TJ,TK, A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK)
+#else
+#define CCALLSFSUB20(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH, \
+        TI,TJ,TK, A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK) \
+do{VVCF(T1,A1,B1)  VVCF(T2,A2,B2)  VVCF(T3,A3,B3)  VVCF(T4,A4,B4)  VVCF(T5,A5,B5)   \
+   VVCF(T6,A6,B6)  VVCF(T7,A7,B7)  VVCF(T8,A8,B8)  VVCF(T9,A9,B9)  VVCF(TA,AA,B10)  \
+   VVCF(TB,AB,B11) VVCF(TC,AC,B12) VVCF(TD,AD,B13) VVCF(TE,AE,B14) VVCF(TF,AF,B15)  \
+   VVCF(TG,AG,B16) VVCF(TH,AH,B17) VVCF(TI,AI,B18) VVCF(TJ,AJ,B19) VVCF(TK,AK,B20)  \
+   CPPPROTOCLSFSUB20(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK)  \
+   ACF(LN,T1,A1,1)  ACF(LN,T2,A2,2)  ACF(LN,T3,A3,3)  ACF(LN,T4,A4,4)          \
+   ACF(LN,T5,A5,5)  ACF(LN,T6,A6,6)  ACF(LN,T7,A7,7)  ACF(LN,T8,A8,8)          \
+   ACF(LN,T9,A9,9)  ACF(LN,TA,AA,10) ACF(LN,TB,AB,11) ACF(LN,TC,AC,12)         \
+   ACF(LN,TD,AD,13) ACF(LN,TE,AE,14) ACF(LN,TF,AF,15) ACF(LN,TG,AG,16)         \
+   ACF(LN,TH,AH,17) ACF(LN,TI,AI,18) ACF(LN,TJ,AJ,19) ACF(LN,TK,AK,20)         \
+   CFC_(UN,LN)( CFARGTA20(AACF,JCF,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK) ); \
+ WCF(T1,A1,1)  WCF(T2,A2,2)  WCF(T3,A3,3)  WCF(T4,A4,4)  WCF(T5,A5,5)  WCF(T6,A6,6)  \
+ WCF(T7,A7,7)  WCF(T8,A8,8)  WCF(T9,A9,9)  WCF(TA,AA,10) WCF(TB,AB,11) WCF(TC,AC,12) \
+ WCF(TD,AD,13) WCF(TE,AE,14) WCF(TF,AF,15) WCF(TG,AG,16) WCF(TH,AH,17) WCF(TI,AI,18) \
+ WCF(TJ,AJ,19) WCF(TK,AK,20) }while(0)
+#endif
+#endif         /* MAX_PREPRO_ARGS */
+
+#if MAX_PREPRO_ARGS>31
+#define CCALLSFSUB21(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL)\
+        CCALLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL,0,0,0,0,0,0)
+#define CCALLSFSUB22(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL,AM)\
+        CCALLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,CF_0,CF_0,CF_0,CF_0,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL,AM,0,0,0,0,0)
+#define CCALLSFSUB23(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL,AM,AN)\
+        CCALLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,CF_0,CF_0,CF_0,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL,AM,AN,0,0,0,0)
+#define CCALLSFSUB24(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL,AM,AN,AO)\
+        CCALLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,CF_0,CF_0,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL,AM,AN,AO,0,0,0)
+#define CCALLSFSUB25(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL,AM,AN,AO,AP)\
+        CCALLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,CF_0,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL,AM,AN,AO,AP,0,0)
+#define CCALLSFSUB26(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL,AM,AN,AO,AP,AQ)\
+        CCALLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL,AM,AN,AO,AP,AQ,0)
+
+#ifdef CFSUBASFUN
+#define CCALLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR, \
+                           A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL,AM,AN,AO,AP,AQ,AR) \
+        CCALLSFFUN27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR, \
+                           A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL,AM,AN,AO,AP,AQ,AR)
+#else
+#define CCALLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR, \
+                           A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL,AM,AN,AO,AP,AQ,AR) \
+do{VVCF(T1,A1,B1)  VVCF(T2,A2,B2)  VVCF(T3,A3,B3)  VVCF(T4,A4,B4)  VVCF(T5,A5,B5)   \
+   VVCF(T6,A6,B6)  VVCF(T7,A7,B7)  VVCF(T8,A8,B8)  VVCF(T9,A9,B9)  VVCF(TA,AA,B10)  \
+   VVCF(TB,AB,B11) VVCF(TC,AC,B12) VVCF(TD,AD,B13) VVCF(TE,AE,B14) VVCF(TF,AF,B15)  \
+   VVCF(TG,AG,B16) VVCF(TH,AH,B17) VVCF(TI,AI,B18) VVCF(TJ,AJ,B19) VVCF(TK,AK,B20)  \
+   VVCF(TL,AL,B21) VVCF(TM,AM,B22) VVCF(TN,AN,B23) VVCF(TO,AO,B24) VVCF(TP,AP,B25)  \
+   VVCF(TQ,AQ,B26) VVCF(TR,AR,B27)                                                  \
+   CPPPROTOCLSFSUB27(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR) \
+   ACF(LN,T1,A1,1)  ACF(LN,T2,A2,2)  ACF(LN,T3,A3,3)  ACF(LN,T4,A4,4)          \
+   ACF(LN,T5,A5,5)  ACF(LN,T6,A6,6)  ACF(LN,T7,A7,7)  ACF(LN,T8,A8,8)          \
+   ACF(LN,T9,A9,9)  ACF(LN,TA,AA,10) ACF(LN,TB,AB,11) ACF(LN,TC,AC,12)         \
+   ACF(LN,TD,AD,13) ACF(LN,TE,AE,14) ACF(LN,TF,AF,15) ACF(LN,TG,AG,16)         \
+   ACF(LN,TH,AH,17) ACF(LN,TI,AI,18) ACF(LN,TJ,AJ,19) ACF(LN,TK,AK,20)         \
+   ACF(LN,TL,AL,21) ACF(LN,TM,AM,22) ACF(LN,TN,AN,23) ACF(LN,TO,AO,24)         \
+   ACF(LN,TP,AP,25) ACF(LN,TQ,AQ,26) ACF(LN,TR,AR,27)                          \
+   CFC_(UN,LN)( CFARGTA27(AACF,JCF,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR,\
+                                   A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE,AF,AG,AH,AI,AJ,AK,AL,AM,AN,AO,AP,AQ,AR) ); \
+ WCF(T1,A1,1)  WCF(T2,A2,2)  WCF(T3,A3,3)  WCF(T4,A4,4)  WCF(T5,A5,5)  WCF(T6,A6,6)  \
+ WCF(T7,A7,7)  WCF(T8,A8,8)  WCF(T9,A9,9)  WCF(TA,AA,10) WCF(TB,AB,11) WCF(TC,AC,12) \
+ WCF(TD,AD,13) WCF(TE,AE,14) WCF(TF,AF,15) WCF(TG,AG,16) WCF(TH,AH,17) WCF(TI,AI,18) \
+ WCF(TJ,AJ,19) WCF(TK,AK,20) WCF(TL,AL,21) WCF(TM,AM,22) WCF(TN,AN,23) WCF(TO,AO,24) \
+ WCF(TP,AP,25) WCF(TQ,AQ,26) WCF(TR,AR,27) }while(0)
+#endif
+#endif         /* MAX_PREPRO_ARGS */
+
+/*-------------------------------------------------------------------------*/
+
+/*               UTILITIES FOR C TO CALL FORTRAN FUNCTIONS                 */
+
+/*N.B. PROTOCCALLSFFUNn(..) generates code, whether or not the FORTRAN
+  function is called. Therefore, especially for creator's of C header files
+  for large FORTRAN libraries which include many functions, to reduce
+  compile time and object code size, it may be desirable to create
+  preprocessor directives to allow users to create code for only those
+  functions which they use.                                                */
+
+/* The following defines the maximum length string that a function can return.
+   Of course it may be undefine-d and re-define-d before individual
+   PROTOCCALLSFFUNn(..) as required. It would also be nice to have this derived
+   from the individual machines' limits.                                      */
+#define MAX_LEN_FORTRAN_FUNCTION_STRING 0x4FE
+
+/* The following defines a character used by CFORTRAN.H to flag the end of a
+   string coming out of a FORTRAN routine.                                 */
+#define CFORTRAN_NON_CHAR 0x7F
+
+#ifdef OLD_VAXC                                /* Prevent %CC-I-PARAMNOTUSED. */
+#pragma nostandard
+#endif
+
+#define _SEP_(TN,C,cfCOMMA)     _(__SEP_,C)(TN,cfCOMMA)
+#define __SEP_0(TN,cfCOMMA)  
+#define __SEP_1(TN,cfCOMMA)     _Icf(2,SEP,TN,cfCOMMA,0)
+#define        INT_cfSEP(T,B) _(A,B)
+#define       INTV_cfSEP(T,B) INT_cfSEP(T,B)
+#define      INTVV_cfSEP(T,B) INT_cfSEP(T,B)
+#define     INTVVV_cfSEP(T,B) INT_cfSEP(T,B)
+#define    INTVVVV_cfSEP(T,B) INT_cfSEP(T,B)
+#define   INTVVVVV_cfSEP(T,B) INT_cfSEP(T,B)
+#define  INTVVVVVV_cfSEP(T,B) INT_cfSEP(T,B)
+#define INTVVVVVVV_cfSEP(T,B) INT_cfSEP(T,B)
+#define       PINT_cfSEP(T,B) INT_cfSEP(T,B)
+#define      PVOID_cfSEP(T,B) INT_cfSEP(T,B)
+#define    ROUTINE_cfSEP(T,B) INT_cfSEP(T,B)
+#define     SIMPLE_cfSEP(T,B) INT_cfSEP(T,B)
+#define       VOID_cfSEP(T,B) INT_cfSEP(T,B)    /* For FORTRAN calls C subr.s.*/
+#define     STRING_cfSEP(T,B) INT_cfSEP(T,B)
+#define    STRINGV_cfSEP(T,B) INT_cfSEP(T,B)
+#define    PSTRING_cfSEP(T,B) INT_cfSEP(T,B)
+#define   PSTRINGV_cfSEP(T,B) INT_cfSEP(T,B)
+#define   PNSTRING_cfSEP(T,B) INT_cfSEP(T,B)
+#define   PPSTRING_cfSEP(T,B) INT_cfSEP(T,B)
+#define    ZTRINGV_cfSEP(T,B) INT_cfSEP(T,B)
+#define   PZTRINGV_cfSEP(T,B) INT_cfSEP(T,B)
+                         
+#if defined(SIGNED_BYTE) || !defined(UNSIGNED_BYTE)
+#ifdef OLD_VAXC
+#define INTEGER_BYTE               char    /* Old VAXC barfs on 'signed char' */
+#else
+#define INTEGER_BYTE        signed char    /* default */
+#endif
+#else
+#define INTEGER_BYTE        unsigned char
+#endif
+#define    BYTEVVVVVVV_cfTYPE INTEGER_BYTE
+#define  DOUBLEVVVVVVV_cfTYPE DOUBLE_PRECISION 
+#define   FLOATVVVVVVV_cfTYPE FORTRAN_REAL
+#define     INTVVVVVVV_cfTYPE int
+#define LOGICALVVVVVVV_cfTYPE int
+#define    LONGVVVVVVV_cfTYPE long
+#define LONGLONGVVVVVVV_cfTYPE LONGLONG   /* added by MR December 2005 */
+#define   SHORTVVVVVVV_cfTYPE short
+#define          PBYTE_cfTYPE INTEGER_BYTE
+#define        PDOUBLE_cfTYPE DOUBLE_PRECISION 
+#define         PFLOAT_cfTYPE FORTRAN_REAL
+#define           PINT_cfTYPE int
+#define       PLOGICAL_cfTYPE int
+#define          PLONG_cfTYPE long
+#define      PLONGLONG_cfTYPE LONGLONG  /* added by MR December 2005 */
+#define         PSHORT_cfTYPE short
+
+#define CFARGS0(A,T,V,W,X,Y,Z) _3(T,_cf,A)
+#define CFARGS1(A,T,V,W,X,Y,Z) _3(T,_cf,A)(V)
+#define CFARGS2(A,T,V,W,X,Y,Z) _3(T,_cf,A)(V,W)
+#define CFARGS3(A,T,V,W,X,Y,Z) _3(T,_cf,A)(V,W,X)
+#define CFARGS4(A,T,V,W,X,Y,Z) _3(T,_cf,A)(V,W,X,Y)
+#define CFARGS5(A,T,V,W,X,Y,Z) _3(T,_cf,A)(V,W,X,Y,Z)
+
+#define  _Icf(N,T,I,X,Y)                 _(I,_cfINT)(N,T,I,X,Y,0)
+#define _Icf4(N,T,I,X,Y,Z)               _(I,_cfINT)(N,T,I,X,Y,Z)
+#define           BYTE_cfINT(N,A,B,X,Y,Z)        DOUBLE_cfINT(N,A,B,X,Y,Z)
+#define         DOUBLE_cfINT(N,A,B,X,Y,Z) _(CFARGS,N)(A,INT,B,X,Y,Z,0)
+#define          FLOAT_cfINT(N,A,B,X,Y,Z)        DOUBLE_cfINT(N,A,B,X,Y,Z)
+#define            INT_cfINT(N,A,B,X,Y,Z)        DOUBLE_cfINT(N,A,B,X,Y,Z)
+#define        LOGICAL_cfINT(N,A,B,X,Y,Z)        DOUBLE_cfINT(N,A,B,X,Y,Z)
+#define           LONG_cfINT(N,A,B,X,Y,Z)        DOUBLE_cfINT(N,A,B,X,Y,Z)
+#define       LONGLONG_cfINT(N,A,B,X,Y,Z)        DOUBLE_cfINT(N,A,B,X,Y,Z) /* added by MR December 2005 */
+#define          SHORT_cfINT(N,A,B,X,Y,Z)        DOUBLE_cfINT(N,A,B,X,Y,Z)
+#define          PBYTE_cfINT(N,A,B,X,Y,Z)       PDOUBLE_cfINT(N,A,B,X,Y,Z)
+#define        PDOUBLE_cfINT(N,A,B,X,Y,Z) _(CFARGS,N)(A,PINT,B,X,Y,Z,0)
+#define         PFLOAT_cfINT(N,A,B,X,Y,Z)       PDOUBLE_cfINT(N,A,B,X,Y,Z)
+#define           PINT_cfINT(N,A,B,X,Y,Z)       PDOUBLE_cfINT(N,A,B,X,Y,Z)
+#define       PLOGICAL_cfINT(N,A,B,X,Y,Z)       PDOUBLE_cfINT(N,A,B,X,Y,Z)
+#define          PLONG_cfINT(N,A,B,X,Y,Z)       PDOUBLE_cfINT(N,A,B,X,Y,Z)
+#define      PLONGLONG_cfINT(N,A,B,X,Y,Z)       PDOUBLE_cfINT(N,A,B,X,Y,Z) /* added by MR December 2005 */
+#define         PSHORT_cfINT(N,A,B,X,Y,Z)       PDOUBLE_cfINT(N,A,B,X,Y,Z)
+#define          BYTEV_cfINT(N,A,B,X,Y,Z)       DOUBLEV_cfINT(N,A,B,X,Y,Z)
+#define         BYTEVV_cfINT(N,A,B,X,Y,Z)      DOUBLEVV_cfINT(N,A,B,X,Y,Z)
+#define        BYTEVVV_cfINT(N,A,B,X,Y,Z)     DOUBLEVVV_cfINT(N,A,B,X,Y,Z)
+#define       BYTEVVVV_cfINT(N,A,B,X,Y,Z)    DOUBLEVVVV_cfINT(N,A,B,X,Y,Z)
+#define      BYTEVVVVV_cfINT(N,A,B,X,Y,Z)   DOUBLEVVVVV_cfINT(N,A,B,X,Y,Z)
+#define     BYTEVVVVVV_cfINT(N,A,B,X,Y,Z)  DOUBLEVVVVVV_cfINT(N,A,B,X,Y,Z)
+#define    BYTEVVVVVVV_cfINT(N,A,B,X,Y,Z) DOUBLEVVVVVVV_cfINT(N,A,B,X,Y,Z)
+#define        DOUBLEV_cfINT(N,A,B,X,Y,Z) _(CFARGS,N)(A,INTV,B,X,Y,Z,0)
+#define       DOUBLEVV_cfINT(N,A,B,X,Y,Z) _(CFARGS,N)(A,INTVV,B,X,Y,Z,0)
+#define      DOUBLEVVV_cfINT(N,A,B,X,Y,Z) _(CFARGS,N)(A,INTVVV,B,X,Y,Z,0)
+#define     DOUBLEVVVV_cfINT(N,A,B,X,Y,Z) _(CFARGS,N)(A,INTVVVV,B,X,Y,Z,0)
+#define    DOUBLEVVVVV_cfINT(N,A,B,X,Y,Z) _(CFARGS,N)(A,INTVVVVV,B,X,Y,Z,0)
+#define   DOUBLEVVVVVV_cfINT(N,A,B,X,Y,Z) _(CFARGS,N)(A,INTVVVVVV,B,X,Y,Z,0)
+#define  DOUBLEVVVVVVV_cfINT(N,A,B,X,Y,Z) _(CFARGS,N)(A,INTVVVVVVV,B,X,Y,Z,0)
+#define         FLOATV_cfINT(N,A,B,X,Y,Z)       DOUBLEV_cfINT(N,A,B,X,Y,Z)
+#define        FLOATVV_cfINT(N,A,B,X,Y,Z)      DOUBLEVV_cfINT(N,A,B,X,Y,Z)
+#define       FLOATVVV_cfINT(N,A,B,X,Y,Z)     DOUBLEVVV_cfINT(N,A,B,X,Y,Z)
+#define      FLOATVVVV_cfINT(N,A,B,X,Y,Z)    DOUBLEVVVV_cfINT(N,A,B,X,Y,Z)
+#define     FLOATVVVVV_cfINT(N,A,B,X,Y,Z)   DOUBLEVVVVV_cfINT(N,A,B,X,Y,Z)
+#define    FLOATVVVVVV_cfINT(N,A,B,X,Y,Z)  DOUBLEVVVVVV_cfINT(N,A,B,X,Y,Z)
+#define   FLOATVVVVVVV_cfINT(N,A,B,X,Y,Z) DOUBLEVVVVVVV_cfINT(N,A,B,X,Y,Z)
+#define           INTV_cfINT(N,A,B,X,Y,Z)       DOUBLEV_cfINT(N,A,B,X,Y,Z)
+#define          INTVV_cfINT(N,A,B,X,Y,Z)      DOUBLEVV_cfINT(N,A,B,X,Y,Z)
+#define         INTVVV_cfINT(N,A,B,X,Y,Z)     DOUBLEVVV_cfINT(N,A,B,X,Y,Z)
+#define        INTVVVV_cfINT(N,A,B,X,Y,Z)    DOUBLEVVVV_cfINT(N,A,B,X,Y,Z)
+#define       INTVVVVV_cfINT(N,A,B,X,Y,Z)   DOUBLEVVVVV_cfINT(N,A,B,X,Y,Z)
+#define      INTVVVVVV_cfINT(N,A,B,X,Y,Z)  DOUBLEVVVVVV_cfINT(N,A,B,X,Y,Z)
+#define     INTVVVVVVV_cfINT(N,A,B,X,Y,Z) DOUBLEVVVVVVV_cfINT(N,A,B,X,Y,Z)
+#define       LOGICALV_cfINT(N,A,B,X,Y,Z)       DOUBLEV_cfINT(N,A,B,X,Y,Z)
+#define      LOGICALVV_cfINT(N,A,B,X,Y,Z)      DOUBLEVV_cfINT(N,A,B,X,Y,Z)
+#define     LOGICALVVV_cfINT(N,A,B,X,Y,Z)     DOUBLEVVV_cfINT(N,A,B,X,Y,Z)
+#define    LOGICALVVVV_cfINT(N,A,B,X,Y,Z)    DOUBLEVVVV_cfINT(N,A,B,X,Y,Z)
+#define   LOGICALVVVVV_cfINT(N,A,B,X,Y,Z)   DOUBLEVVVVV_cfINT(N,A,B,X,Y,Z)
+#define  LOGICALVVVVVV_cfINT(N,A,B,X,Y,Z)  DOUBLEVVVVVV_cfINT(N,A,B,X,Y,Z)
+#define LOGICALVVVVVVV_cfINT(N,A,B,X,Y,Z) DOUBLEVVVVVVV_cfINT(N,A,B,X,Y,Z)
+#define          LONGV_cfINT(N,A,B,X,Y,Z)       DOUBLEV_cfINT(N,A,B,X,Y,Z)
+#define         LONGVV_cfINT(N,A,B,X,Y,Z)      DOUBLEVV_cfINT(N,A,B,X,Y,Z)
+#define        LONGVVV_cfINT(N,A,B,X,Y,Z)     DOUBLEVVV_cfINT(N,A,B,X,Y,Z)
+#define       LONGVVVV_cfINT(N,A,B,X,Y,Z)    DOUBLEVVVV_cfINT(N,A,B,X,Y,Z)
+#define      LONGVVVVV_cfINT(N,A,B,X,Y,Z)   DOUBLEVVVVV_cfINT(N,A,B,X,Y,Z)
+#define     LONGVVVVVV_cfINT(N,A,B,X,Y,Z)  DOUBLEVVVVVV_cfINT(N,A,B,X,Y,Z)
+#define    LONGVVVVVVV_cfINT(N,A,B,X,Y,Z) DOUBLEVVVVVVV_cfINT(N,A,B,X,Y,Z)
+#define      LONGLONGV_cfINT(N,A,B,X,Y,Z)       DOUBLEV_cfINT(N,A,B,X,Y,Z) /* added by MR December 2005 */
+#define     LONGLONGVV_cfINT(N,A,B,X,Y,Z)      DOUBLEVV_cfINT(N,A,B,X,Y,Z) /* added by MR December 2005 */
+#define    LONGLONGVVV_cfINT(N,A,B,X,Y,Z)     DOUBLEVVV_cfINT(N,A,B,X,Y,Z) /* added by MR December 2005 */
+#define   LONGLONGVVVV_cfINT(N,A,B,X,Y,Z)    DOUBLEVVVV_cfINT(N,A,B,X,Y,Z) /* added by MR December 2005 */
+#define  LONGLONGVVVVV_cfINT(N,A,B,X,Y,Z)   DOUBLEVVVVV_cfINT(N,A,B,X,Y,Z) /* added by MR December 2005 */
+#define LONGLONGVVVVVV_cfINT(N,A,B,X,Y,Z)  DOUBLEVVVVVV_cfINT(N,A,B,X,Y,Z) /* added by MR December 2005 */
+#define LONGLONGVVVVVVV_cfINT(N,A,B,X,Y,Z) DOUBLEVVVVVVV_cfINT(N,A,B,X,Y,Z) /* added by MR December 2005 */
+#define         SHORTV_cfINT(N,A,B,X,Y,Z)       DOUBLEV_cfINT(N,A,B,X,Y,Z)
+#define        SHORTVV_cfINT(N,A,B,X,Y,Z)      DOUBLEVV_cfINT(N,A,B,X,Y,Z)
+#define       SHORTVVV_cfINT(N,A,B,X,Y,Z)     DOUBLEVVV_cfINT(N,A,B,X,Y,Z)
+#define      SHORTVVVV_cfINT(N,A,B,X,Y,Z)    DOUBLEVVVV_cfINT(N,A,B,X,Y,Z)
+#define     SHORTVVVVV_cfINT(N,A,B,X,Y,Z)   DOUBLEVVVVV_cfINT(N,A,B,X,Y,Z)
+#define    SHORTVVVVVV_cfINT(N,A,B,X,Y,Z)  DOUBLEVVVVVV_cfINT(N,A,B,X,Y,Z)
+#define   SHORTVVVVVVV_cfINT(N,A,B,X,Y,Z) DOUBLEVVVVVVV_cfINT(N,A,B,X,Y,Z)
+#define          PVOID_cfINT(N,A,B,X,Y,Z) _(CFARGS,N)(A,B,B,X,Y,Z,0)
+#define        ROUTINE_cfINT(N,A,B,X,Y,Z)         PVOID_cfINT(N,A,B,X,Y,Z)
+/*CRAY coughs on the first,
+  i.e. the usual trouble of not being able to
+  define macros to macros with arguments. 
+  New ultrix is worse, it coughs on all such uses.
+ */
+/*#define       SIMPLE_cfINT                    PVOID_cfINT*/
+#define         SIMPLE_cfINT(N,A,B,X,Y,Z)         PVOID_cfINT(N,A,B,X,Y,Z)
+#define           VOID_cfINT(N,A,B,X,Y,Z)         PVOID_cfINT(N,A,B,X,Y,Z)
+#define         STRING_cfINT(N,A,B,X,Y,Z)         PVOID_cfINT(N,A,B,X,Y,Z)
+#define        STRINGV_cfINT(N,A,B,X,Y,Z)         PVOID_cfINT(N,A,B,X,Y,Z)
+#define        PSTRING_cfINT(N,A,B,X,Y,Z)         PVOID_cfINT(N,A,B,X,Y,Z)
+#define       PSTRINGV_cfINT(N,A,B,X,Y,Z)         PVOID_cfINT(N,A,B,X,Y,Z)
+#define       PNSTRING_cfINT(N,A,B,X,Y,Z)         PVOID_cfINT(N,A,B,X,Y,Z)
+#define       PPSTRING_cfINT(N,A,B,X,Y,Z)         PVOID_cfINT(N,A,B,X,Y,Z)
+#define        ZTRINGV_cfINT(N,A,B,X,Y,Z)         PVOID_cfINT(N,A,B,X,Y,Z)
+#define       PZTRINGV_cfINT(N,A,B,X,Y,Z)         PVOID_cfINT(N,A,B,X,Y,Z)
+#define           CF_0_cfINT(N,A,B,X,Y,Z)
+                         
+
+#define   UCF(TN,I,C)  _SEP_(TN,C,cfCOMMA) _Icf(2,U,TN,_(A,I),0)
+#define  UUCF(TN,I,C)  _SEP_(TN,C,cfCOMMA) _SEP_(TN,1,I) 
+#define UUUCF(TN,I,C)  _SEP_(TN,C,cfCOLON) _Icf(2,U,TN,_(A,I),0)
+#define        INT_cfU(T,A) _(T,VVVVVVV_cfTYPE)   A
+#define       INTV_cfU(T,A) _(T,VVVVVV_cfTYPE)  * A
+#define      INTVV_cfU(T,A) _(T,VVVVV_cfTYPE)   * A
+#define     INTVVV_cfU(T,A) _(T,VVVV_cfTYPE)    * A
+#define    INTVVVV_cfU(T,A) _(T,VVV_cfTYPE)     * A
+#define   INTVVVVV_cfU(T,A) _(T,VV_cfTYPE)      * A
+#define  INTVVVVVV_cfU(T,A) _(T,V_cfTYPE)       * A
+#define INTVVVVVVV_cfU(T,A) _(T,_cfTYPE)        * A
+#define       PINT_cfU(T,A) _(T,_cfTYPE)        * A
+#define      PVOID_cfU(T,A) void  *A 
+#define    ROUTINE_cfU(T,A) void (*A)(CF_NULL_PROTO) 
+#define       VOID_cfU(T,A) void   A    /* Needed for C calls FORTRAN sub.s.  */
+#define     STRING_cfU(T,A) char  *A    /*            via VOID and wrapper.   */
+#define    STRINGV_cfU(T,A) char  *A
+#define    PSTRING_cfU(T,A) char  *A
+#define   PSTRINGV_cfU(T,A) char  *A
+#define    ZTRINGV_cfU(T,A) char  *A
+#define   PZTRINGV_cfU(T,A) char  *A
+
+/* VOID breaks U into U and UU. */
+#define       INT_cfUU(T,A) _(T,VVVVVVV_cfTYPE) A
+#define      VOID_cfUU(T,A)             /* Needed for FORTRAN calls C sub.s.  */
+#define    STRING_cfUU(T,A) char *A 
+
+
+#define      BYTE_cfPU(A)   CFextern INTEGER_BYTE      FCALLSC_QUALIFIER A
+#define    DOUBLE_cfPU(A)   CFextern DOUBLE_PRECISION  FCALLSC_QUALIFIER A
+#if ! (defined(FLOATFUNCTIONTYPE)&&defined(ASSIGNFLOAT)&&defined(RETURNFLOAT))
+#if defined (f2cFortran) && ! defined (gFortran)
+/* f2c/g77 return double from FORTRAN REAL functions. (KMCCARTY, 2005/12/09) */
+#define     FLOAT_cfPU(A)   CFextern DOUBLE_PRECISION  FCALLSC_QUALIFIER A
+#else
+#define     FLOAT_cfPU(A)   CFextern FORTRAN_REAL      FCALLSC_QUALIFIER A
+#endif
+#else				   	                   
+#define     FLOAT_cfPU(A)   CFextern FLOATFUNCTIONTYPE FCALLSC_QUALIFIER A
+#endif				   	                   
+#define       INT_cfPU(A)   CFextern int   FCALLSC_QUALIFIER   A
+#define   LOGICAL_cfPU(A)   CFextern int   FCALLSC_QUALIFIER   A
+#define      LONG_cfPU(A)   CFextern long  FCALLSC_QUALIFIER   A
+#define     SHORT_cfPU(A)   CFextern short FCALLSC_QUALIFIER   A
+#define    STRING_cfPU(A)   CFextern void  FCALLSC_QUALIFIER   A
+#define      VOID_cfPU(A)   CFextern void  FCALLSC_QUALIFIER   A
+
+#define    BYTE_cfE INTEGER_BYTE     A0;
+#define  DOUBLE_cfE DOUBLE_PRECISION A0;
+#if ! (defined(FLOATFUNCTIONTYPE)&&defined(ASSIGNFLOAT)&&defined(RETURNFLOAT))
+#define   FLOAT_cfE FORTRAN_REAL  A0;
+#else
+#define   FLOAT_cfE FORTRAN_REAL AA0;   FLOATFUNCTIONTYPE A0;
+#endif
+#define     INT_cfE int    A0;
+#define LOGICAL_cfE int    A0;
+#define    LONG_cfE long   A0;
+#define   SHORT_cfE short  A0;
+#define    VOID_cfE
+#ifdef vmsFortran
+#define  STRING_cfE static char AA0[1+MAX_LEN_FORTRAN_FUNCTION_STRING];        \
+                       static fstring A0 =                                     \
+             {MAX_LEN_FORTRAN_FUNCTION_STRING,DSC$K_DTYPE_T,DSC$K_CLASS_S,AA0};\
+               memset(AA0, CFORTRAN_NON_CHAR, MAX_LEN_FORTRAN_FUNCTION_STRING);\
+                                    *(AA0+MAX_LEN_FORTRAN_FUNCTION_STRING)='\0';
+#else
+#ifdef CRAYFortran
+#define  STRING_cfE static char AA0[1+MAX_LEN_FORTRAN_FUNCTION_STRING];        \
+                   static _fcd A0; *(AA0+MAX_LEN_FORTRAN_FUNCTION_STRING)='\0';\
+                memset(AA0,CFORTRAN_NON_CHAR, MAX_LEN_FORTRAN_FUNCTION_STRING);\
+                            A0 = _cptofcd(AA0,MAX_LEN_FORTRAN_FUNCTION_STRING);
+#else
+/* 'cc: SC3.0.1 13 Jul 1994' barfs on char A0[0x4FE+1]; 
+ * char A0[0x4FE +1]; char A0[1+0x4FE]; are both OK.     */
+#define STRING_cfE static char A0[1+MAX_LEN_FORTRAN_FUNCTION_STRING];          \
+                       memset(A0, CFORTRAN_NON_CHAR,                           \
+                              MAX_LEN_FORTRAN_FUNCTION_STRING);                \
+                       *(A0+MAX_LEN_FORTRAN_FUNCTION_STRING)='\0';
+#endif
+#endif
+/* ESTRING must use static char. array which is guaranteed to exist after
+   function returns.                                                     */
+
+/* N.B.i) The diff. for 0 (Zero) and >=1 arguments.
+       ii)That the following create an unmatched bracket, i.e. '(', which
+          must of course be matched in the call.
+       iii)Commas must be handled very carefully                         */
+#define    INT_cfGZ(T,UN,LN) A0=CFC_(UN,LN)(
+#define   VOID_cfGZ(T,UN,LN)    CFC_(UN,LN)(
+#ifdef vmsFortran
+#define STRING_cfGZ(T,UN,LN)    CFC_(UN,LN)(&A0
+#else
+#if defined(CRAYFortran) || defined(AbsoftUNIXFortran) || defined(AbsoftProFortran)
+#define STRING_cfGZ(T,UN,LN)    CFC_(UN,LN)( A0
+#else
+#define STRING_cfGZ(T,UN,LN)    CFC_(UN,LN)( A0,MAX_LEN_FORTRAN_FUNCTION_STRING
+#endif
+#endif
+
+#define     INT_cfG(T,UN,LN)    INT_cfGZ(T,UN,LN)
+#define    VOID_cfG(T,UN,LN)   VOID_cfGZ(T,UN,LN)
+#define  STRING_cfG(T,UN,LN) STRING_cfGZ(T,UN,LN), /*, is only diff. from _cfG*/
+
+#define    BYTEVVVVVVV_cfPP
+#define     INTVVVVVVV_cfPP     /* These complement FLOATVVVVVVV_cfPP. */
+#define  DOUBLEVVVVVVV_cfPP
+#define LOGICALVVVVVVV_cfPP
+#define    LONGVVVVVVV_cfPP
+#define   SHORTVVVVVVV_cfPP
+#define          PBYTE_cfPP
+#define           PINT_cfPP
+#define        PDOUBLE_cfPP
+#define       PLOGICAL_cfPP
+#define          PLONG_cfPP
+#define         PSHORT_cfPP
+#define         PFLOAT_cfPP FLOATVVVVVVV_cfPP
+
+#define BCF(TN,AN,C)        _SEP_(TN,C,cfCOMMA) _Icf(2,B,TN,AN,0)
+#define        INT_cfB(T,A) (_(T,VVVVVVV_cfTYPE)) A
+#define       INTV_cfB(T,A)            A
+#define      INTVV_cfB(T,A)           (A)[0]
+#define     INTVVV_cfB(T,A)           (A)[0][0]
+#define    INTVVVV_cfB(T,A)           (A)[0][0][0]
+#define   INTVVVVV_cfB(T,A)           (A)[0][0][0][0]
+#define  INTVVVVVV_cfB(T,A)           (A)[0][0][0][0][0]
+#define INTVVVVVVV_cfB(T,A)           (A)[0][0][0][0][0][0]
+#define       PINT_cfB(T,A) _(T,_cfPP)&A
+#define     STRING_cfB(T,A) (char *)   A
+#define    STRINGV_cfB(T,A) (char *)   A
+#define    PSTRING_cfB(T,A) (char *)   A
+#define   PSTRINGV_cfB(T,A) (char *)   A
+#define      PVOID_cfB(T,A) (void *)   A
+#define    ROUTINE_cfB(T,A) (cfCAST_FUNCTION)A
+#define    ZTRINGV_cfB(T,A) (char *)   A
+#define   PZTRINGV_cfB(T,A) (char *)   A
+                                                              	
+#define SCF(TN,NAME,I,A)    _(TN,_cfSTR)(3,S,NAME,I,A,0,0)
+#define  DEFAULT_cfS(M,I,A)
+#define  LOGICAL_cfS(M,I,A)
+#define PLOGICAL_cfS(M,I,A)
+#define   STRING_cfS(M,I,A) ,sizeof(A)
+#define  STRINGV_cfS(M,I,A) ,( (unsigned)0xFFFF*firstindexlength(A) \
+                              +secondindexlength(A))
+#define  PSTRING_cfS(M,I,A) ,sizeof(A)
+#define PSTRINGV_cfS(M,I,A) STRINGV_cfS(M,I,A)
+#define  ZTRINGV_cfS(M,I,A)
+#define PZTRINGV_cfS(M,I,A)
+
+#define   HCF(TN,I)         _(TN,_cfSTR)(3,H,cfCOMMA, H,_(C,I),0,0)
+#define  HHCF(TN,I)         _(TN,_cfSTR)(3,H,cfCOMMA,HH,_(C,I),0,0)
+#define HHHCF(TN,I)         _(TN,_cfSTR)(3,H,cfCOLON, H,_(C,I),0,0)
+#define  H_CF_SPECIAL       unsigned
+#define HH_CF_SPECIAL
+#define  DEFAULT_cfH(M,I,A)
+#define  LOGICAL_cfH(S,U,B)
+#define PLOGICAL_cfH(S,U,B)
+#define   STRING_cfH(S,U,B) _(A,S) _(U,_CF_SPECIAL) B
+#define  STRINGV_cfH(S,U,B) STRING_cfH(S,U,B)
+#define  PSTRING_cfH(S,U,B) STRING_cfH(S,U,B)
+#define PSTRINGV_cfH(S,U,B) STRING_cfH(S,U,B)
+#define PNSTRING_cfH(S,U,B) STRING_cfH(S,U,B)
+#define PPSTRING_cfH(S,U,B) STRING_cfH(S,U,B)
+#define  ZTRINGV_cfH(S,U,B)
+#define PZTRINGV_cfH(S,U,B)
+
+/* Need VOID_cfSTR because Absoft forced function types go through _cfSTR. */
+/* No spaces inside expansion. They screws up macro catenation kludge.     */
+#define           VOID_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define           BYTE_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define         DOUBLE_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define          FLOAT_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define            INT_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define        LOGICAL_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,LOGICAL,A,B,C,D,E)
+#define           LONG_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define       LONGLONG_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E) /* added by MR December 2005 */
+#define          SHORT_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define          BYTEV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define         BYTEVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define        BYTEVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define       BYTEVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define      BYTEVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define     BYTEVVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define    BYTEVVVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define        DOUBLEV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define       DOUBLEVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define      DOUBLEVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define     DOUBLEVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define    DOUBLEVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define   DOUBLEVVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define  DOUBLEVVVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define         FLOATV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define        FLOATVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define       FLOATVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define      FLOATVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define     FLOATVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define    FLOATVVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define   FLOATVVVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define           INTV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define          INTVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define         INTVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define        INTVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define       INTVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define      INTVVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define     INTVVVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define       LOGICALV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define      LOGICALVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define     LOGICALVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define    LOGICALVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define   LOGICALVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define  LOGICALVVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define LOGICALVVVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define          LONGV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define         LONGVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define        LONGVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define       LONGVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define      LONGVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define     LONGVVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define    LONGVVVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define      LONGLONGV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E) /* added by MR December 2005 */
+#define     LONGLONGVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E) /* added by MR December 2005 */
+#define    LONGLONGVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E) /* added by MR December 2005 */
+#define   LONGLONGVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E) /* added by MR December 2005 */
+#define  LONGLONGVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E) /* added by MR December 2005 */
+#define LONGLONGVVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E) /* added by MR December 2005 */
+#define LONGLONGVVVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E) /* added by MR December 2005 */
+#define         SHORTV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define        SHORTVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define       SHORTVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define      SHORTVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define     SHORTVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define    SHORTVVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define   SHORTVVVVVVV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define          PBYTE_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define        PDOUBLE_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define         PFLOAT_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define           PINT_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define       PLOGICAL_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,PLOGICAL,A,B,C,D,E)
+#define          PLONG_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define      PLONGLONG_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E) /* added by MR December 2005 */
+#define         PSHORT_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define         STRING_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,STRING,A,B,C,D,E)
+#define        PSTRING_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,PSTRING,A,B,C,D,E)
+#define        STRINGV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,STRINGV,A,B,C,D,E)
+#define       PSTRINGV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,PSTRINGV,A,B,C,D,E)
+#define       PNSTRING_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,PNSTRING,A,B,C,D,E)
+#define       PPSTRING_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,PPSTRING,A,B,C,D,E)
+#define          PVOID_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define        ROUTINE_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define         SIMPLE_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,DEFAULT,A,B,C,D,E)
+#define        ZTRINGV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,ZTRINGV,A,B,C,D,E)
+#define       PZTRINGV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,PZTRINGV,A,B,C,D,E)
+#define           CF_0_cfSTR(N,T,A,B,C,D,E)
+
+/* See ACF table comments, which explain why CCF was split into two. */
+#define CCF(NAME,TN,I)     _(TN,_cfSTR)(5,C,NAME,I,_(A,I),_(B,I),_(C,I))
+#define  DEFAULT_cfC(M,I,A,B,C)
+#define  LOGICAL_cfC(M,I,A,B,C)  A=C2FLOGICAL( A);
+#define PLOGICAL_cfC(M,I,A,B,C) *A=C2FLOGICAL(*A);
+#ifdef vmsFortran
+#define   STRING_cfC(M,I,A,B,C) (B.clen=strlen(A),B.f.dsc$a_pointer=A,         \
+        C==sizeof(char*)||C==(unsigned)(B.clen+1)?B.f.dsc$w_length=B.clen:     \
+          (memset((A)+B.clen,' ',C-B.clen-1),A[B.f.dsc$w_length=C-1]='\0'));
+      /* PSTRING_cfC to beware of array A which does not contain any \0.      */
+#define  PSTRING_cfC(M,I,A,B,C) (B.dsc$a_pointer=A, C==sizeof(char*) ?         \
+             B.dsc$w_length=strlen(A):  (A[C-1]='\0',B.dsc$w_length=strlen(A), \
+       memset((A)+B.dsc$w_length,' ',C-B.dsc$w_length-1), B.dsc$w_length=C-1));
+#else
+#define   STRING_cfC(M,I,A,B,C) (B.nombre=A,B.clen=strlen(A),                             \
+                C==sizeof(char*)||C==(unsigned)(B.clen+1)?B.flen=B.clen:       \
+                        (memset(B.nombre+B.clen,' ',C-B.clen-1),B.nombre[B.flen=C-1]='\0'));
+#define  PSTRING_cfC(M,I,A,B,C) (C==sizeof(char*)? B=strlen(A):                \
+                    (A[C-1]='\0',B=strlen(A),memset((A)+B,' ',C-B-1),B=C-1));
+#endif
+          /* For CRAYFortran for (P)STRINGV_cfC, B.fs is set, but irrelevant. */
+#define  STRINGV_cfC(M,I,A,B,C) \
+        AATRINGV_cfA(    A,B,(C/0xFFFF)*(C%0xFFFF),C/0xFFFF,C%0xFFFF)
+#define PSTRINGV_cfC(M,I,A,B,C) \
+       APATRINGV_cfA(    A,B,(C/0xFFFF)*(C%0xFFFF),C/0xFFFF,C%0xFFFF)
+#define  ZTRINGV_cfC(M,I,A,B,C) \
+        AATRINGV_cfA(    A,B, (_3(M,_ELEMS_,I))*((_3(M,_ELEMLEN_,I))+1),       \
+                              (_3(M,_ELEMS_,I)), (_3(M,_ELEMLEN_,I))+1   )
+#define PZTRINGV_cfC(M,I,A,B,C) \
+       APATRINGV_cfA(    A,B, (_3(M,_ELEMS_,I))*((_3(M,_ELEMLEN_,I))+1),       \
+                              (_3(M,_ELEMS_,I)), (_3(M,_ELEMLEN_,I))+1   )
+
+#define     BYTE_cfCCC(A,B) &A
+#define   DOUBLE_cfCCC(A,B) &A
+#if !defined(__CF__KnR)
+#define    FLOAT_cfCCC(A,B) &A
+                               /* Although the VAX doesn't, at least the      */
+#else                          /* HP and K&R mips promote float arg.'s of     */
+#define    FLOAT_cfCCC(A,B) &B /* unprototyped functions to double. Cannot    */
+#endif                         /* use A here to pass the argument to FORTRAN. */
+#define      INT_cfCCC(A,B) &A
+#define  LOGICAL_cfCCC(A,B) &A
+#define     LONG_cfCCC(A,B) &A
+#define    SHORT_cfCCC(A,B) &A
+#define    PBYTE_cfCCC(A,B)  A
+#define  PDOUBLE_cfCCC(A,B)  A
+#define   PFLOAT_cfCCC(A,B)  A
+#define     PINT_cfCCC(A,B)  A
+#define PLOGICAL_cfCCC(A,B)  B=A       /* B used to keep a common W table. */
+#define    PLONG_cfCCC(A,B)  A
+#define   PSHORT_cfCCC(A,B)  A
+
+#define CCCF(TN,I,M)           _SEP_(TN,M,cfCOMMA) _Icf(3,CC,TN,_(A,I),_(B,I))
+#define        INT_cfCC(T,A,B) _(T,_cfCCC)(A,B) 
+#define       INTV_cfCC(T,A,B)  A
+#define      INTVV_cfCC(T,A,B)  A
+#define     INTVVV_cfCC(T,A,B)  A
+#define    INTVVVV_cfCC(T,A,B)  A
+#define   INTVVVVV_cfCC(T,A,B)  A
+#define  INTVVVVVV_cfCC(T,A,B)  A
+#define INTVVVVVVV_cfCC(T,A,B)  A
+#define       PINT_cfCC(T,A,B) _(T,_cfCCC)(A,B) 
+#define      PVOID_cfCC(T,A,B)  A
+#if defined(apolloFortran) || defined(hpuxFortran800) || defined(AbsoftUNIXFortran)
+#define    ROUTINE_cfCC(T,A,B) &A
+#else
+#define    ROUTINE_cfCC(T,A,B)  A
+#endif
+#define     SIMPLE_cfCC(T,A,B)  A
+#ifdef vmsFortran
+#define     STRING_cfCC(T,A,B) &B.f
+#define    STRINGV_cfCC(T,A,B) &B
+#define    PSTRING_cfCC(T,A,B) &B
+#define   PSTRINGV_cfCC(T,A,B) &B
+#else
+#ifdef CRAYFortran
+#define     STRING_cfCC(T,A,B) _cptofcd(A,B.flen)
+#define    STRINGV_cfCC(T,A,B) _cptofcd(B.s,B.flen)
+#define    PSTRING_cfCC(T,A,B) _cptofcd(A,B)
+#define   PSTRINGV_cfCC(T,A,B) _cptofcd(A,B.flen)
+#else
+#define     STRING_cfCC(T,A,B)  A
+#define    STRINGV_cfCC(T,A,B)  B.fs
+#define    PSTRING_cfCC(T,A,B)  A
+#define   PSTRINGV_cfCC(T,A,B)  B.fs
+#endif
+#endif
+#define    ZTRINGV_cfCC(T,A,B)   STRINGV_cfCC(T,A,B)
+#define   PZTRINGV_cfCC(T,A,B)  PSTRINGV_cfCC(T,A,B)
+
+#define    BYTE_cfX  return A0;
+#define  DOUBLE_cfX  return A0;
+#if ! (defined(FLOATFUNCTIONTYPE)&&defined(ASSIGNFLOAT)&&defined(RETURNFLOAT))
+#define   FLOAT_cfX  return A0;
+#else
+#define   FLOAT_cfX  ASSIGNFLOAT(AA0,A0); return AA0;
+#endif
+#define     INT_cfX  return A0;
+#define LOGICAL_cfX  return F2CLOGICAL(A0);
+#define    LONG_cfX  return A0;
+#define   SHORT_cfX  return A0;
+#define    VOID_cfX  return   ;
+#if defined(vmsFortran) || defined(CRAYFortran)
+#define  STRING_cfX  return kill_trailing(                                     \
+                                      kill_trailing(AA0,CFORTRAN_NON_CHAR),' ');
+#else
+#define  STRING_cfX  return kill_trailing(                                     \
+                                      kill_trailing( A0,CFORTRAN_NON_CHAR),' ');
+#endif
+
+#define CFFUN(NAME) _(__cf__,NAME)
+
+/* Note that we don't use LN here, but we keep it for consistency. */
+#define CCALLSFFUN0(UN,LN) CFFUN(UN)()
+
+#ifdef OLD_VAXC                                  /* Allow %CC-I-PARAMNOTUSED. */
+#pragma standard
+#endif
+
+#define CCALLSFFUN1( UN,LN,T1,                        A1)         \
+        CCALLSFFUN5 (UN,LN,T1,CF_0,CF_0,CF_0,CF_0,A1,0,0,0,0)
+#define CCALLSFFUN2( UN,LN,T1,T2,                     A1,A2)      \
+        CCALLSFFUN5 (UN,LN,T1,T2,CF_0,CF_0,CF_0,A1,A2,0,0,0)
+#define CCALLSFFUN3( UN,LN,T1,T2,T3,                  A1,A2,A3)   \
+        CCALLSFFUN5 (UN,LN,T1,T2,T3,CF_0,CF_0,A1,A2,A3,0,0)
+#define CCALLSFFUN4( UN,LN,T1,T2,T3,T4,               A1,A2,A3,A4)\
+        CCALLSFFUN5 (UN,LN,T1,T2,T3,T4,CF_0,A1,A2,A3,A4,0)
+#define CCALLSFFUN5( UN,LN,T1,T2,T3,T4,T5,            A1,A2,A3,A4,A5)          \
+        CCALLSFFUN10(UN,LN,T1,T2,T3,T4,T5,CF_0,CF_0,CF_0,CF_0,CF_0,A1,A2,A3,A4,A5,0,0,0,0,0)
+#define CCALLSFFUN6( UN,LN,T1,T2,T3,T4,T5,T6,         A1,A2,A3,A4,A5,A6)       \
+        CCALLSFFUN10(UN,LN,T1,T2,T3,T4,T5,T6,CF_0,CF_0,CF_0,CF_0,A1,A2,A3,A4,A5,A6,0,0,0,0)
+#define CCALLSFFUN7( UN,LN,T1,T2,T3,T4,T5,T6,T7,      A1,A2,A3,A4,A5,A6,A7)    \
+        CCALLSFFUN10(UN,LN,T1,T2,T3,T4,T5,T6,T7,CF_0,CF_0,CF_0,A1,A2,A3,A4,A5,A6,A7,0,0,0)
+#define CCALLSFFUN8( UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,   A1,A2,A3,A4,A5,A6,A7,A8) \
+        CCALLSFFUN10(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,CF_0,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,0,0)
+#define CCALLSFFUN9( UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,A1,A2,A3,A4,A5,A6,A7,A8,A9)\
+        CCALLSFFUN10(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,0)
+#define CCALLSFFUN10(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA)\
+        CCALLSFFUN14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,CF_0,CF_0,CF_0,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,0,0,0,0)
+#define CCALLSFFUN11(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB)\
+        CCALLSFFUN14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,CF_0,CF_0,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,0,0,0)
+#define CCALLSFFUN12(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC)\
+        CCALLSFFUN14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,CF_0,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,0,0)
+#define CCALLSFFUN13(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD)\
+        CCALLSFFUN14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,CF_0,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,0)
+
+#define CCALLSFFUN14(UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,A1,A2,A3,A4,A5,A6,A7,A8,A9,AA,AB,AC,AD,AE)\
+((CFFUN(UN)(  BCF(T1,A1,0) BCF(T2,A2,1) BCF(T3,A3,1) BCF(T4,A4,1) BCF(T5,A5,1) \
+              BCF(T6,A6,1) BCF(T7,A7,1) BCF(T8,A8,1) BCF(T9,A9,1) BCF(TA,AA,1) \
+              BCF(TB,AB,1) BCF(TC,AC,1) BCF(TD,AD,1) BCF(TE,AE,1)              \
+           SCF(T1,LN,1,A1)  SCF(T2,LN,2,A2)  SCF(T3,LN,3,A3)  SCF(T4,LN,4,A4)  \
+           SCF(T5,LN,5,A5)  SCF(T6,LN,6,A6)  SCF(T7,LN,7,A7)  SCF(T8,LN,8,A8)  \
+           SCF(T9,LN,9,A9)  SCF(TA,LN,10,AA) SCF(TB,LN,11,AB) SCF(TC,LN,12,AC) \
+           SCF(TD,LN,13,AD) SCF(TE,LN,14,AE))))
+
+/*  N.B. Create a separate function instead of using (call function, function
+value here) because in order to create the variables needed for the input
+arg.'s which may be const.'s one has to do the creation within {}, but these
+can never be placed within ()'s. Therefore one must create wrapper functions.
+gcc, on the other hand may be able to avoid the wrapper functions. */
+
+/* Prototypes are needed to correctly handle the value returned correctly. N.B.
+Can only have prototype arg.'s with difficulty, a la G... table since FORTRAN
+functions returning strings have extra arg.'s. Don't bother, since this only
+causes a compiler warning to come up when one uses FCALLSCFUNn and CCALLSFFUNn
+for the same function in the same source code. Something done by the experts in
+debugging only.*/    
+
+#define PROTOCCALLSFFUN0(F,UN,LN)                                              \
+_(F,_cfPU)( CFC_(UN,LN))(CF_NULL_PROTO);                                       \
+static _Icf(2,U,F,CFFUN(UN),0)() {_(F,_cfE) _Icf(3,GZ,F,UN,LN) ABSOFT_cf1(F));_(F,_cfX)}
+
+#define PROTOCCALLSFFUN1( T0,UN,LN,T1)                                         \
+        PROTOCCALLSFFUN5 (T0,UN,LN,T1,CF_0,CF_0,CF_0,CF_0)
+#define PROTOCCALLSFFUN2( T0,UN,LN,T1,T2)                                      \
+        PROTOCCALLSFFUN5 (T0,UN,LN,T1,T2,CF_0,CF_0,CF_0)
+#define PROTOCCALLSFFUN3( T0,UN,LN,T1,T2,T3)                                   \
+        PROTOCCALLSFFUN5 (T0,UN,LN,T1,T2,T3,CF_0,CF_0)
+#define PROTOCCALLSFFUN4( T0,UN,LN,T1,T2,T3,T4)                                \
+        PROTOCCALLSFFUN5 (T0,UN,LN,T1,T2,T3,T4,CF_0)
+#define PROTOCCALLSFFUN5( T0,UN,LN,T1,T2,T3,T4,T5)                             \
+        PROTOCCALLSFFUN10(T0,UN,LN,T1,T2,T3,T4,T5,CF_0,CF_0,CF_0,CF_0,CF_0)
+#define PROTOCCALLSFFUN6( T0,UN,LN,T1,T2,T3,T4,T5,T6)                          \
+        PROTOCCALLSFFUN10(T0,UN,LN,T1,T2,T3,T4,T5,T6,CF_0,CF_0,CF_0,CF_0)
+#define PROTOCCALLSFFUN7( T0,UN,LN,T1,T2,T3,T4,T5,T6,T7)                       \
+        PROTOCCALLSFFUN10(T0,UN,LN,T1,T2,T3,T4,T5,T6,T7,CF_0,CF_0,CF_0)
+#define PROTOCCALLSFFUN8( T0,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8)                    \
+        PROTOCCALLSFFUN10(T0,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,CF_0,CF_0)
+#define PROTOCCALLSFFUN9( T0,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9)                 \
+        PROTOCCALLSFFUN10(T0,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,CF_0)
+#define PROTOCCALLSFFUN10(T0,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA)              \
+        PROTOCCALLSFFUN14(T0,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,CF_0,CF_0,CF_0,CF_0)
+#define PROTOCCALLSFFUN11(T0,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB)           \
+        PROTOCCALLSFFUN14(T0,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,CF_0,CF_0,CF_0)
+#define PROTOCCALLSFFUN12(T0,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC)        \
+        PROTOCCALLSFFUN14(T0,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,CF_0,CF_0)
+#define PROTOCCALLSFFUN13(T0,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD)     \
+        PROTOCCALLSFFUN14(T0,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,CF_0)
+
+/* HP/UX 9.01 cc requires the blank between '_Icf(3,G,T0,UN,LN) CCCF(T1,1,0)' */
+
+#ifndef __CF__KnR
+#define PROTOCCALLSFFUN14(T0,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)  \
+ _(T0,_cfPU)(CFC_(UN,LN))(CF_NULL_PROTO); static _Icf(2,U,T0,CFFUN(UN),0)(     \
+   CFARGT14FS(UCF,HCF,_Z,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE) )          \
+{       CFARGT14S(VCF,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)    _(T0,_cfE) \
+ CCF(LN,T1,1)  CCF(LN,T2,2)  CCF(LN,T3,3)  CCF(LN,T4,4)  CCF(LN,T5,5)          \
+ CCF(LN,T6,6)  CCF(LN,T7,7)  CCF(LN,T8,8)  CCF(LN,T9,9)  CCF(LN,TA,10)         \
+ CCF(LN,TB,11) CCF(LN,TC,12) CCF(LN,TD,13) CCF(LN,TE,14)    _Icf(3,G,T0,UN,LN) \
+ CFARGT14(CCCF,JCF,ABSOFT_cf1(T0),T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)); \
+ WCF(T1,A1,1)   WCF(T2,A2,2)   WCF(T3,A3,3)   WCF(T4,A4,4)  WCF(T5,A5,5)       \
+ WCF(T6,A6,6)   WCF(T7,A7,7)   WCF(T8,A8,8)   WCF(T9,A9,9)  WCF(TA,A10,10)     \
+ WCF(TB,A11,11) WCF(TC,A12,12) WCF(TD,A13,13) WCF(TE,A14,14) _(T0,_cfX)}
+#else
+#define PROTOCCALLSFFUN14(T0,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)  \
+ _(T0,_cfPU)(CFC_(UN,LN))(CF_NULL_PROTO); static _Icf(2,U,T0,CFFUN(UN),0)(     \
+   CFARGT14FS(UUCF,HHCF,_Z,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE) )        \
+ CFARGT14FS(UUUCF,HHHCF,_Z,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE) ;        \
+{       CFARGT14S(VCF,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)    _(T0,_cfE) \
+ CCF(LN,T1,1)  CCF(LN,T2,2)  CCF(LN,T3,3)  CCF(LN,T4,4)  CCF(LN,T5,5)          \
+ CCF(LN,T6,6)  CCF(LN,T7,7)  CCF(LN,T8,8)  CCF(LN,T9,9)  CCF(LN,TA,10)         \
+ CCF(LN,TB,11) CCF(LN,TC,12) CCF(LN,TD,13) CCF(LN,TE,14)    _Icf(3,G,T0,UN,LN) \
+ CFARGT14(CCCF,JCF,ABSOFT_cf1(T0),T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)); \
+ WCF(T1,A1,1)   WCF(T2,A2,2)   WCF(T3,A3,3)   WCF(T4,A4,4)   WCF(T5,A5,5)      \
+ WCF(T6,A6,6)   WCF(T7,A7,7)   WCF(T8,A8,8)   WCF(T9,A9,9)   WCF(TA,A10,10)    \
+ WCF(TB,A11,11) WCF(TC,A12,12) WCF(TD,A13,13) WCF(TE,A14,14) _(T0,_cfX)}
+#endif
+
+/*-------------------------------------------------------------------------*/
+
+/*               UTILITIES FOR FORTRAN TO CALL C ROUTINES                  */
+
+#ifdef OLD_VAXC                                /* Prevent %CC-I-PARAMNOTUSED. */
+#pragma nostandard
+#endif
+
+#if defined(vmsFortran) || defined(CRAYFortran)
+#define   DCF(TN,I)
+#define  DDCF(TN,I)
+#define DDDCF(TN,I)
+#else
+#define   DCF(TN,I)          HCF(TN,I)
+#define  DDCF(TN,I)         HHCF(TN,I)
+#define DDDCF(TN,I)        HHHCF(TN,I)
+#endif
+
+#define QCF(TN,I)       _(TN,_cfSTR)(1,Q,_(B,I), 0,0,0,0)
+#define  DEFAULT_cfQ(B)
+#define  LOGICAL_cfQ(B)
+#define PLOGICAL_cfQ(B)
+#define  STRINGV_cfQ(B) char *B; unsigned int _(B,N);
+#define   STRING_cfQ(B) char *B=NULL;
+#define  PSTRING_cfQ(B) char *B=NULL;
+#define PSTRINGV_cfQ(B) STRINGV_cfQ(B)
+#define PNSTRING_cfQ(B) char *B=NULL;
+#define PPSTRING_cfQ(B)
+
+#ifdef     __sgi   /* Else SGI gives warning 182 contrary to its C LRM A.17.7 */
+#define ROUTINE_orig    *(void**)& 
+#else
+#define ROUTINE_orig     (void *)  
+#endif
+
+#define ROUTINE_1     ROUTINE_orig   
+#define ROUTINE_2     ROUTINE_orig   
+#define ROUTINE_3     ROUTINE_orig   
+#define ROUTINE_4     ROUTINE_orig   
+#define ROUTINE_5     ROUTINE_orig   
+#define ROUTINE_6     ROUTINE_orig   
+#define ROUTINE_7     ROUTINE_orig   
+#define ROUTINE_8     ROUTINE_orig   
+#define ROUTINE_9     ROUTINE_orig   
+#define ROUTINE_10    ROUTINE_orig   
+#define ROUTINE_11    ROUTINE_orig   
+#define ROUTINE_12    ROUTINE_orig   
+#define ROUTINE_13    ROUTINE_orig   
+#define ROUTINE_14    ROUTINE_orig   
+#define ROUTINE_15    ROUTINE_orig   
+#define ROUTINE_16    ROUTINE_orig   
+#define ROUTINE_17    ROUTINE_orig   
+#define ROUTINE_18    ROUTINE_orig   
+#define ROUTINE_19    ROUTINE_orig   
+#define ROUTINE_20    ROUTINE_orig   
+#define ROUTINE_21    ROUTINE_orig   
+#define ROUTINE_22    ROUTINE_orig   
+#define ROUTINE_23    ROUTINE_orig   
+#define ROUTINE_24    ROUTINE_orig   
+#define ROUTINE_25    ROUTINE_orig   
+#define ROUTINE_26    ROUTINE_orig   
+#define ROUTINE_27    ROUTINE_orig   
+
+#define TCF(NAME,TN,I,M)              _SEP_(TN,M,cfCOMMA) _(TN,_cfT)(NAME,I,_(A,I),_(B,I),_(C,I))
+#define           BYTE_cfT(M,I,A,B,D) *A
+#define         DOUBLE_cfT(M,I,A,B,D) *A
+#define          FLOAT_cfT(M,I,A,B,D) *A
+#define            INT_cfT(M,I,A,B,D) *A
+#define        LOGICAL_cfT(M,I,A,B,D)  F2CLOGICAL(*A)
+#define           LONG_cfT(M,I,A,B,D) *A
+#define       LONGLONG_cfT(M,I,A,B,D) *A /* added by MR December 2005 */
+#define          SHORT_cfT(M,I,A,B,D) *A
+#define          BYTEV_cfT(M,I,A,B,D)  A
+#define        DOUBLEV_cfT(M,I,A,B,D)  A
+#define         FLOATV_cfT(M,I,A,B,D)  VOIDP A
+#define           INTV_cfT(M,I,A,B,D)  A
+#define       LOGICALV_cfT(M,I,A,B,D)  A
+#define          LONGV_cfT(M,I,A,B,D)  A
+#define      LONGLONGV_cfT(M,I,A,B,D)  A /* added by MR December 2005 */
+#define         SHORTV_cfT(M,I,A,B,D)  A
+#define         BYTEVV_cfT(M,I,A,B,D)  (void *)A /* We have to cast to void *,*/
+#define        BYTEVVV_cfT(M,I,A,B,D)  (void *)A /* since we don't know the   */
+#define       BYTEVVVV_cfT(M,I,A,B,D)  (void *)A /* dimensions of the array.  */
+#define      BYTEVVVVV_cfT(M,I,A,B,D)  (void *)A /* i.e. Unfortunately, can't */
+#define     BYTEVVVVVV_cfT(M,I,A,B,D)  (void *)A /* check that the type       */
+#define    BYTEVVVVVVV_cfT(M,I,A,B,D)  (void *)A /* matches the prototype.    */
+#define       DOUBLEVV_cfT(M,I,A,B,D)  (void *)A
+#define      DOUBLEVVV_cfT(M,I,A,B,D)  (void *)A
+#define     DOUBLEVVVV_cfT(M,I,A,B,D)  (void *)A
+#define    DOUBLEVVVVV_cfT(M,I,A,B,D)  (void *)A
+#define   DOUBLEVVVVVV_cfT(M,I,A,B,D)  (void *)A
+#define  DOUBLEVVVVVVV_cfT(M,I,A,B,D)  (void *)A
+#define        FLOATVV_cfT(M,I,A,B,D)  (void *)A
+#define       FLOATVVV_cfT(M,I,A,B,D)  (void *)A
+#define      FLOATVVVV_cfT(M,I,A,B,D)  (void *)A
+#define     FLOATVVVVV_cfT(M,I,A,B,D)  (void *)A
+#define    FLOATVVVVVV_cfT(M,I,A,B,D)  (void *)A
+#define   FLOATVVVVVVV_cfT(M,I,A,B,D)  (void *)A
+#define          INTVV_cfT(M,I,A,B,D)  (void *)A  
+#define         INTVVV_cfT(M,I,A,B,D)  (void *)A  
+#define        INTVVVV_cfT(M,I,A,B,D)  (void *)A  
+#define       INTVVVVV_cfT(M,I,A,B,D)  (void *)A
+#define      INTVVVVVV_cfT(M,I,A,B,D)  (void *)A
+#define     INTVVVVVVV_cfT(M,I,A,B,D)  (void *)A
+#define      LOGICALVV_cfT(M,I,A,B,D)  (void *)A
+#define     LOGICALVVV_cfT(M,I,A,B,D)  (void *)A
+#define    LOGICALVVVV_cfT(M,I,A,B,D)  (void *)A
+#define   LOGICALVVVVV_cfT(M,I,A,B,D)  (void *)A
+#define  LOGICALVVVVVV_cfT(M,I,A,B,D)  (void *)A
+#define LOGICALVVVVVVV_cfT(M,I,A,B,D)  (void *)A
+#define         LONGVV_cfT(M,I,A,B,D)  (void *)A
+#define        LONGVVV_cfT(M,I,A,B,D)  (void *)A
+#define       LONGVVVV_cfT(M,I,A,B,D)  (void *)A
+#define      LONGVVVVV_cfT(M,I,A,B,D)  (void *)A
+#define     LONGVVVVVV_cfT(M,I,A,B,D)  (void *)A
+#define    LONGVVVVVVV_cfT(M,I,A,B,D)  (void *)A
+#define     LONGLONGVV_cfT(M,I,A,B,D)  (void *)A /* added by MR December 2005 */
+#define    LONGLONGVVV_cfT(M,I,A,B,D)  (void *)A /* added by MR December 2005 */
+#define   LONGLONGVVVV_cfT(M,I,A,B,D)  (void *)A /* added by MR December 2005 */
+#define  LONGLONGVVVVV_cfT(M,I,A,B,D)  (void *)A /* added by MR December 2005 */
+#define LONGLONGVVVVVV_cfT(M,I,A,B,D)  (void *)A /* added by MR December 2005 */
+#define LONGLONGVVVVVVV_cfT(M,I,A,B,D)  (void *)A /* added by MR December 2005 */
+#define        SHORTVV_cfT(M,I,A,B,D)  (void *)A
+#define       SHORTVVV_cfT(M,I,A,B,D)  (void *)A
+#define      SHORTVVVV_cfT(M,I,A,B,D)  (void *)A
+#define     SHORTVVVVV_cfT(M,I,A,B,D)  (void *)A
+#define    SHORTVVVVVV_cfT(M,I,A,B,D)  (void *)A
+#define   SHORTVVVVVVV_cfT(M,I,A,B,D)  (void *)A
+#define          PBYTE_cfT(M,I,A,B,D)  A
+#define        PDOUBLE_cfT(M,I,A,B,D)  A
+#define         PFLOAT_cfT(M,I,A,B,D)  VOIDP A
+#define           PINT_cfT(M,I,A,B,D)  A
+#define       PLOGICAL_cfT(M,I,A,B,D)  ((*A=F2CLOGICAL(*A)),A)
+#define          PLONG_cfT(M,I,A,B,D)  A
+#define      PLONGLONG_cfT(M,I,A,B,D)  A /* added by MR December 2005 */
+#define         PSHORT_cfT(M,I,A,B,D)  A
+#define          PVOID_cfT(M,I,A,B,D)  A
+#if defined(apolloFortran) || defined(hpuxFortran800) || defined(AbsoftUNIXFortran)
+#define        ROUTINE_cfT(M,I,A,B,D)  _(ROUTINE_,I)  (*A)
+#else
+#define        ROUTINE_cfT(M,I,A,B,D)  _(ROUTINE_,I)    A
+#endif
+/* A == pointer to the characters
+   D == length of the string, or of an element in an array of strings
+   E == number of elements in an array of strings                             */
+#define TTSTR(    A,B,D)                                                       \
+           ((B=_cf_malloc(D+1))[D]='\0', memcpy(B,A,D), kill_trailing(B,' '))
+#define TTTTSTR(  A,B,D)   (!(D<4||A[0]||A[1]||A[2]||A[3]))?NULL:              \
+                            memchr(A,'\0',D)                 ?A   : TTSTR(A,B,D)
+#define TTTTSTRV( A,B,D,E) (_(B,N)=E,B=_cf_malloc(_(B,N)*(D+1)), (void *)      \
+  vkill_trailing(f2cstrv(A,B,D+1, _(B,N)*(D+1)), D+1,_(B,N)*(D+1),' '))
+#ifdef vmsFortran
+#define         STRING_cfT(M,I,A,B,D)  TTTTSTR( A->dsc$a_pointer,B,A->dsc$w_length)
+#define        STRINGV_cfT(M,I,A,B,D)  TTTTSTRV(A->dsc$a_pointer, B,           \
+                                             A->dsc$w_length , A->dsc$l_m[0])
+#define        PSTRING_cfT(M,I,A,B,D)    TTSTR( A->dsc$a_pointer,B,A->dsc$w_length)
+#define       PPSTRING_cfT(M,I,A,B,D)           A->dsc$a_pointer
+#else
+#ifdef CRAYFortran
+#define         STRING_cfT(M,I,A,B,D)  TTTTSTR( _fcdtocp(A),B,_fcdlen(A))
+#define        STRINGV_cfT(M,I,A,B,D)  TTTTSTRV(_fcdtocp(A),B,_fcdlen(A),      \
+                              num_elem(_fcdtocp(A),_fcdlen(A),_3(M,_STRV_A,I)))
+#define        PSTRING_cfT(M,I,A,B,D)    TTSTR( _fcdtocp(A),B,_fcdlen(A))
+#define       PPSTRING_cfT(M,I,A,B,D)           _fcdtocp(A)
+#else
+#define         STRING_cfT(M,I,A,B,D)  TTTTSTR( A,B,D)
+#define        STRINGV_cfT(M,I,A,B,D)  TTTTSTRV(A,B,D, num_elem(A,D,_3(M,_STRV_A,I)))
+#define        PSTRING_cfT(M,I,A,B,D)    TTSTR( A,B,D)
+#define       PPSTRING_cfT(M,I,A,B,D)           A
+#endif
+#endif
+#define       PNSTRING_cfT(M,I,A,B,D)    STRING_cfT(M,I,A,B,D)
+#define       PSTRINGV_cfT(M,I,A,B,D)   STRINGV_cfT(M,I,A,B,D)
+#define           CF_0_cfT(M,I,A,B,D)
+
+#define RCF(TN,I)           _(TN,_cfSTR)(3,R,_(A,I),_(B,I),_(C,I),0,0)
+#define  DEFAULT_cfR(A,B,D)
+#define  LOGICAL_cfR(A,B,D)
+#define PLOGICAL_cfR(A,B,D) *A=C2FLOGICAL(*A);
+#define   STRING_cfR(A,B,D) if (B) _cf_free(B);
+#define  STRINGV_cfR(A,B,D) _cf_free(B);
+/* A and D as defined above for TSTRING(V) */
+#define RRRRPSTR( A,B,D)    if (B) memcpy(A,B, _cfMIN(strlen(B),D)),           \
+                  (D>strlen(B)?memset(A+strlen(B),' ', D-strlen(B)):0), _cf_free(B);
+#define RRRRPSTRV(A,B,D)    c2fstrv(B,A,D+1,(D+1)*_(B,N)), _cf_free(B);
+#ifdef vmsFortran
+#define  PSTRING_cfR(A,B,D) RRRRPSTR( A->dsc$a_pointer,B,A->dsc$w_length)
+#define PSTRINGV_cfR(A,B,D) RRRRPSTRV(A->dsc$a_pointer,B,A->dsc$w_length)
+#else
+#ifdef CRAYFortran
+#define  PSTRING_cfR(A,B,D) RRRRPSTR( _fcdtocp(A),B,_fcdlen(A))
+#define PSTRINGV_cfR(A,B,D) RRRRPSTRV(_fcdtocp(A),B,_fcdlen(A))
+#else
+#define  PSTRING_cfR(A,B,D) RRRRPSTR( A,B,D)
+#define PSTRINGV_cfR(A,B,D) RRRRPSTRV(A,B,D)
+#endif
+#endif
+#define PNSTRING_cfR(A,B,D) PSTRING_cfR(A,B,D)
+#define PPSTRING_cfR(A,B,D)
+
+#define    BYTE_cfFZ(UN,LN) INTEGER_BYTE     FCALLSC_QUALIFIER fcallsc(UN,LN)(
+#define  DOUBLE_cfFZ(UN,LN) DOUBLE_PRECISION FCALLSC_QUALIFIER fcallsc(UN,LN)(
+#define     INT_cfFZ(UN,LN) int   FCALLSC_QUALIFIER fcallsc(UN,LN)(
+#define LOGICAL_cfFZ(UN,LN) int   FCALLSC_QUALIFIER fcallsc(UN,LN)(
+#define    LONG_cfFZ(UN,LN) long  FCALLSC_QUALIFIER fcallsc(UN,LN)(
+#define LONGLONG_cfFZ(UN,LN) LONGLONG FCALLSC_QUALIFIER fcallsc(UN,LN)( /* added by MR December 2005 */
+#define   SHORT_cfFZ(UN,LN) short FCALLSC_QUALIFIER fcallsc(UN,LN)(
+#define    VOID_cfFZ(UN,LN) void  FCALLSC_QUALIFIER fcallsc(UN,LN)(
+#ifndef __CF__KnR
+/* The void is req'd by the Apollo, to make this an ANSI function declaration.
+   The Apollo promotes K&R float functions to double. */
+#if defined (f2cFortran) && ! defined (gFortran)
+/* f2c/g77 return double from FORTRAN REAL functions. (KMCCARTY, 2005/12/09) */
+#define FLOAT_cfFZ(UN,LN) DOUBLE_PRECISION FCALLSC_QUALIFIER fcallsc(UN,LN)(void
+#else
+#define FLOAT_cfFZ(UN,LN) FORTRAN_REAL FCALLSC_QUALIFIER fcallsc(UN,LN)(void
+#endif
+#ifdef vmsFortran
+#define  STRING_cfFZ(UN,LN) void  FCALLSC_QUALIFIER fcallsc(UN,LN)(fstring *AS
+#else
+#ifdef CRAYFortran
+#define  STRING_cfFZ(UN,LN) void  FCALLSC_QUALIFIER fcallsc(UN,LN)(_fcd     AS
+#else
+#if  defined(AbsoftUNIXFortran) || defined(AbsoftProFortran)
+#define  STRING_cfFZ(UN,LN) void  FCALLSC_QUALIFIER fcallsc(UN,LN)(char    *AS
+#else
+#define  STRING_cfFZ(UN,LN) void  FCALLSC_QUALIFIER fcallsc(UN,LN)(char    *AS, unsigned D0
+#endif
+#endif
+#endif
+#else
+#if ! (defined(FLOATFUNCTIONTYPE)&&defined(ASSIGNFLOAT)&&defined(RETURNFLOAT))
+#if defined (f2cFortran) && ! defined (gFortran)
+/* f2c/g77 return double from FORTRAN REAL functions. (KMCCARTY, 2005/12/09) */
+#define   FLOAT_cfFZ(UN,LN) DOUBLE_PRECISION  FCALLSC_QUALIFIER fcallsc(UN,LN)(
+#else
+#define   FLOAT_cfFZ(UN,LN) FORTRAN_REAL      FCALLSC_QUALIFIER fcallsc(UN,LN)(
+#endif
+#else
+#define   FLOAT_cfFZ(UN,LN) FLOATFUNCTIONTYPE FCALLSC_QUALIFIER fcallsc(UN,LN)(
+#endif
+#if defined(vmsFortran) || defined(CRAYFortran) || defined(AbsoftUNIXFortran)
+#define  STRING_cfFZ(UN,LN) void  FCALLSC_QUALIFIER fcallsc(UN,LN)(AS
+#else
+#define  STRING_cfFZ(UN,LN) void  FCALLSC_QUALIFIER fcallsc(UN,LN)(AS, D0
+#endif
+#endif
+
+#define    BYTE_cfF(UN,LN)     BYTE_cfFZ(UN,LN)
+#define  DOUBLE_cfF(UN,LN)   DOUBLE_cfFZ(UN,LN)
+#ifndef __CF_KnR
+#if defined (f2cFortran) && ! defined (gFortran)
+/* f2c/g77 return double from FORTRAN REAL functions. (KMCCARTY, 2005/12/09) */
+#define   FLOAT_cfF(UN,LN)  DOUBLE_PRECISION FCALLSC_QUALIFIER fcallsc(UN,LN)(
+#else
+#define   FLOAT_cfF(UN,LN)  FORTRAN_REAL FCALLSC_QUALIFIER fcallsc(UN,LN)(
+#endif
+#else
+#define   FLOAT_cfF(UN,LN)    FLOAT_cfFZ(UN,LN)
+#endif
+#define     INT_cfF(UN,LN)      INT_cfFZ(UN,LN)
+#define LOGICAL_cfF(UN,LN)  LOGICAL_cfFZ(UN,LN)
+#define    LONG_cfF(UN,LN)     LONG_cfFZ(UN,LN)
+#define LONGLONG_cfF(UN,LN) LONGLONG_cfFZ(UN,LN) /* added by MR December 2005 */
+#define   SHORT_cfF(UN,LN)    SHORT_cfFZ(UN,LN)
+#define    VOID_cfF(UN,LN)     VOID_cfFZ(UN,LN)
+#define  STRING_cfF(UN,LN)   STRING_cfFZ(UN,LN),
+
+#define     INT_cfFF
+#define    VOID_cfFF
+#ifdef vmsFortran
+#define  STRING_cfFF           fstring *AS; 
+#else
+#ifdef CRAYFortran
+#define  STRING_cfFF           _fcd     AS;
+#else
+#define  STRING_cfFF           char    *AS; unsigned D0;
+#endif
+#endif
+
+#define     INT_cfL            A0=
+#define  STRING_cfL            A0=
+#define    VOID_cfL                        
+
+#define    INT_cfK
+#define   VOID_cfK
+/* KSTRING copies the string into the position provided by the caller. */
+#ifdef vmsFortran
+#define STRING_cfK                                                             \
+ memcpy(AS->dsc$a_pointer,A0,_cfMIN(AS->dsc$w_length,(A0==NULL?0:strlen(A0))));\
+ AS->dsc$w_length>(A0==NULL?0:strlen(A0))?                                     \
+  memset(AS->dsc$a_pointer+(A0==NULL?0:strlen(A0)),' ',                        \
+         AS->dsc$w_length-(A0==NULL?0:strlen(A0))):0;
+#else
+#ifdef CRAYFortran
+#define STRING_cfK                                                             \
+ memcpy(_fcdtocp(AS),A0, _cfMIN(_fcdlen(AS),(A0==NULL?0:strlen(A0))) );        \
+ _fcdlen(AS)>(A0==NULL?0:strlen(A0))?                                          \
+  memset(_fcdtocp(AS)+(A0==NULL?0:strlen(A0)),' ',                             \
+         _fcdlen(AS)-(A0==NULL?0:strlen(A0))):0;
+#else
+#define STRING_cfK         memcpy(AS,A0, _cfMIN(D0,(A0==NULL?0:strlen(A0))) ); \
+                 D0>(A0==NULL?0:strlen(A0))?memset(AS+(A0==NULL?0:strlen(A0)), \
+                                            ' ', D0-(A0==NULL?0:strlen(A0))):0;
+#endif
+#endif
+
+/* Note that K.. and I.. can't be combined since K.. has to access data before
+R.., in order for functions returning strings which are also passed in as
+arguments to work correctly. Note that R.. frees and hence may corrupt the
+string. */
+#define    BYTE_cfI  return A0;
+#define  DOUBLE_cfI  return A0;
+#if ! (defined(FLOATFUNCTIONTYPE)&&defined(ASSIGNFLOAT)&&defined(RETURNFLOAT))
+#define   FLOAT_cfI  return A0;
+#else
+#define   FLOAT_cfI  RETURNFLOAT(A0);
+#endif
+#define     INT_cfI  return A0;
+#ifdef hpuxFortran800
+/* Incredibly, functions must return true as 1, elsewhere .true.==0x01000000. */
+#define LOGICAL_cfI  return ((A0)?1:0);
+#else
+#define LOGICAL_cfI  return C2FLOGICAL(A0);
+#endif
+#define    LONG_cfI  return A0;
+#define LONGLONG_cfI  return A0; /* added by MR December 2005 */
+#define   SHORT_cfI  return A0;
+#define  STRING_cfI  return   ;
+#define    VOID_cfI  return   ;
+
+#ifdef OLD_VAXC                                  /* Allow %CC-I-PARAMNOTUSED. */
+#pragma standard
+#endif
+
+#define FCALLSCSUB0( CN,UN,LN)             FCALLSCFUN0(VOID,CN,UN,LN)
+#define FCALLSCSUB1( CN,UN,LN,T1)          FCALLSCFUN1(VOID,CN,UN,LN,T1)
+#define FCALLSCSUB2( CN,UN,LN,T1,T2)       FCALLSCFUN2(VOID,CN,UN,LN,T1,T2)
+#define FCALLSCSUB3( CN,UN,LN,T1,T2,T3)    FCALLSCFUN3(VOID,CN,UN,LN,T1,T2,T3)
+#define FCALLSCSUB4( CN,UN,LN,T1,T2,T3,T4) \
+    FCALLSCFUN4(VOID,CN,UN,LN,T1,T2,T3,T4)
+#define FCALLSCSUB5( CN,UN,LN,T1,T2,T3,T4,T5) \
+    FCALLSCFUN5(VOID,CN,UN,LN,T1,T2,T3,T4,T5)
+#define FCALLSCSUB6( CN,UN,LN,T1,T2,T3,T4,T5,T6) \
+    FCALLSCFUN6(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6)       
+#define FCALLSCSUB7( CN,UN,LN,T1,T2,T3,T4,T5,T6,T7) \
+    FCALLSCFUN7(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7)
+#define FCALLSCSUB8( CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8) \
+    FCALLSCFUN8(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8)
+#define FCALLSCSUB9( CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9) \
+    FCALLSCFUN9(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9)
+#define FCALLSCSUB10(CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA) \
+   FCALLSCFUN10(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA)
+#define FCALLSCSUB11(CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB) \
+   FCALLSCFUN11(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB)
+#define FCALLSCSUB12(CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC) \
+   FCALLSCFUN12(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC)
+#define FCALLSCSUB13(CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD) \
+   FCALLSCFUN13(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD)
+#define FCALLSCSUB14(CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE) \
+   FCALLSCFUN14(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)
+#define FCALLSCSUB15(CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF) \
+   FCALLSCFUN15(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF)
+#define FCALLSCSUB16(CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG) \
+   FCALLSCFUN16(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG)
+#define FCALLSCSUB17(CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH) \
+   FCALLSCFUN17(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH)
+#define FCALLSCSUB18(CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI) \
+   FCALLSCFUN18(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI)
+#define FCALLSCSUB19(CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ) \
+   FCALLSCFUN19(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ)
+#define FCALLSCSUB20(CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK) \
+   FCALLSCFUN20(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK)
+#define FCALLSCSUB21(CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL) \
+   FCALLSCFUN21(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL)
+#define FCALLSCSUB22(CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM) \
+   FCALLSCFUN22(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM)
+#define FCALLSCSUB23(CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN) \
+   FCALLSCFUN23(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN)
+#define FCALLSCSUB24(CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO) \
+   FCALLSCFUN24(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO)
+#define FCALLSCSUB25(CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP) \
+   FCALLSCFUN25(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP)
+#define FCALLSCSUB26(CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ) \
+   FCALLSCFUN26(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ)
+#define FCALLSCSUB27(CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR) \
+   FCALLSCFUN27(VOID,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR)
+
+
+#define FCALLSCFUN1( T0,CN,UN,LN,T1) \
+        FCALLSCFUN5 (T0,CN,UN,LN,T1,CF_0,CF_0,CF_0,CF_0)
+#define FCALLSCFUN2( T0,CN,UN,LN,T1,T2) \
+        FCALLSCFUN5 (T0,CN,UN,LN,T1,T2,CF_0,CF_0,CF_0)
+#define FCALLSCFUN3( T0,CN,UN,LN,T1,T2,T3) \
+        FCALLSCFUN5 (T0,CN,UN,LN,T1,T2,T3,CF_0,CF_0)
+#define FCALLSCFUN4( T0,CN,UN,LN,T1,T2,T3,T4) \
+        FCALLSCFUN5 (T0,CN,UN,LN,T1,T2,T3,T4,CF_0)
+#define FCALLSCFUN5( T0,CN,UN,LN,T1,T2,T3,T4,T5) \
+        FCALLSCFUN10(T0,CN,UN,LN,T1,T2,T3,T4,T5,CF_0,CF_0,CF_0,CF_0,CF_0)
+#define FCALLSCFUN6( T0,CN,UN,LN,T1,T2,T3,T4,T5,T6) \
+        FCALLSCFUN10(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,CF_0,CF_0,CF_0,CF_0)
+#define FCALLSCFUN7( T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7) \
+        FCALLSCFUN10(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,CF_0,CF_0,CF_0)
+#define FCALLSCFUN8( T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8) \
+        FCALLSCFUN10(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,CF_0,CF_0)
+#define FCALLSCFUN9( T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9) \
+        FCALLSCFUN10(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,CF_0)
+#define FCALLSCFUN10(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA) \
+        FCALLSCFUN14(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,CF_0,CF_0,CF_0,CF_0)
+#define FCALLSCFUN11(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB) \
+        FCALLSCFUN14(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,CF_0,CF_0,CF_0)
+#define FCALLSCFUN12(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC) \
+        FCALLSCFUN14(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,CF_0,CF_0)
+#define FCALLSCFUN13(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD) \
+        FCALLSCFUN14(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,CF_0)
+
+
+#define FCALLSCFUN15(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF) \
+        FCALLSCFUN20(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,CF_0,CF_0,CF_0,CF_0,CF_0)
+#define FCALLSCFUN16(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG) \
+        FCALLSCFUN20(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,CF_0,CF_0,CF_0,CF_0)
+#define FCALLSCFUN17(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH) \
+        FCALLSCFUN20(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,CF_0,CF_0,CF_0)
+#define FCALLSCFUN18(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI) \
+        FCALLSCFUN20(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,CF_0,CF_0)
+#define FCALLSCFUN19(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ) \
+        FCALLSCFUN20(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,CF_0)
+#define FCALLSCFUN20(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK) \
+        FCALLSCFUN27(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0)
+#define FCALLSCFUN21(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL) \
+        FCALLSCFUN27(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0)
+#define FCALLSCFUN22(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM) \
+        FCALLSCFUN27(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,CF_0,CF_0,CF_0,CF_0,CF_0)
+#define FCALLSCFUN23(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN) \
+        FCALLSCFUN27(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,CF_0,CF_0,CF_0,CF_0)
+#define FCALLSCFUN24(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO) \
+        FCALLSCFUN27(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,CF_0,CF_0,CF_0)
+#define FCALLSCFUN25(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP) \
+        FCALLSCFUN27(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,CF_0,CF_0)
+#define FCALLSCFUN26(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ) \
+        FCALLSCFUN27(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,CF_0)
+
+
+#ifndef __CF__KnR
+#define FCALLSCFUN0(T0,CN,UN,LN) CFextern _(T0,_cfFZ)(UN,LN) ABSOFT_cf2(T0))   \
+        {_Icf(2,UU,T0,A0,0); _Icf(0,L,T0,0,0) CN(); _Icf(0,K,T0,0,0) _(T0,_cfI)}
+
+#define FCALLSCFUN14(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)    \
+                                 CFextern _(T0,_cfF)(UN,LN)                    \
+ CFARGT14(NCF,DCF,ABSOFT_cf2(T0),T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE) )  \
+ {                 CFARGT14S(QCF,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)    \
+  _Icf(2,UU,T0,A0,0); _Icf(0,L,T0,0,0)      CN(    TCF(LN,T1,1,0)  TCF(LN,T2,2,1) \
+    TCF(LN,T3,3,1)  TCF(LN,T4,4,1) TCF(LN,T5,5,1)  TCF(LN,T6,6,1)  TCF(LN,T7,7,1) \
+    TCF(LN,T8,8,1)  TCF(LN,T9,9,1) TCF(LN,TA,10,1) TCF(LN,TB,11,1) TCF(LN,TC,12,1) \
+    TCF(LN,TD,13,1) TCF(LN,TE,14,1) );                          _Icf(0,K,T0,0,0) \
+                   CFARGT14S(RCF,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)  _(T0,_cfI) }
+
+#define FCALLSCFUN27(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR)   \
+                                 CFextern _(T0,_cfF)(UN,LN)                    \
+ CFARGT27(NCF,DCF,ABSOFT_cf2(T0),T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR) ) \
+ {                 CFARGT27S(QCF,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR)   \
+  _Icf(2,UU,T0,A0,0); _Icf(0,L,T0,0,0)      CN(     TCF(LN,T1,1,0)  TCF(LN,T2,2,1)  \
+    TCF(LN,T3,3,1)  TCF(LN,T4,4,1)  TCF(LN,T5,5,1)  TCF(LN,T6,6,1)  TCF(LN,T7,7,1)  \
+    TCF(LN,T8,8,1)  TCF(LN,T9,9,1)  TCF(LN,TA,10,1) TCF(LN,TB,11,1) TCF(LN,TC,12,1) \
+    TCF(LN,TD,13,1) TCF(LN,TE,14,1) TCF(LN,TF,15,1) TCF(LN,TG,16,1) TCF(LN,TH,17,1) \
+    TCF(LN,TI,18,1) TCF(LN,TJ,19,1) TCF(LN,TK,20,1) TCF(LN,TL,21,1) TCF(LN,TM,22,1) \
+    TCF(LN,TN,23,1) TCF(LN,TO,24,1) TCF(LN,TP,25,1) TCF(LN,TQ,26,1) TCF(LN,TR,27,1) ); _Icf(0,K,T0,0,0) \
+                   CFARGT27S(RCF,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR)  _(T0,_cfI) }
+
+#else
+#define FCALLSCFUN0(T0,CN,UN,LN) CFextern _(T0,_cfFZ)(UN,LN) ABSOFT_cf3(T0)) _Icf(0,FF,T0,0,0)\
+        {_Icf(2,UU,T0,A0,0); _Icf(0,L,T0,0,0) CN(); _Icf(0,K,T0,0,0) _(T0,_cfI)}
+
+#define FCALLSCFUN14(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)    \
+                                 CFextern _(T0,_cfF)(UN,LN)                    \
+ CFARGT14(NNCF,DDCF,ABSOFT_cf3(T0),T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)) _Icf(0,FF,T0,0,0) \
+       CFARGT14FS(NNNCF,DDDCF,_Z,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE);   \
+ {                 CFARGT14S(QCF,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)    \
+  _Icf(2,UU,T0,A0,0); _Icf(0,L,T0,0,0)      CN(  TCF(LN,T1,1,0) TCF(LN,T2,2,1) \
+    TCF(LN,T3,3,1) TCF(LN,T4,4,1) TCF(LN,T5,5,1) TCF(LN,T6,6,1) TCF(LN,T7,7,1) \
+    TCF(LN,T8,8,1) TCF(LN,T9,9,1) TCF(LN,TA,10,1) TCF(LN,TB,11,1) TCF(LN,TC,12,1) \
+    TCF(LN,TD,13,1) TCF(LN,TE,14,1) );                          _Icf(0,K,T0,0,0) \
+                   CFARGT14S(RCF,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)  _(T0,_cfI)}
+
+#define FCALLSCFUN27(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR)  \
+                                 CFextern _(T0,_cfF)(UN,LN)                    \
+ CFARGT27(NNCF,DDCF,ABSOFT_cf3(T0),T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR)) _Icf(0,FF,T0,0,0) \
+       CFARGT27FS(NNNCF,DDDCF,_Z,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR); \
+ {                 CFARGT27S(QCF,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR)  \
+  _Icf(2,UU,T0,A0,0); _Icf(0,L,T0,0,0)      CN(     TCF(LN,T1,1,0)  TCF(LN,T2,2,1)  \
+    TCF(LN,T3,3,1)  TCF(LN,T4,4,1)  TCF(LN,T5,5,1)  TCF(LN,T6,6,1)  TCF(LN,T7,7,1)  \
+    TCF(LN,T8,8,1)  TCF(LN,T9,9,1)  TCF(LN,TA,10,1) TCF(LN,TB,11,1) TCF(LN,TC,12,1) \
+    TCF(LN,TD,13,1) TCF(LN,TE,14,1) TCF(LN,TF,15,1) TCF(LN,TG,16,1) TCF(LN,TH,17,1) \
+    TCF(LN,TI,18,1) TCF(LN,TJ,19,1) TCF(LN,TK,20,1) TCF(LN,TL,21,1) TCF(LN,TM,22,1) \
+    TCF(LN,TN,23,1) TCF(LN,TO,24,1) TCF(LN,TP,25,1) TCF(LN,TQ,26,1) TCF(LN,TR,27,1) ); _Icf(0,K,T0,0,0) \
+                   CFARGT27S(RCF,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE,TF,TG,TH,TI,TJ,TK,TL,TM,TN,TO,TP,TQ,TR)  _(T0,_cfI)}
+
+#endif
+
+
+#endif	 /* __CFORTRAN_LOADED */
diff --git a/src/plugins/cfitsio/checksum.c b/src/plugins/cfitsio/checksum.c
new file mode 100644
index 0000000..c52b9ef
--- /dev/null
+++ b/src/plugins/cfitsio/checksum.c
@@ -0,0 +1,508 @@
+/*  This file, checksum.c, contains the checksum-related routines in the   */
+/*  FITSIO library.                                                        */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+
+#include <string.h>
+#include <stdlib.h>
+#include "fitsio2.h"
+/*------------------------------------------------------------------------*/
+int ffcsum(fitsfile *fptr,      /* I - FITS file pointer                  */
+           long nrec,           /* I - number of 2880-byte blocks to sum  */
+           unsigned long *sum,  /* IO - accumulated checksum              */
+           int *status)         /* IO - error status                      */
+/*
+    Calculate a 32-bit 1's complement checksum of the FITS 2880-byte blocks.
+    This routine is based on the C algorithm developed by Rob
+    Seaman at NOAO that was presented at the 1994 ADASS conference,  
+    published in the Astronomical Society of the Pacific Conference Series.
+    This uses a 32-bit 1's complement checksum in which the overflow bits
+    are permuted back into the sum and therefore all bit positions are
+    sampled evenly. 
+*/
+{
+    long ii, jj;
+    unsigned short sbuf[1440];
+    unsigned long hi, lo, hicarry, locarry;
+
+    if (*status > 0)
+        return(*status);
+  /*
+    Sum the specified number of FITS 2880-byte records.  This assumes that
+    the FITSIO file pointer points to the start of the records to be summed.
+    Read each FITS block as 1440 short values (do byte swapping if needed).
+  */
+    for (jj = 0; jj < nrec; jj++)
+    {
+      ffgbyt(fptr, 2880, sbuf, status);
+
+#if BYTESWAPPED
+
+      ffswap2( (short *)sbuf, 1440); /* reverse order of bytes in each value */
+
+#endif
+
+      hi = (*sum >> 16);
+      lo = *sum & 0xFFFF;
+
+      for (ii = 0; ii < 1440; ii += 2)
+      {
+        hi += sbuf[ii];
+        lo += sbuf[ii+1];
+      }
+
+      hicarry = hi >> 16;    /* fold carry bits in */
+      locarry = lo >> 16;
+
+      while (hicarry | locarry)
+      {
+        hi = (hi & 0xFFFF) + locarry;
+        lo = (lo & 0xFFFF) + hicarry;
+        hicarry = hi >> 16;
+        locarry = lo >> 16;
+      }
+
+      *sum = (hi << 16) + lo;
+    }
+    return(*status);
+}
+/*-------------------------------------------------------------------------*/
+void ffesum(unsigned long sum,  /* I - accumulated checksum                */
+           int complm,          /* I - = 1 to encode complement of the sum */
+           char *ascii)         /* O - 16-char ASCII encoded checksum      */
+/*
+    encode the 32 bit checksum by converting every 
+    2 bits of each byte into an ASCII character (32 bit word encoded 
+    as 16 character string).   Only ASCII letters and digits are used
+    to encode the values (no ASCII punctuation characters).
+
+    If complm=TRUE, then the complement of the sum will be encoded.
+
+    This routine is based on the C algorithm developed by Rob
+    Seaman at NOAO that was presented at the 1994 ADASS conference,
+    published in the Astronomical Society of the Pacific Conference Series.
+*/
+{
+    unsigned int exclude[13] = { 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40,
+                                       0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60 };
+    unsigned long mask[4] = { 0xff000000, 0xff0000, 0xff00, 0xff  };
+
+    int offset = 0x30;     /* ASCII 0 (zero) */
+
+    unsigned long value;
+    int byte, quotient, remainder, ch[4], check, ii, jj, kk;
+    char asc[32];
+
+    if (complm)
+        value = 0xFFFFFFFF - sum;   /* complement each bit of the value */
+    else
+        value = sum;
+
+    for (ii = 0; ii < 4; ii++)
+    {
+        byte = (value & mask[ii]) >> (24 - (8 * ii));
+        quotient = byte / 4 + offset;
+        remainder = byte % 4;
+        for (jj = 0; jj < 4; jj++)
+            ch[jj] = quotient;
+
+        ch[0] += remainder;
+
+        for (check = 1; check;)   /* avoid ASCII  punctuation */
+            for (check = 0, kk = 0; kk < 13; kk++)
+                for (jj = 0; jj < 4; jj += 2)
+                    if ((unsigned char) ch[jj] == exclude[kk] ||
+                        (unsigned char) ch[jj+1] == exclude[kk])
+                    {
+                        ch[jj]++;
+                        ch[jj+1]--;
+                        check++;
+                    }
+
+        for (jj = 0; jj < 4; jj++)        /* assign the bytes */
+            asc[4*jj+ii] = ch[jj];
+    }
+
+    for (ii = 0; ii < 16; ii++)       /* shift the bytes 1 to the right */
+        ascii[ii] = asc[(ii+15)%16];
+
+    ascii[16] = '\0';
+}
+/*-------------------------------------------------------------------------*/
+unsigned long ffdsum(char *ascii,  /* I - 16-char ASCII encoded checksum   */
+                     int complm,   /* I - =1 to decode complement of the   */
+                     unsigned long *sum)  /* O - 32-bit checksum           */
+/*
+    decode the 16-char ASCII encoded checksum into an unsigned 32-bit long.
+    If complm=TRUE, then the complement of the sum will be decoded.
+
+    This routine is based on the C algorithm developed by Rob
+    Seaman at NOAO that was presented at the 1994 ADASS conference,
+    published in the Astronomical Society of the Pacific Conference Series.
+*/
+{
+    char cbuf[16];
+    unsigned long hi = 0, lo = 0, hicarry, locarry;
+    int ii;
+
+    /* remove the permuted FITS byte alignment and the ASCII 0 offset */
+    for (ii = 0; ii < 16; ii++)
+    {
+        cbuf[ii] = ascii[(ii+1)%16];
+        cbuf[ii] -= 0x30;
+    }
+
+    for (ii = 0; ii < 16; ii += 4)
+    {
+        hi += (cbuf[ii]   << 8) + cbuf[ii+1];
+        lo += (cbuf[ii+2] << 8) + cbuf[ii+3];
+    }
+
+    hicarry = hi >> 16;
+    locarry = lo >> 16;
+    while (hicarry || locarry)
+    {
+        hi = (hi & 0xFFFF) + locarry;
+        lo = (lo & 0xFFFF) + hicarry;
+        hicarry = hi >> 16;
+        locarry = lo >> 16;
+    }
+
+    *sum = (hi << 16) + lo;
+    if (complm)
+        *sum = 0xFFFFFFFF - *sum;   /* complement each bit of the value */
+
+    return(*sum);
+}
+/*------------------------------------------------------------------------*/
+int ffpcks(fitsfile *fptr,      /* I - FITS file pointer                  */
+           int *status)         /* IO - error status                      */
+/*
+   Create or update the checksum keywords in the CHDU.  These keywords
+   provide a checksum verification of the FITS HDU based on the ASCII
+   coded 1's complement checksum algorithm developed by Rob Seaman at NOAO.
+*/
+{
+    char datestr[20], checksum[FLEN_VALUE], datasum[FLEN_VALUE];
+    char  comm[FLEN_COMMENT], chkcomm[FLEN_COMMENT], datacomm[FLEN_COMMENT];
+    int tstatus;
+    long nrec;
+    LONGLONG headstart, datastart, dataend;
+    unsigned long dsum, olddsum, sum;
+    double tdouble;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    /* generate current date string and construct the keyword comments */
+    ffgstm(datestr, NULL, status);
+    strcpy(chkcomm, "HDU checksum updated ");
+    strcat(chkcomm, datestr);
+    strcpy(datacomm, "data unit checksum updated ");
+    strcat(datacomm, datestr);
+
+    /* write the CHECKSUM keyword if it does not exist */
+    tstatus = *status;
+    if (ffgkys(fptr, "CHECKSUM", checksum, comm, status) == KEY_NO_EXIST)
+    {
+        *status = tstatus;
+        strcpy(checksum, "0000000000000000");
+        ffpkys(fptr, "CHECKSUM", checksum, chkcomm, status);
+    }
+
+    /* write the DATASUM keyword if it does not exist */
+    tstatus = *status;
+    if (ffgkys(fptr, "DATASUM", datasum, comm, status) == KEY_NO_EXIST)
+    {
+        *status = tstatus;
+        olddsum = 0;
+        ffpkys(fptr, "DATASUM", "         0", datacomm, status);
+
+        /* set the CHECKSUM keyword as undefined, if it isn't already */
+        if (strcmp(checksum, "0000000000000000") )
+        {
+            strcpy(checksum, "0000000000000000");
+            ffmkys(fptr, "CHECKSUM", checksum, chkcomm, status);
+        }
+    }
+    else
+    {
+        /* decode the datasum into an unsigned long variable */
+
+        /* olddsum = strtoul(datasum, 0, 10); doesn't work on SUN OS */
+
+        tdouble = atof(datasum);
+        olddsum = (unsigned long) tdouble;
+    }
+
+    /* close header: rewrite END keyword and following blank fill */
+    /* and re-read the required keywords to determine the structure */
+    if (ffrdef(fptr, status) > 0)
+        return(*status);
+
+    if ((fptr->Fptr)->heapsize > 0)
+         ffuptf(fptr, status);  /* update the variable length TFORM values */
+
+    /* write the correct data fill values, if they are not already correct */
+    if (ffpdfl(fptr, status) > 0)
+        return(*status);
+
+    /* calc size of data unit, in FITS 2880-byte blocks */
+    if (ffghadll(fptr, &headstart, &datastart, &dataend, status) > 0)
+        return(*status);
+
+    nrec = (long) ((dataend - datastart) / 2880);
+    dsum = 0;
+
+    if (nrec > 0)
+    {
+        /* accumulate the 32-bit 1's complement checksum */
+        ffmbyt(fptr, datastart, REPORT_EOF, status);
+        if (ffcsum(fptr, nrec, &dsum, status) > 0)
+            return(*status);
+    }
+
+    if (dsum != olddsum)
+    {
+        /* update the DATASUM keyword with the correct value */ 
+        sprintf(datasum, "%lu", dsum);
+        ffmkys(fptr, "DATASUM", datasum, datacomm, status);
+
+        /* set the CHECKSUM keyword as undefined, if it isn't already */
+        if (strcmp(checksum, "0000000000000000") )
+        {
+            strcpy(checksum, "0000000000000000");
+            ffmkys(fptr, "CHECKSUM", checksum, chkcomm, status);
+        }
+    }        
+
+    if (strcmp(checksum, "0000000000000000") )
+    {
+        /* check if CHECKSUM is still OK; move to the start of the header */
+        ffmbyt(fptr, headstart, REPORT_EOF, status);
+
+        /* accumulate the header checksum into the previous data checksum */
+        nrec = (long) ((datastart - headstart) / 2880);
+        sum = dsum;
+        if (ffcsum(fptr, nrec, &sum, status) > 0)
+            return(*status);
+
+        if (sum == 0 || sum == 0xFFFFFFFF)
+           return(*status);            /* CHECKSUM is correct */
+
+        /* Zero the CHECKSUM and recompute the new value */
+        ffmkys(fptr, "CHECKSUM", "0000000000000000", chkcomm, status);
+    }
+
+    /* move to the start of the header */
+    ffmbyt(fptr, headstart, REPORT_EOF, status);
+
+    /* accumulate the header checksum into the previous data checksum */
+    nrec = (long) ((datastart - headstart) / 2880);
+    sum = dsum;
+    if (ffcsum(fptr, nrec, &sum, status) > 0)
+           return(*status);
+
+    /* encode the COMPLEMENT of the checksum into a 16-character string */
+    ffesum(sum, TRUE, checksum);
+
+    /* update the CHECKSUM keyword value with the new string */
+    ffmkys(fptr, "CHECKSUM", checksum, "&", status);
+
+    return(*status);
+}
+/*------------------------------------------------------------------------*/
+int ffupck(fitsfile *fptr,      /* I - FITS file pointer                  */
+           int *status)         /* IO - error status                      */
+/*
+   Update the CHECKSUM keyword value.  This assumes that the DATASUM
+   keyword exists and has the correct value.
+*/
+{
+    char datestr[20], chkcomm[FLEN_COMMENT], comm[FLEN_COMMENT];
+    char checksum[FLEN_VALUE], datasum[FLEN_VALUE];
+    int tstatus;
+    long nrec;
+    LONGLONG headstart, datastart, dataend;
+    unsigned long sum, dsum;
+    double tdouble;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    /* generate current date string and construct the keyword comments */
+    ffgstm(datestr, NULL, status);
+    strcpy(chkcomm, "HDU checksum updated ");
+    strcat(chkcomm, datestr);
+
+    /* get the DATASUM keyword and convert it to a unsigned long */
+    if (ffgkys(fptr, "DATASUM", datasum, comm, status) == KEY_NO_EXIST)
+    {
+        ffpmsg("DATASUM keyword not found (ffupck");
+        return(*status);
+    }
+
+    tdouble = atof(datasum); /* read as a double as a workaround */
+    dsum = (unsigned long) tdouble;
+
+    /* get size of the HDU */
+    if (ffghadll(fptr, &headstart, &datastart, &dataend, status) > 0)
+        return(*status);
+
+    /* get the checksum keyword, if it exists */
+    tstatus = *status;
+    if (ffgkys(fptr, "CHECKSUM", checksum, comm, status) == KEY_NO_EXIST)
+    {
+        *status = tstatus;
+        strcpy(checksum, "0000000000000000");
+        ffpkys(fptr, "CHECKSUM", checksum, chkcomm, status);
+    }
+    else
+    {
+        /* check if CHECKSUM is still OK */
+        /* rewrite END keyword and following blank fill */
+        if (ffwend(fptr, status) > 0)
+            return(*status);
+
+        /* move to the start of the header */
+        ffmbyt(fptr, headstart, REPORT_EOF, status);
+
+        /* accumulate the header checksum into the previous data checksum */
+        nrec = (long) ((datastart - headstart) / 2880);
+        sum = dsum;
+        if (ffcsum(fptr, nrec, &sum, status) > 0)
+           return(*status);
+
+        if (sum == 0 || sum == 0xFFFFFFFF)
+           return(*status);    /* CHECKSUM is already correct */
+
+        /* Zero the CHECKSUM and recompute the new value */
+        ffmkys(fptr, "CHECKSUM", "0000000000000000", chkcomm, status);
+    }
+
+    /* move to the start of the header */
+    ffmbyt(fptr, headstart, REPORT_EOF, status);
+
+    /* accumulate the header checksum into the previous data checksum */
+    nrec = (long) ((datastart - headstart) / 2880);
+    sum = dsum;
+    if (ffcsum(fptr, nrec, &sum, status) > 0)
+           return(*status);
+
+    /* encode the COMPLEMENT of the checksum into a 16-character string */
+    ffesum(sum, TRUE, checksum);
+
+    /* update the CHECKSUM keyword value with the new string */
+    ffmkys(fptr, "CHECKSUM", checksum, "&", status);
+
+    return(*status);
+}
+/*------------------------------------------------------------------------*/
+int ffvcks(fitsfile *fptr,      /* I - FITS file pointer                  */
+           int *datastatus,     /* O - data checksum status               */
+           int *hdustatus,      /* O - hdu checksum status                */
+                                /*     1  verification is correct         */
+                                /*     0  checksum keyword is not present */
+                                /*    -1 verification not correct         */
+           int *status)         /* IO - error status                      */
+/*
+    Verify the HDU by comparing the value of the computed checksums against
+    the values of the DATASUM and CHECKSUM keywords if they are present.
+*/
+{
+    int tstatus;
+    double tdouble;
+    unsigned long datasum, hdusum, olddatasum;
+    char chksum[FLEN_VALUE], comm[FLEN_COMMENT];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    *datastatus = -1;
+    *hdustatus  = -1;
+
+    tstatus = *status;
+    if (ffgkys(fptr, "CHECKSUM", chksum, comm, status) == KEY_NO_EXIST)
+    {
+        *hdustatus = 0;             /* CHECKSUM keyword does not exist */
+        *status = tstatus;
+    }
+    if (chksum[0] == '\0')
+        *hdustatus = 0;    /* all blank checksum means it is undefined */
+
+    if (ffgkys(fptr, "DATASUM", chksum, comm, status) == KEY_NO_EXIST)
+    {
+        *datastatus = 0;            /* DATASUM keyword does not exist */
+        *status = tstatus;
+    }
+    if (chksum[0] == '\0')
+        *datastatus = 0;    /* all blank checksum means it is undefined */
+
+    if ( *status > 0 || (!(*hdustatus) && !(*datastatus)) )
+        return(*status);            /* return if neither keywords exist */
+
+    /* convert string to unsigned long */
+
+    /* olddatasum = strtoul(chksum, 0, 10);  doesn't work w/ gcc on SUN OS */
+    /* sscanf(chksum, "%u", &olddatasum);   doesn't work w/ cc on VAX/VMS */
+
+    tdouble = atof(chksum); /* read as a double as a workaround */
+    olddatasum = (unsigned long) tdouble;
+
+    /*  calculate the data checksum and the HDU checksum */
+    if (ffgcks(fptr, &datasum, &hdusum, status) > 0)
+        return(*status);
+
+    if (*datastatus)
+        if (datasum == olddatasum)
+            *datastatus = 1;
+
+    if (*hdustatus)
+        if (hdusum == 0 || hdusum == 0xFFFFFFFF)
+            *hdustatus = 1;
+
+    return(*status);
+}
+/*------------------------------------------------------------------------*/
+int ffgcks(fitsfile *fptr,           /* I - FITS file pointer             */
+           unsigned long *datasum,   /* O - data checksum                 */
+           unsigned long *hdusum,    /* O - hdu checksum                  */
+           int *status)              /* IO - error status                 */
+
+    /* calculate the checksums of the data unit and the total HDU */
+{
+    long nrec;
+    LONGLONG headstart, datastart, dataend;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    /* get size of the HDU */
+    if (ffghadll(fptr, &headstart, &datastart, &dataend, status) > 0)
+        return(*status);
+
+    nrec = (long) ((dataend - datastart) / 2880);
+
+    *datasum = 0;
+
+    if (nrec > 0)
+    {
+        /* accumulate the 32-bit 1's complement checksum */
+        ffmbyt(fptr, datastart, REPORT_EOF, status);
+        if (ffcsum(fptr, nrec, datasum, status) > 0)
+            return(*status);
+    }
+
+    /* move to the start of the header and calc. size of header */
+    ffmbyt(fptr, headstart, REPORT_EOF, status);
+    nrec = (long) ((datastart - headstart) / 2880);
+
+    /* accumulate the header checksum into the previous data checksum */
+    *hdusum = *datasum;
+    ffcsum(fptr, nrec, hdusum, status);
+
+    return(*status);
+}
+
diff --git a/src/plugins/cfitsio/crc32.c b/src/plugins/cfitsio/crc32.c
new file mode 100644
index 0000000..08843ff
--- /dev/null
+++ b/src/plugins/cfitsio/crc32.c
@@ -0,0 +1,440 @@
+/* crc32.c -- compute the CRC-32 of a data stream
+ * Copyright (C) 1995-2006, 2010 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ *
+ * Thanks to Rodney Brown <rbrown64 csc com au> for his contribution of faster
+ * CRC methods: exclusive-oring 32 bits of data at a time, and pre-computing
+ * tables for updating the shift register in one step with three exclusive-ors
+ * instead of four steps with four exclusive-ors.  This results in about a
+ * factor of two increase in speed on a Power PC G4 (PPC7455) using gcc -O3.
+ */
+
+/*
+  Note on the use of DYNAMIC_CRC_TABLE: there is no mutex or semaphore
+  protection on the static variables used to control the first-use generation
+  of the crc tables.  Therefore, if you #define DYNAMIC_CRC_TABLE, you should
+  first call get_crc_table() to initialize the tables before allowing more than
+  one thread to use crc32().
+ */
+
+#ifdef MAKECRCH
+#  include <stdio.h>
+#  ifndef DYNAMIC_CRC_TABLE
+#    define DYNAMIC_CRC_TABLE
+#  endif /* !DYNAMIC_CRC_TABLE */
+#endif /* MAKECRCH */
+
+#include "zutil.h"      /* for STDC and FAR definitions */
+
+#define local static
+
+/* Find a four-byte integer type for crc32_little() and crc32_big(). */
+#ifndef NOBYFOUR
+#  ifdef STDC           /* need ANSI C limits.h to determine sizes */
+#    include <limits.h>
+#    define BYFOUR
+#    if (UINT_MAX == 0xffffffffUL)
+       typedef unsigned int u4;
+#    else
+#      if (ULONG_MAX == 0xffffffffUL)
+         typedef unsigned long u4;
+#      else
+#        if (USHRT_MAX == 0xffffffffUL)
+           typedef unsigned short u4;
+#        else
+#          undef BYFOUR     /* can't find a four-byte integer type! */
+#        endif
+#      endif
+#    endif
+#  endif /* STDC */
+#endif /* !NOBYFOUR */
+
+/* Definitions for doing the crc four data bytes at a time. */
+#ifdef BYFOUR
+#  define REV(w) ((((w)>>24)&0xff)+(((w)>>8)&0xff00)+ \
+                (((w)&0xff00)<<8)+(((w)&0xff)<<24))
+   local unsigned long crc32_little OF((unsigned long,
+                        const unsigned char FAR *, unsigned));
+   local unsigned long crc32_big OF((unsigned long,
+                        const unsigned char FAR *, unsigned));
+#  define TBLS 8
+#else
+#  define TBLS 1
+#endif /* BYFOUR */
+
+/* Local functions for crc concatenation */
+local unsigned long gf2_matrix_times OF((unsigned long *mat,
+                                         unsigned long vec));
+local void gf2_matrix_square OF((unsigned long *square, unsigned long *mat));
+local uLong crc32_combine_(uLong crc1, uLong crc2, z_off64_t len2);
+
+
+#ifdef DYNAMIC_CRC_TABLE
+
+local volatile int crc_table_empty = 1;
+local unsigned long FAR crc_table[TBLS][256];
+local void make_crc_table OF((void));
+#ifdef MAKECRCH
+   local void write_table OF((FILE *, const unsigned long FAR *));
+#endif /* MAKECRCH */
+/*
+  Generate tables for a byte-wise 32-bit CRC calculation on the polynomial:
+  x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1.
+
+  Polynomials over GF(2) are represented in binary, one bit per coefficient,
+  with the lowest powers in the most significant bit.  Then adding polynomials
+  is just exclusive-or, and multiplying a polynomial by x is a right shift by
+  one.  If we call the above polynomial p, and represent a byte as the
+  polynomial q, also with the lowest power in the most significant bit (so the
+  byte 0xb1 is the polynomial x^7+x^3+x+1), then the CRC is (q*x^32) mod p,
+  where a mod b means the remainder after dividing a by b.
+
+  This calculation is done using the shift-register method of multiplying and
+  taking the remainder.  The register is initialized to zero, and for each
+  incoming bit, x^32 is added mod p to the register if the bit is a one (where
+  x^32 mod p is p+x^32 = x^26+...+1), and the register is multiplied mod p by
+  x (which is shifting right by one and adding x^32 mod p if the bit shifted
+  out is a one).  We start with the highest power (least significant bit) of
+  q and repeat for all eight bits of q.
+
+  The first table is simply the CRC of all possible eight bit values.  This is
+  all the information needed to generate CRCs on data a byte at a time for all
+  combinations of CRC register values and incoming bytes.  The remaining tables
+  allow for word-at-a-time CRC calculation for both big-endian and little-
+  endian machines, where a word is four bytes.
+*/
+local void make_crc_table()
+{
+    unsigned long c;
+    int n, k;
+    unsigned long poly;                 /* polynomial exclusive-or pattern */
+    /* terms of polynomial defining this crc (except x^32): */
+    static volatile int first = 1;      /* flag to limit concurrent making */
+    static const unsigned char p[] = {0,1,2,4,5,7,8,10,11,12,16,22,23,26};
+
+    /* See if another task is already doing this (not thread-safe, but better
+       than nothing -- significantly reduces duration of vulnerability in
+       case the advice about DYNAMIC_CRC_TABLE is ignored) */
+    if (first) {
+        first = 0;
+
+        /* make exclusive-or pattern from polynomial (0xedb88320UL) */
+        poly = 0UL;
+        for (n = 0; n < sizeof(p)/sizeof(unsigned char); n++)
+            poly |= 1UL << (31 - p[n]);
+
+        /* generate a crc for every 8-bit value */
+        for (n = 0; n < 256; n++) {
+            c = (unsigned long)n;
+            for (k = 0; k < 8; k++)
+                c = c & 1 ? poly ^ (c >> 1) : c >> 1;
+            crc_table[0][n] = c;
+        }
+
+#ifdef BYFOUR
+        /* generate crc for each value followed by one, two, and three zeros,
+           and then the byte reversal of those as well as the first table */
+        for (n = 0; n < 256; n++) {
+            c = crc_table[0][n];
+            crc_table[4][n] = REV(c);
+            for (k = 1; k < 4; k++) {
+                c = crc_table[0][c & 0xff] ^ (c >> 8);
+                crc_table[k][n] = c;
+                crc_table[k + 4][n] = REV(c);
+            }
+        }
+#endif /* BYFOUR */
+
+        crc_table_empty = 0;
+    }
+    else {      /* not first */
+        /* wait for the other guy to finish (not efficient, but rare) */
+        while (crc_table_empty)
+            ;
+    }
+
+#ifdef MAKECRCH
+    /* write out CRC tables to crc32.h */
+    {
+        FILE *out;
+
+        out = fopen("crc32.h", "w");
+        if (out == NULL) return;
+        fprintf(out, "/* crc32.h -- tables for rapid CRC calculation\n");
+        fprintf(out, " * Generated automatically by crc32.c\n */\n\n");
+        fprintf(out, "local const unsigned long FAR ");
+        fprintf(out, "crc_table[TBLS][256] =\n{\n  {\n");
+        write_table(out, crc_table[0]);
+#  ifdef BYFOUR
+        fprintf(out, "#ifdef BYFOUR\n");
+        for (k = 1; k < 8; k++) {
+            fprintf(out, "  },\n  {\n");
+            write_table(out, crc_table[k]);
+        }
+        fprintf(out, "#endif\n");
+#  endif /* BYFOUR */
+        fprintf(out, "  }\n};\n");
+        fclose(out);
+    }
+#endif /* MAKECRCH */
+}
+
+#ifdef MAKECRCH
+local void write_table(out, table)
+    FILE *out;
+    const unsigned long FAR *table;
+{
+    int n;
+
+    for (n = 0; n < 256; n++)
+        fprintf(out, "%s0x%08lxUL%s", n % 5 ? "" : "    ", table[n],
+                n == 255 ? "\n" : (n % 5 == 4 ? ",\n" : ", "));
+}
+#endif /* MAKECRCH */
+
+#else /* !DYNAMIC_CRC_TABLE */
+/* ========================================================================
+ * Tables of CRC-32s of all single-byte values, made by make_crc_table().
+ */
+#include "crc32.h"
+#endif /* DYNAMIC_CRC_TABLE */
+
+/* =========================================================================
+ * This function can be used by asm versions of crc32()
+ */
+const unsigned long FAR * ZEXPORT get_crc_table()
+{
+#ifdef DYNAMIC_CRC_TABLE
+    if (crc_table_empty)
+        make_crc_table();
+#endif /* DYNAMIC_CRC_TABLE */
+    return (const unsigned long FAR *)crc_table;
+}
+
+/* ========================================================================= */
+#define DO1 crc = crc_table[0][((int)crc ^ (*buf++)) & 0xff] ^ (crc >> 8)
+#define DO8 DO1; DO1; DO1; DO1; DO1; DO1; DO1; DO1
+
+/* ========================================================================= */
+unsigned long ZEXPORT crc32(crc, buf, len)
+    unsigned long crc;
+    const unsigned char FAR *buf;
+    uInt len;
+{
+    if (buf == Z_NULL) return 0UL;
+
+#ifdef DYNAMIC_CRC_TABLE
+    if (crc_table_empty)
+        make_crc_table();
+#endif /* DYNAMIC_CRC_TABLE */
+
+#ifdef BYFOUR
+    if (sizeof(void *) == sizeof(ptrdiff_t)) {
+        u4 endian;
+
+        endian = 1;
+        if (*((unsigned char *)(&endian)))
+            return crc32_little(crc, buf, len);
+        else
+            return crc32_big(crc, buf, len);
+    }
+#endif /* BYFOUR */
+    crc = crc ^ 0xffffffffUL;
+    while (len >= 8) {
+        DO8;
+        len -= 8;
+    }
+    if (len) do {
+        DO1;
+    } while (--len);
+    return crc ^ 0xffffffffUL;
+}
+
+#ifdef BYFOUR
+
+/* ========================================================================= */
+#define DOLIT4 c ^= *buf4++; \
+        c = crc_table[3][c & 0xff] ^ crc_table[2][(c >> 8) & 0xff] ^ \
+            crc_table[1][(c >> 16) & 0xff] ^ crc_table[0][c >> 24]
+#define DOLIT32 DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4
+
+/* ========================================================================= */
+local unsigned long crc32_little(crc, buf, len)
+    unsigned long crc;
+    const unsigned char FAR *buf;
+    unsigned len;
+{
+    register u4 c;
+    register const u4 FAR *buf4;
+
+    c = (u4)crc;
+    c = ~c;
+    while (len && ((ptrdiff_t)buf & 3)) {
+        c = crc_table[0][(c ^ *buf++) & 0xff] ^ (c >> 8);
+        len--;
+    }
+
+    buf4 = (const u4 FAR *)(const void FAR *)buf;
+    while (len >= 32) {
+        DOLIT32;
+        len -= 32;
+    }
+    while (len >= 4) {
+        DOLIT4;
+        len -= 4;
+    }
+    buf = (const unsigned char FAR *)buf4;
+
+    if (len) do {
+        c = crc_table[0][(c ^ *buf++) & 0xff] ^ (c >> 8);
+    } while (--len);
+    c = ~c;
+    return (unsigned long)c;
+}
+
+/* ========================================================================= */
+#define DOBIG4 c ^= *++buf4; \
+        c = crc_table[4][c & 0xff] ^ crc_table[5][(c >> 8) & 0xff] ^ \
+            crc_table[6][(c >> 16) & 0xff] ^ crc_table[7][c >> 24]
+#define DOBIG32 DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4
+
+/* ========================================================================= */
+local unsigned long crc32_big(crc, buf, len)
+    unsigned long crc;
+    const unsigned char FAR *buf;
+    unsigned len;
+{
+    register u4 c;
+    register const u4 FAR *buf4;
+
+    c = REV((u4)crc);
+    c = ~c;
+    while (len && ((ptrdiff_t)buf & 3)) {
+        c = crc_table[4][(c >> 24) ^ *buf++] ^ (c << 8);
+        len--;
+    }
+
+    buf4 = (const u4 FAR *)(const void FAR *)buf;
+    buf4--;
+    while (len >= 32) {
+        DOBIG32;
+        len -= 32;
+    }
+    while (len >= 4) {
+        DOBIG4;
+        len -= 4;
+    }
+    buf4++;
+    buf = (const unsigned char FAR *)buf4;
+
+    if (len) do {
+        c = crc_table[4][(c >> 24) ^ *buf++] ^ (c << 8);
+    } while (--len);
+    c = ~c;
+    return (unsigned long)(REV(c));
+}
+
+#endif /* BYFOUR */
+
+#define GF2_DIM 32      /* dimension of GF(2) vectors (length of CRC) */
+
+/* ========================================================================= */
+local unsigned long gf2_matrix_times(mat, vec)
+    unsigned long *mat;
+    unsigned long vec;
+{
+    unsigned long sum;
+
+    sum = 0;
+    while (vec) {
+        if (vec & 1)
+            sum ^= *mat;
+        vec >>= 1;
+        mat++;
+    }
+    return sum;
+}
+
+/* ========================================================================= */
+local void gf2_matrix_square(square, mat)
+    unsigned long *square;
+    unsigned long *mat;
+{
+    int n;
+
+    for (n = 0; n < GF2_DIM; n++)
+        square[n] = gf2_matrix_times(mat, mat[n]);
+}
+
+/* ========================================================================= */
+local uLong crc32_combine_(crc1, crc2, len2)
+    uLong crc1;
+    uLong crc2;
+    z_off64_t len2;
+{
+    int n;
+    unsigned long row;
+    unsigned long even[GF2_DIM];    /* even-power-of-two zeros operator */
+    unsigned long odd[GF2_DIM];     /* odd-power-of-two zeros operator */
+
+    /* degenerate case (also disallow negative lengths) */
+    if (len2 <= 0)
+        return crc1;
+
+    /* put operator for one zero bit in odd */
+    odd[0] = 0xedb88320UL;          /* CRC-32 polynomial */
+    row = 1;
+    for (n = 1; n < GF2_DIM; n++) {
+        odd[n] = row;
+        row <<= 1;
+    }
+
+    /* put operator for two zero bits in even */
+    gf2_matrix_square(even, odd);
+
+    /* put operator for four zero bits in odd */
+    gf2_matrix_square(odd, even);
+
+    /* apply len2 zeros to crc1 (first square will put the operator for one
+       zero byte, eight zero bits, in even) */
+    do {
+        /* apply zeros operator for this bit of len2 */
+        gf2_matrix_square(even, odd);
+        if (len2 & 1)
+            crc1 = gf2_matrix_times(even, crc1);
+        len2 >>= 1;
+
+        /* if no more bits set, then done */
+        if (len2 == 0)
+            break;
+
+        /* another iteration of the loop with odd and even swapped */
+        gf2_matrix_square(odd, even);
+        if (len2 & 1)
+            crc1 = gf2_matrix_times(odd, crc1);
+        len2 >>= 1;
+
+        /* if no more bits set, then done */
+    } while (len2 != 0);
+
+    /* return combined crc */
+    crc1 ^= crc2;
+    return crc1;
+}
+
+/* ========================================================================= */
+uLong ZEXPORT crc32_combine(crc1, crc2, len2)
+    uLong crc1;
+    uLong crc2;
+    z_off_t len2;
+{
+    return crc32_combine_(crc1, crc2, len2);
+}
+
+uLong ZEXPORT crc32_combine64(crc1, crc2, len2)
+    uLong crc1;
+    uLong crc2;
+    z_off64_t len2;
+{
+    return crc32_combine_(crc1, crc2, len2);
+}
diff --git a/src/plugins/cfitsio/crc32.h b/src/plugins/cfitsio/crc32.h
new file mode 100644
index 0000000..8053b61
--- /dev/null
+++ b/src/plugins/cfitsio/crc32.h
@@ -0,0 +1,441 @@
+/* crc32.h -- tables for rapid CRC calculation
+ * Generated automatically by crc32.c
+ */
+
+local const unsigned long FAR crc_table[TBLS][256] =
+{
+  {
+    0x00000000UL, 0x77073096UL, 0xee0e612cUL, 0x990951baUL, 0x076dc419UL,
+    0x706af48fUL, 0xe963a535UL, 0x9e6495a3UL, 0x0edb8832UL, 0x79dcb8a4UL,
+    0xe0d5e91eUL, 0x97d2d988UL, 0x09b64c2bUL, 0x7eb17cbdUL, 0xe7b82d07UL,
+    0x90bf1d91UL, 0x1db71064UL, 0x6ab020f2UL, 0xf3b97148UL, 0x84be41deUL,
+    0x1adad47dUL, 0x6ddde4ebUL, 0xf4d4b551UL, 0x83d385c7UL, 0x136c9856UL,
+    0x646ba8c0UL, 0xfd62f97aUL, 0x8a65c9ecUL, 0x14015c4fUL, 0x63066cd9UL,
+    0xfa0f3d63UL, 0x8d080df5UL, 0x3b6e20c8UL, 0x4c69105eUL, 0xd56041e4UL,
+    0xa2677172UL, 0x3c03e4d1UL, 0x4b04d447UL, 0xd20d85fdUL, 0xa50ab56bUL,
+    0x35b5a8faUL, 0x42b2986cUL, 0xdbbbc9d6UL, 0xacbcf940UL, 0x32d86ce3UL,
+    0x45df5c75UL, 0xdcd60dcfUL, 0xabd13d59UL, 0x26d930acUL, 0x51de003aUL,
+    0xc8d75180UL, 0xbfd06116UL, 0x21b4f4b5UL, 0x56b3c423UL, 0xcfba9599UL,
+    0xb8bda50fUL, 0x2802b89eUL, 0x5f058808UL, 0xc60cd9b2UL, 0xb10be924UL,
+    0x2f6f7c87UL, 0x58684c11UL, 0xc1611dabUL, 0xb6662d3dUL, 0x76dc4190UL,
+    0x01db7106UL, 0x98d220bcUL, 0xefd5102aUL, 0x71b18589UL, 0x06b6b51fUL,
+    0x9fbfe4a5UL, 0xe8b8d433UL, 0x7807c9a2UL, 0x0f00f934UL, 0x9609a88eUL,
+    0xe10e9818UL, 0x7f6a0dbbUL, 0x086d3d2dUL, 0x91646c97UL, 0xe6635c01UL,
+    0x6b6b51f4UL, 0x1c6c6162UL, 0x856530d8UL, 0xf262004eUL, 0x6c0695edUL,
+    0x1b01a57bUL, 0x8208f4c1UL, 0xf50fc457UL, 0x65b0d9c6UL, 0x12b7e950UL,
+    0x8bbeb8eaUL, 0xfcb9887cUL, 0x62dd1ddfUL, 0x15da2d49UL, 0x8cd37cf3UL,
+    0xfbd44c65UL, 0x4db26158UL, 0x3ab551ceUL, 0xa3bc0074UL, 0xd4bb30e2UL,
+    0x4adfa541UL, 0x3dd895d7UL, 0xa4d1c46dUL, 0xd3d6f4fbUL, 0x4369e96aUL,
+    0x346ed9fcUL, 0xad678846UL, 0xda60b8d0UL, 0x44042d73UL, 0x33031de5UL,
+    0xaa0a4c5fUL, 0xdd0d7cc9UL, 0x5005713cUL, 0x270241aaUL, 0xbe0b1010UL,
+    0xc90c2086UL, 0x5768b525UL, 0x206f85b3UL, 0xb966d409UL, 0xce61e49fUL,
+    0x5edef90eUL, 0x29d9c998UL, 0xb0d09822UL, 0xc7d7a8b4UL, 0x59b33d17UL,
+    0x2eb40d81UL, 0xb7bd5c3bUL, 0xc0ba6cadUL, 0xedb88320UL, 0x9abfb3b6UL,
+    0x03b6e20cUL, 0x74b1d29aUL, 0xead54739UL, 0x9dd277afUL, 0x04db2615UL,
+    0x73dc1683UL, 0xe3630b12UL, 0x94643b84UL, 0x0d6d6a3eUL, 0x7a6a5aa8UL,
+    0xe40ecf0bUL, 0x9309ff9dUL, 0x0a00ae27UL, 0x7d079eb1UL, 0xf00f9344UL,
+    0x8708a3d2UL, 0x1e01f268UL, 0x6906c2feUL, 0xf762575dUL, 0x806567cbUL,
+    0x196c3671UL, 0x6e6b06e7UL, 0xfed41b76UL, 0x89d32be0UL, 0x10da7a5aUL,
+    0x67dd4accUL, 0xf9b9df6fUL, 0x8ebeeff9UL, 0x17b7be43UL, 0x60b08ed5UL,
+    0xd6d6a3e8UL, 0xa1d1937eUL, 0x38d8c2c4UL, 0x4fdff252UL, 0xd1bb67f1UL,
+    0xa6bc5767UL, 0x3fb506ddUL, 0x48b2364bUL, 0xd80d2bdaUL, 0xaf0a1b4cUL,
+    0x36034af6UL, 0x41047a60UL, 0xdf60efc3UL, 0xa867df55UL, 0x316e8eefUL,
+    0x4669be79UL, 0xcb61b38cUL, 0xbc66831aUL, 0x256fd2a0UL, 0x5268e236UL,
+    0xcc0c7795UL, 0xbb0b4703UL, 0x220216b9UL, 0x5505262fUL, 0xc5ba3bbeUL,
+    0xb2bd0b28UL, 0x2bb45a92UL, 0x5cb36a04UL, 0xc2d7ffa7UL, 0xb5d0cf31UL,
+    0x2cd99e8bUL, 0x5bdeae1dUL, 0x9b64c2b0UL, 0xec63f226UL, 0x756aa39cUL,
+    0x026d930aUL, 0x9c0906a9UL, 0xeb0e363fUL, 0x72076785UL, 0x05005713UL,
+    0x95bf4a82UL, 0xe2b87a14UL, 0x7bb12baeUL, 0x0cb61b38UL, 0x92d28e9bUL,
+    0xe5d5be0dUL, 0x7cdcefb7UL, 0x0bdbdf21UL, 0x86d3d2d4UL, 0xf1d4e242UL,
+    0x68ddb3f8UL, 0x1fda836eUL, 0x81be16cdUL, 0xf6b9265bUL, 0x6fb077e1UL,
+    0x18b74777UL, 0x88085ae6UL, 0xff0f6a70UL, 0x66063bcaUL, 0x11010b5cUL,
+    0x8f659effUL, 0xf862ae69UL, 0x616bffd3UL, 0x166ccf45UL, 0xa00ae278UL,
+    0xd70dd2eeUL, 0x4e048354UL, 0x3903b3c2UL, 0xa7672661UL, 0xd06016f7UL,
+    0x4969474dUL, 0x3e6e77dbUL, 0xaed16a4aUL, 0xd9d65adcUL, 0x40df0b66UL,
+    0x37d83bf0UL, 0xa9bcae53UL, 0xdebb9ec5UL, 0x47b2cf7fUL, 0x30b5ffe9UL,
+    0xbdbdf21cUL, 0xcabac28aUL, 0x53b39330UL, 0x24b4a3a6UL, 0xbad03605UL,
+    0xcdd70693UL, 0x54de5729UL, 0x23d967bfUL, 0xb3667a2eUL, 0xc4614ab8UL,
+    0x5d681b02UL, 0x2a6f2b94UL, 0xb40bbe37UL, 0xc30c8ea1UL, 0x5a05df1bUL,
+    0x2d02ef8dUL
+#ifdef BYFOUR
+  },
+  {
+    0x00000000UL, 0x191b3141UL, 0x32366282UL, 0x2b2d53c3UL, 0x646cc504UL,
+    0x7d77f445UL, 0x565aa786UL, 0x4f4196c7UL, 0xc8d98a08UL, 0xd1c2bb49UL,
+    0xfaefe88aUL, 0xe3f4d9cbUL, 0xacb54f0cUL, 0xb5ae7e4dUL, 0x9e832d8eUL,
+    0x87981ccfUL, 0x4ac21251UL, 0x53d92310UL, 0x78f470d3UL, 0x61ef4192UL,
+    0x2eaed755UL, 0x37b5e614UL, 0x1c98b5d7UL, 0x05838496UL, 0x821b9859UL,
+    0x9b00a918UL, 0xb02dfadbUL, 0xa936cb9aUL, 0xe6775d5dUL, 0xff6c6c1cUL,
+    0xd4413fdfUL, 0xcd5a0e9eUL, 0x958424a2UL, 0x8c9f15e3UL, 0xa7b24620UL,
+    0xbea97761UL, 0xf1e8e1a6UL, 0xe8f3d0e7UL, 0xc3de8324UL, 0xdac5b265UL,
+    0x5d5daeaaUL, 0x44469febUL, 0x6f6bcc28UL, 0x7670fd69UL, 0x39316baeUL,
+    0x202a5aefUL, 0x0b07092cUL, 0x121c386dUL, 0xdf4636f3UL, 0xc65d07b2UL,
+    0xed705471UL, 0xf46b6530UL, 0xbb2af3f7UL, 0xa231c2b6UL, 0x891c9175UL,
+    0x9007a034UL, 0x179fbcfbUL, 0x0e848dbaUL, 0x25a9de79UL, 0x3cb2ef38UL,
+    0x73f379ffUL, 0x6ae848beUL, 0x41c51b7dUL, 0x58de2a3cUL, 0xf0794f05UL,
+    0xe9627e44UL, 0xc24f2d87UL, 0xdb541cc6UL, 0x94158a01UL, 0x8d0ebb40UL,
+    0xa623e883UL, 0xbf38d9c2UL, 0x38a0c50dUL, 0x21bbf44cUL, 0x0a96a78fUL,
+    0x138d96ceUL, 0x5ccc0009UL, 0x45d73148UL, 0x6efa628bUL, 0x77e153caUL,
+    0xbabb5d54UL, 0xa3a06c15UL, 0x888d3fd6UL, 0x91960e97UL, 0xded79850UL,
+    0xc7cca911UL, 0xece1fad2UL, 0xf5facb93UL, 0x7262d75cUL, 0x6b79e61dUL,
+    0x4054b5deUL, 0x594f849fUL, 0x160e1258UL, 0x0f152319UL, 0x243870daUL,
+    0x3d23419bUL, 0x65fd6ba7UL, 0x7ce65ae6UL, 0x57cb0925UL, 0x4ed03864UL,
+    0x0191aea3UL, 0x188a9fe2UL, 0x33a7cc21UL, 0x2abcfd60UL, 0xad24e1afUL,
+    0xb43fd0eeUL, 0x9f12832dUL, 0x8609b26cUL, 0xc94824abUL, 0xd05315eaUL,
+    0xfb7e4629UL, 0xe2657768UL, 0x2f3f79f6UL, 0x362448b7UL, 0x1d091b74UL,
+    0x04122a35UL, 0x4b53bcf2UL, 0x52488db3UL, 0x7965de70UL, 0x607eef31UL,
+    0xe7e6f3feUL, 0xfefdc2bfUL, 0xd5d0917cUL, 0xcccba03dUL, 0x838a36faUL,
+    0x9a9107bbUL, 0xb1bc5478UL, 0xa8a76539UL, 0x3b83984bUL, 0x2298a90aUL,
+    0x09b5fac9UL, 0x10aecb88UL, 0x5fef5d4fUL, 0x46f46c0eUL, 0x6dd93fcdUL,
+    0x74c20e8cUL, 0xf35a1243UL, 0xea412302UL, 0xc16c70c1UL, 0xd8774180UL,
+    0x9736d747UL, 0x8e2de606UL, 0xa500b5c5UL, 0xbc1b8484UL, 0x71418a1aUL,
+    0x685abb5bUL, 0x4377e898UL, 0x5a6cd9d9UL, 0x152d4f1eUL, 0x0c367e5fUL,
+    0x271b2d9cUL, 0x3e001cddUL, 0xb9980012UL, 0xa0833153UL, 0x8bae6290UL,
+    0x92b553d1UL, 0xddf4c516UL, 0xc4eff457UL, 0xefc2a794UL, 0xf6d996d5UL,
+    0xae07bce9UL, 0xb71c8da8UL, 0x9c31de6bUL, 0x852aef2aUL, 0xca6b79edUL,
+    0xd37048acUL, 0xf85d1b6fUL, 0xe1462a2eUL, 0x66de36e1UL, 0x7fc507a0UL,
+    0x54e85463UL, 0x4df36522UL, 0x02b2f3e5UL, 0x1ba9c2a4UL, 0x30849167UL,
+    0x299fa026UL, 0xe4c5aeb8UL, 0xfdde9ff9UL, 0xd6f3cc3aUL, 0xcfe8fd7bUL,
+    0x80a96bbcUL, 0x99b25afdUL, 0xb29f093eUL, 0xab84387fUL, 0x2c1c24b0UL,
+    0x350715f1UL, 0x1e2a4632UL, 0x07317773UL, 0x4870e1b4UL, 0x516bd0f5UL,
+    0x7a468336UL, 0x635db277UL, 0xcbfad74eUL, 0xd2e1e60fUL, 0xf9ccb5ccUL,
+    0xe0d7848dUL, 0xaf96124aUL, 0xb68d230bUL, 0x9da070c8UL, 0x84bb4189UL,
+    0x03235d46UL, 0x1a386c07UL, 0x31153fc4UL, 0x280e0e85UL, 0x674f9842UL,
+    0x7e54a903UL, 0x5579fac0UL, 0x4c62cb81UL, 0x8138c51fUL, 0x9823f45eUL,
+    0xb30ea79dUL, 0xaa1596dcUL, 0xe554001bUL, 0xfc4f315aUL, 0xd7626299UL,
+    0xce7953d8UL, 0x49e14f17UL, 0x50fa7e56UL, 0x7bd72d95UL, 0x62cc1cd4UL,
+    0x2d8d8a13UL, 0x3496bb52UL, 0x1fbbe891UL, 0x06a0d9d0UL, 0x5e7ef3ecUL,
+    0x4765c2adUL, 0x6c48916eUL, 0x7553a02fUL, 0x3a1236e8UL, 0x230907a9UL,
+    0x0824546aUL, 0x113f652bUL, 0x96a779e4UL, 0x8fbc48a5UL, 0xa4911b66UL,
+    0xbd8a2a27UL, 0xf2cbbce0UL, 0xebd08da1UL, 0xc0fdde62UL, 0xd9e6ef23UL,
+    0x14bce1bdUL, 0x0da7d0fcUL, 0x268a833fUL, 0x3f91b27eUL, 0x70d024b9UL,
+    0x69cb15f8UL, 0x42e6463bUL, 0x5bfd777aUL, 0xdc656bb5UL, 0xc57e5af4UL,
+    0xee530937UL, 0xf7483876UL, 0xb809aeb1UL, 0xa1129ff0UL, 0x8a3fcc33UL,
+    0x9324fd72UL
+  },
+  {
+    0x00000000UL, 0x01c26a37UL, 0x0384d46eUL, 0x0246be59UL, 0x0709a8dcUL,
+    0x06cbc2ebUL, 0x048d7cb2UL, 0x054f1685UL, 0x0e1351b8UL, 0x0fd13b8fUL,
+    0x0d9785d6UL, 0x0c55efe1UL, 0x091af964UL, 0x08d89353UL, 0x0a9e2d0aUL,
+    0x0b5c473dUL, 0x1c26a370UL, 0x1de4c947UL, 0x1fa2771eUL, 0x1e601d29UL,
+    0x1b2f0bacUL, 0x1aed619bUL, 0x18abdfc2UL, 0x1969b5f5UL, 0x1235f2c8UL,
+    0x13f798ffUL, 0x11b126a6UL, 0x10734c91UL, 0x153c5a14UL, 0x14fe3023UL,
+    0x16b88e7aUL, 0x177ae44dUL, 0x384d46e0UL, 0x398f2cd7UL, 0x3bc9928eUL,
+    0x3a0bf8b9UL, 0x3f44ee3cUL, 0x3e86840bUL, 0x3cc03a52UL, 0x3d025065UL,
+    0x365e1758UL, 0x379c7d6fUL, 0x35dac336UL, 0x3418a901UL, 0x3157bf84UL,
+    0x3095d5b3UL, 0x32d36beaUL, 0x331101ddUL, 0x246be590UL, 0x25a98fa7UL,
+    0x27ef31feUL, 0x262d5bc9UL, 0x23624d4cUL, 0x22a0277bUL, 0x20e69922UL,
+    0x2124f315UL, 0x2a78b428UL, 0x2bbade1fUL, 0x29fc6046UL, 0x283e0a71UL,
+    0x2d711cf4UL, 0x2cb376c3UL, 0x2ef5c89aUL, 0x2f37a2adUL, 0x709a8dc0UL,
+    0x7158e7f7UL, 0x731e59aeUL, 0x72dc3399UL, 0x7793251cUL, 0x76514f2bUL,
+    0x7417f172UL, 0x75d59b45UL, 0x7e89dc78UL, 0x7f4bb64fUL, 0x7d0d0816UL,
+    0x7ccf6221UL, 0x798074a4UL, 0x78421e93UL, 0x7a04a0caUL, 0x7bc6cafdUL,
+    0x6cbc2eb0UL, 0x6d7e4487UL, 0x6f38fadeUL, 0x6efa90e9UL, 0x6bb5866cUL,
+    0x6a77ec5bUL, 0x68315202UL, 0x69f33835UL, 0x62af7f08UL, 0x636d153fUL,
+    0x612bab66UL, 0x60e9c151UL, 0x65a6d7d4UL, 0x6464bde3UL, 0x662203baUL,
+    0x67e0698dUL, 0x48d7cb20UL, 0x4915a117UL, 0x4b531f4eUL, 0x4a917579UL,
+    0x4fde63fcUL, 0x4e1c09cbUL, 0x4c5ab792UL, 0x4d98dda5UL, 0x46c49a98UL,
+    0x4706f0afUL, 0x45404ef6UL, 0x448224c1UL, 0x41cd3244UL, 0x400f5873UL,
+    0x4249e62aUL, 0x438b8c1dUL, 0x54f16850UL, 0x55330267UL, 0x5775bc3eUL,
+    0x56b7d609UL, 0x53f8c08cUL, 0x523aaabbUL, 0x507c14e2UL, 0x51be7ed5UL,
+    0x5ae239e8UL, 0x5b2053dfUL, 0x5966ed86UL, 0x58a487b1UL, 0x5deb9134UL,
+    0x5c29fb03UL, 0x5e6f455aUL, 0x5fad2f6dUL, 0xe1351b80UL, 0xe0f771b7UL,
+    0xe2b1cfeeUL, 0xe373a5d9UL, 0xe63cb35cUL, 0xe7fed96bUL, 0xe5b86732UL,
+    0xe47a0d05UL, 0xef264a38UL, 0xeee4200fUL, 0xeca29e56UL, 0xed60f461UL,
+    0xe82fe2e4UL, 0xe9ed88d3UL, 0xebab368aUL, 0xea695cbdUL, 0xfd13b8f0UL,
+    0xfcd1d2c7UL, 0xfe976c9eUL, 0xff5506a9UL, 0xfa1a102cUL, 0xfbd87a1bUL,
+    0xf99ec442UL, 0xf85cae75UL, 0xf300e948UL, 0xf2c2837fUL, 0xf0843d26UL,
+    0xf1465711UL, 0xf4094194UL, 0xf5cb2ba3UL, 0xf78d95faUL, 0xf64fffcdUL,
+    0xd9785d60UL, 0xd8ba3757UL, 0xdafc890eUL, 0xdb3ee339UL, 0xde71f5bcUL,
+    0xdfb39f8bUL, 0xddf521d2UL, 0xdc374be5UL, 0xd76b0cd8UL, 0xd6a966efUL,
+    0xd4efd8b6UL, 0xd52db281UL, 0xd062a404UL, 0xd1a0ce33UL, 0xd3e6706aUL,
+    0xd2241a5dUL, 0xc55efe10UL, 0xc49c9427UL, 0xc6da2a7eUL, 0xc7184049UL,
+    0xc25756ccUL, 0xc3953cfbUL, 0xc1d382a2UL, 0xc011e895UL, 0xcb4dafa8UL,
+    0xca8fc59fUL, 0xc8c97bc6UL, 0xc90b11f1UL, 0xcc440774UL, 0xcd866d43UL,
+    0xcfc0d31aUL, 0xce02b92dUL, 0x91af9640UL, 0x906dfc77UL, 0x922b422eUL,
+    0x93e92819UL, 0x96a63e9cUL, 0x976454abUL, 0x9522eaf2UL, 0x94e080c5UL,
+    0x9fbcc7f8UL, 0x9e7eadcfUL, 0x9c381396UL, 0x9dfa79a1UL, 0x98b56f24UL,
+    0x99770513UL, 0x9b31bb4aUL, 0x9af3d17dUL, 0x8d893530UL, 0x8c4b5f07UL,
+    0x8e0de15eUL, 0x8fcf8b69UL, 0x8a809decUL, 0x8b42f7dbUL, 0x89044982UL,
+    0x88c623b5UL, 0x839a6488UL, 0x82580ebfUL, 0x801eb0e6UL, 0x81dcdad1UL,
+    0x8493cc54UL, 0x8551a663UL, 0x8717183aUL, 0x86d5720dUL, 0xa9e2d0a0UL,
+    0xa820ba97UL, 0xaa6604ceUL, 0xaba46ef9UL, 0xaeeb787cUL, 0xaf29124bUL,
+    0xad6fac12UL, 0xacadc625UL, 0xa7f18118UL, 0xa633eb2fUL, 0xa4755576UL,
+    0xa5b73f41UL, 0xa0f829c4UL, 0xa13a43f3UL, 0xa37cfdaaUL, 0xa2be979dUL,
+    0xb5c473d0UL, 0xb40619e7UL, 0xb640a7beUL, 0xb782cd89UL, 0xb2cddb0cUL,
+    0xb30fb13bUL, 0xb1490f62UL, 0xb08b6555UL, 0xbbd72268UL, 0xba15485fUL,
+    0xb853f606UL, 0xb9919c31UL, 0xbcde8ab4UL, 0xbd1ce083UL, 0xbf5a5edaUL,
+    0xbe9834edUL
+  },
+  {
+    0x00000000UL, 0xb8bc6765UL, 0xaa09c88bUL, 0x12b5afeeUL, 0x8f629757UL,
+    0x37def032UL, 0x256b5fdcUL, 0x9dd738b9UL, 0xc5b428efUL, 0x7d084f8aUL,
+    0x6fbde064UL, 0xd7018701UL, 0x4ad6bfb8UL, 0xf26ad8ddUL, 0xe0df7733UL,
+    0x58631056UL, 0x5019579fUL, 0xe8a530faUL, 0xfa109f14UL, 0x42acf871UL,
+    0xdf7bc0c8UL, 0x67c7a7adUL, 0x75720843UL, 0xcdce6f26UL, 0x95ad7f70UL,
+    0x2d111815UL, 0x3fa4b7fbUL, 0x8718d09eUL, 0x1acfe827UL, 0xa2738f42UL,
+    0xb0c620acUL, 0x087a47c9UL, 0xa032af3eUL, 0x188ec85bUL, 0x0a3b67b5UL,
+    0xb28700d0UL, 0x2f503869UL, 0x97ec5f0cUL, 0x8559f0e2UL, 0x3de59787UL,
+    0x658687d1UL, 0xdd3ae0b4UL, 0xcf8f4f5aUL, 0x7733283fUL, 0xeae41086UL,
+    0x525877e3UL, 0x40edd80dUL, 0xf851bf68UL, 0xf02bf8a1UL, 0x48979fc4UL,
+    0x5a22302aUL, 0xe29e574fUL, 0x7f496ff6UL, 0xc7f50893UL, 0xd540a77dUL,
+    0x6dfcc018UL, 0x359fd04eUL, 0x8d23b72bUL, 0x9f9618c5UL, 0x272a7fa0UL,
+    0xbafd4719UL, 0x0241207cUL, 0x10f48f92UL, 0xa848e8f7UL, 0x9b14583dUL,
+    0x23a83f58UL, 0x311d90b6UL, 0x89a1f7d3UL, 0x1476cf6aUL, 0xaccaa80fUL,
+    0xbe7f07e1UL, 0x06c36084UL, 0x5ea070d2UL, 0xe61c17b7UL, 0xf4a9b859UL,
+    0x4c15df3cUL, 0xd1c2e785UL, 0x697e80e0UL, 0x7bcb2f0eUL, 0xc377486bUL,
+    0xcb0d0fa2UL, 0x73b168c7UL, 0x6104c729UL, 0xd9b8a04cUL, 0x446f98f5UL,
+    0xfcd3ff90UL, 0xee66507eUL, 0x56da371bUL, 0x0eb9274dUL, 0xb6054028UL,
+    0xa4b0efc6UL, 0x1c0c88a3UL, 0x81dbb01aUL, 0x3967d77fUL, 0x2bd27891UL,
+    0x936e1ff4UL, 0x3b26f703UL, 0x839a9066UL, 0x912f3f88UL, 0x299358edUL,
+    0xb4446054UL, 0x0cf80731UL, 0x1e4da8dfUL, 0xa6f1cfbaUL, 0xfe92dfecUL,
+    0x462eb889UL, 0x549b1767UL, 0xec277002UL, 0x71f048bbUL, 0xc94c2fdeUL,
+    0xdbf98030UL, 0x6345e755UL, 0x6b3fa09cUL, 0xd383c7f9UL, 0xc1366817UL,
+    0x798a0f72UL, 0xe45d37cbUL, 0x5ce150aeUL, 0x4e54ff40UL, 0xf6e89825UL,
+    0xae8b8873UL, 0x1637ef16UL, 0x048240f8UL, 0xbc3e279dUL, 0x21e91f24UL,
+    0x99557841UL, 0x8be0d7afUL, 0x335cb0caUL, 0xed59b63bUL, 0x55e5d15eUL,
+    0x47507eb0UL, 0xffec19d5UL, 0x623b216cUL, 0xda874609UL, 0xc832e9e7UL,
+    0x708e8e82UL, 0x28ed9ed4UL, 0x9051f9b1UL, 0x82e4565fUL, 0x3a58313aUL,
+    0xa78f0983UL, 0x1f336ee6UL, 0x0d86c108UL, 0xb53aa66dUL, 0xbd40e1a4UL,
+    0x05fc86c1UL, 0x1749292fUL, 0xaff54e4aUL, 0x322276f3UL, 0x8a9e1196UL,
+    0x982bbe78UL, 0x2097d91dUL, 0x78f4c94bUL, 0xc048ae2eUL, 0xd2fd01c0UL,
+    0x6a4166a5UL, 0xf7965e1cUL, 0x4f2a3979UL, 0x5d9f9697UL, 0xe523f1f2UL,
+    0x4d6b1905UL, 0xf5d77e60UL, 0xe762d18eUL, 0x5fdeb6ebUL, 0xc2098e52UL,
+    0x7ab5e937UL, 0x680046d9UL, 0xd0bc21bcUL, 0x88df31eaUL, 0x3063568fUL,
+    0x22d6f961UL, 0x9a6a9e04UL, 0x07bda6bdUL, 0xbf01c1d8UL, 0xadb46e36UL,
+    0x15080953UL, 0x1d724e9aUL, 0xa5ce29ffUL, 0xb77b8611UL, 0x0fc7e174UL,
+    0x9210d9cdUL, 0x2aacbea8UL, 0x38191146UL, 0x80a57623UL, 0xd8c66675UL,
+    0x607a0110UL, 0x72cfaefeUL, 0xca73c99bUL, 0x57a4f122UL, 0xef189647UL,
+    0xfdad39a9UL, 0x45115eccUL, 0x764dee06UL, 0xcef18963UL, 0xdc44268dUL,
+    0x64f841e8UL, 0xf92f7951UL, 0x41931e34UL, 0x5326b1daUL, 0xeb9ad6bfUL,
+    0xb3f9c6e9UL, 0x0b45a18cUL, 0x19f00e62UL, 0xa14c6907UL, 0x3c9b51beUL,
+    0x842736dbUL, 0x96929935UL, 0x2e2efe50UL, 0x2654b999UL, 0x9ee8defcUL,
+    0x8c5d7112UL, 0x34e11677UL, 0xa9362eceUL, 0x118a49abUL, 0x033fe645UL,
+    0xbb838120UL, 0xe3e09176UL, 0x5b5cf613UL, 0x49e959fdUL, 0xf1553e98UL,
+    0x6c820621UL, 0xd43e6144UL, 0xc68bceaaUL, 0x7e37a9cfUL, 0xd67f4138UL,
+    0x6ec3265dUL, 0x7c7689b3UL, 0xc4caeed6UL, 0x591dd66fUL, 0xe1a1b10aUL,
+    0xf3141ee4UL, 0x4ba87981UL, 0x13cb69d7UL, 0xab770eb2UL, 0xb9c2a15cUL,
+    0x017ec639UL, 0x9ca9fe80UL, 0x241599e5UL, 0x36a0360bUL, 0x8e1c516eUL,
+    0x866616a7UL, 0x3eda71c2UL, 0x2c6fde2cUL, 0x94d3b949UL, 0x090481f0UL,
+    0xb1b8e695UL, 0xa30d497bUL, 0x1bb12e1eUL, 0x43d23e48UL, 0xfb6e592dUL,
+    0xe9dbf6c3UL, 0x516791a6UL, 0xccb0a91fUL, 0x740cce7aUL, 0x66b96194UL,
+    0xde0506f1UL
+  },
+  {
+    0x00000000UL, 0x96300777UL, 0x2c610eeeUL, 0xba510999UL, 0x19c46d07UL,
+    0x8ff46a70UL, 0x35a563e9UL, 0xa395649eUL, 0x3288db0eUL, 0xa4b8dc79UL,
+    0x1ee9d5e0UL, 0x88d9d297UL, 0x2b4cb609UL, 0xbd7cb17eUL, 0x072db8e7UL,
+    0x911dbf90UL, 0x6410b71dUL, 0xf220b06aUL, 0x4871b9f3UL, 0xde41be84UL,
+    0x7dd4da1aUL, 0xebe4dd6dUL, 0x51b5d4f4UL, 0xc785d383UL, 0x56986c13UL,
+    0xc0a86b64UL, 0x7af962fdUL, 0xecc9658aUL, 0x4f5c0114UL, 0xd96c0663UL,
+    0x633d0ffaUL, 0xf50d088dUL, 0xc8206e3bUL, 0x5e10694cUL, 0xe44160d5UL,
+    0x727167a2UL, 0xd1e4033cUL, 0x47d4044bUL, 0xfd850dd2UL, 0x6bb50aa5UL,
+    0xfaa8b535UL, 0x6c98b242UL, 0xd6c9bbdbUL, 0x40f9bcacUL, 0xe36cd832UL,
+    0x755cdf45UL, 0xcf0dd6dcUL, 0x593dd1abUL, 0xac30d926UL, 0x3a00de51UL,
+    0x8051d7c8UL, 0x1661d0bfUL, 0xb5f4b421UL, 0x23c4b356UL, 0x9995bacfUL,
+    0x0fa5bdb8UL, 0x9eb80228UL, 0x0888055fUL, 0xb2d90cc6UL, 0x24e90bb1UL,
+    0x877c6f2fUL, 0x114c6858UL, 0xab1d61c1UL, 0x3d2d66b6UL, 0x9041dc76UL,
+    0x0671db01UL, 0xbc20d298UL, 0x2a10d5efUL, 0x8985b171UL, 0x1fb5b606UL,
+    0xa5e4bf9fUL, 0x33d4b8e8UL, 0xa2c90778UL, 0x34f9000fUL, 0x8ea80996UL,
+    0x18980ee1UL, 0xbb0d6a7fUL, 0x2d3d6d08UL, 0x976c6491UL, 0x015c63e6UL,
+    0xf4516b6bUL, 0x62616c1cUL, 0xd8306585UL, 0x4e0062f2UL, 0xed95066cUL,
+    0x7ba5011bUL, 0xc1f40882UL, 0x57c40ff5UL, 0xc6d9b065UL, 0x50e9b712UL,
+    0xeab8be8bUL, 0x7c88b9fcUL, 0xdf1ddd62UL, 0x492dda15UL, 0xf37cd38cUL,
+    0x654cd4fbUL, 0x5861b24dUL, 0xce51b53aUL, 0x7400bca3UL, 0xe230bbd4UL,
+    0x41a5df4aUL, 0xd795d83dUL, 0x6dc4d1a4UL, 0xfbf4d6d3UL, 0x6ae96943UL,
+    0xfcd96e34UL, 0x468867adUL, 0xd0b860daUL, 0x732d0444UL, 0xe51d0333UL,
+    0x5f4c0aaaUL, 0xc97c0dddUL, 0x3c710550UL, 0xaa410227UL, 0x10100bbeUL,
+    0x86200cc9UL, 0x25b56857UL, 0xb3856f20UL, 0x09d466b9UL, 0x9fe461ceUL,
+    0x0ef9de5eUL, 0x98c9d929UL, 0x2298d0b0UL, 0xb4a8d7c7UL, 0x173db359UL,
+    0x810db42eUL, 0x3b5cbdb7UL, 0xad6cbac0UL, 0x2083b8edUL, 0xb6b3bf9aUL,
+    0x0ce2b603UL, 0x9ad2b174UL, 0x3947d5eaUL, 0xaf77d29dUL, 0x1526db04UL,
+    0x8316dc73UL, 0x120b63e3UL, 0x843b6494UL, 0x3e6a6d0dUL, 0xa85a6a7aUL,
+    0x0bcf0ee4UL, 0x9dff0993UL, 0x27ae000aUL, 0xb19e077dUL, 0x44930ff0UL,
+    0xd2a30887UL, 0x68f2011eUL, 0xfec20669UL, 0x5d5762f7UL, 0xcb676580UL,
+    0x71366c19UL, 0xe7066b6eUL, 0x761bd4feUL, 0xe02bd389UL, 0x5a7ada10UL,
+    0xcc4add67UL, 0x6fdfb9f9UL, 0xf9efbe8eUL, 0x43beb717UL, 0xd58eb060UL,
+    0xe8a3d6d6UL, 0x7e93d1a1UL, 0xc4c2d838UL, 0x52f2df4fUL, 0xf167bbd1UL,
+    0x6757bca6UL, 0xdd06b53fUL, 0x4b36b248UL, 0xda2b0dd8UL, 0x4c1b0aafUL,
+    0xf64a0336UL, 0x607a0441UL, 0xc3ef60dfUL, 0x55df67a8UL, 0xef8e6e31UL,
+    0x79be6946UL, 0x8cb361cbUL, 0x1a8366bcUL, 0xa0d26f25UL, 0x36e26852UL,
+    0x95770cccUL, 0x03470bbbUL, 0xb9160222UL, 0x2f260555UL, 0xbe3bbac5UL,
+    0x280bbdb2UL, 0x925ab42bUL, 0x046ab35cUL, 0xa7ffd7c2UL, 0x31cfd0b5UL,
+    0x8b9ed92cUL, 0x1daede5bUL, 0xb0c2649bUL, 0x26f263ecUL, 0x9ca36a75UL,
+    0x0a936d02UL, 0xa906099cUL, 0x3f360eebUL, 0x85670772UL, 0x13570005UL,
+    0x824abf95UL, 0x147ab8e2UL, 0xae2bb17bUL, 0x381bb60cUL, 0x9b8ed292UL,
+    0x0dbed5e5UL, 0xb7efdc7cUL, 0x21dfdb0bUL, 0xd4d2d386UL, 0x42e2d4f1UL,
+    0xf8b3dd68UL, 0x6e83da1fUL, 0xcd16be81UL, 0x5b26b9f6UL, 0xe177b06fUL,
+    0x7747b718UL, 0xe65a0888UL, 0x706a0fffUL, 0xca3b0666UL, 0x5c0b0111UL,
+    0xff9e658fUL, 0x69ae62f8UL, 0xd3ff6b61UL, 0x45cf6c16UL, 0x78e20aa0UL,
+    0xeed20dd7UL, 0x5483044eUL, 0xc2b30339UL, 0x612667a7UL, 0xf71660d0UL,
+    0x4d476949UL, 0xdb776e3eUL, 0x4a6ad1aeUL, 0xdc5ad6d9UL, 0x660bdf40UL,
+    0xf03bd837UL, 0x53aebca9UL, 0xc59ebbdeUL, 0x7fcfb247UL, 0xe9ffb530UL,
+    0x1cf2bdbdUL, 0x8ac2bacaUL, 0x3093b353UL, 0xa6a3b424UL, 0x0536d0baUL,
+    0x9306d7cdUL, 0x2957de54UL, 0xbf67d923UL, 0x2e7a66b3UL, 0xb84a61c4UL,
+    0x021b685dUL, 0x942b6f2aUL, 0x37be0bb4UL, 0xa18e0cc3UL, 0x1bdf055aUL,
+    0x8def022dUL
+  },
+  {
+    0x00000000UL, 0x41311b19UL, 0x82623632UL, 0xc3532d2bUL, 0x04c56c64UL,
+    0x45f4777dUL, 0x86a75a56UL, 0xc796414fUL, 0x088ad9c8UL, 0x49bbc2d1UL,
+    0x8ae8effaUL, 0xcbd9f4e3UL, 0x0c4fb5acUL, 0x4d7eaeb5UL, 0x8e2d839eUL,
+    0xcf1c9887UL, 0x5112c24aUL, 0x1023d953UL, 0xd370f478UL, 0x9241ef61UL,
+    0x55d7ae2eUL, 0x14e6b537UL, 0xd7b5981cUL, 0x96848305UL, 0x59981b82UL,
+    0x18a9009bUL, 0xdbfa2db0UL, 0x9acb36a9UL, 0x5d5d77e6UL, 0x1c6c6cffUL,
+    0xdf3f41d4UL, 0x9e0e5acdUL, 0xa2248495UL, 0xe3159f8cUL, 0x2046b2a7UL,
+    0x6177a9beUL, 0xa6e1e8f1UL, 0xe7d0f3e8UL, 0x2483dec3UL, 0x65b2c5daUL,
+    0xaaae5d5dUL, 0xeb9f4644UL, 0x28cc6b6fUL, 0x69fd7076UL, 0xae6b3139UL,
+    0xef5a2a20UL, 0x2c09070bUL, 0x6d381c12UL, 0xf33646dfUL, 0xb2075dc6UL,
+    0x715470edUL, 0x30656bf4UL, 0xf7f32abbUL, 0xb6c231a2UL, 0x75911c89UL,
+    0x34a00790UL, 0xfbbc9f17UL, 0xba8d840eUL, 0x79dea925UL, 0x38efb23cUL,
+    0xff79f373UL, 0xbe48e86aUL, 0x7d1bc541UL, 0x3c2ade58UL, 0x054f79f0UL,
+    0x447e62e9UL, 0x872d4fc2UL, 0xc61c54dbUL, 0x018a1594UL, 0x40bb0e8dUL,
+    0x83e823a6UL, 0xc2d938bfUL, 0x0dc5a038UL, 0x4cf4bb21UL, 0x8fa7960aUL,
+    0xce968d13UL, 0x0900cc5cUL, 0x4831d745UL, 0x8b62fa6eUL, 0xca53e177UL,
+    0x545dbbbaUL, 0x156ca0a3UL, 0xd63f8d88UL, 0x970e9691UL, 0x5098d7deUL,
+    0x11a9ccc7UL, 0xd2fae1ecUL, 0x93cbfaf5UL, 0x5cd76272UL, 0x1de6796bUL,
+    0xdeb55440UL, 0x9f844f59UL, 0x58120e16UL, 0x1923150fUL, 0xda703824UL,
+    0x9b41233dUL, 0xa76bfd65UL, 0xe65ae67cUL, 0x2509cb57UL, 0x6438d04eUL,
+    0xa3ae9101UL, 0xe29f8a18UL, 0x21cca733UL, 0x60fdbc2aUL, 0xafe124adUL,
+    0xeed03fb4UL, 0x2d83129fUL, 0x6cb20986UL, 0xab2448c9UL, 0xea1553d0UL,
+    0x29467efbUL, 0x687765e2UL, 0xf6793f2fUL, 0xb7482436UL, 0x741b091dUL,
+    0x352a1204UL, 0xf2bc534bUL, 0xb38d4852UL, 0x70de6579UL, 0x31ef7e60UL,
+    0xfef3e6e7UL, 0xbfc2fdfeUL, 0x7c91d0d5UL, 0x3da0cbccUL, 0xfa368a83UL,
+    0xbb07919aUL, 0x7854bcb1UL, 0x3965a7a8UL, 0x4b98833bUL, 0x0aa99822UL,
+    0xc9fab509UL, 0x88cbae10UL, 0x4f5def5fUL, 0x0e6cf446UL, 0xcd3fd96dUL,
+    0x8c0ec274UL, 0x43125af3UL, 0x022341eaUL, 0xc1706cc1UL, 0x804177d8UL,
+    0x47d73697UL, 0x06e62d8eUL, 0xc5b500a5UL, 0x84841bbcUL, 0x1a8a4171UL,
+    0x5bbb5a68UL, 0x98e87743UL, 0xd9d96c5aUL, 0x1e4f2d15UL, 0x5f7e360cUL,
+    0x9c2d1b27UL, 0xdd1c003eUL, 0x120098b9UL, 0x533183a0UL, 0x9062ae8bUL,
+    0xd153b592UL, 0x16c5f4ddUL, 0x57f4efc4UL, 0x94a7c2efUL, 0xd596d9f6UL,
+    0xe9bc07aeUL, 0xa88d1cb7UL, 0x6bde319cUL, 0x2aef2a85UL, 0xed796bcaUL,
+    0xac4870d3UL, 0x6f1b5df8UL, 0x2e2a46e1UL, 0xe136de66UL, 0xa007c57fUL,
+    0x6354e854UL, 0x2265f34dUL, 0xe5f3b202UL, 0xa4c2a91bUL, 0x67918430UL,
+    0x26a09f29UL, 0xb8aec5e4UL, 0xf99fdefdUL, 0x3accf3d6UL, 0x7bfde8cfUL,
+    0xbc6ba980UL, 0xfd5ab299UL, 0x3e099fb2UL, 0x7f3884abUL, 0xb0241c2cUL,
+    0xf1150735UL, 0x32462a1eUL, 0x73773107UL, 0xb4e17048UL, 0xf5d06b51UL,
+    0x3683467aUL, 0x77b25d63UL, 0x4ed7facbUL, 0x0fe6e1d2UL, 0xccb5ccf9UL,
+    0x8d84d7e0UL, 0x4a1296afUL, 0x0b238db6UL, 0xc870a09dUL, 0x8941bb84UL,
+    0x465d2303UL, 0x076c381aUL, 0xc43f1531UL, 0x850e0e28UL, 0x42984f67UL,
+    0x03a9547eUL, 0xc0fa7955UL, 0x81cb624cUL, 0x1fc53881UL, 0x5ef42398UL,
+    0x9da70eb3UL, 0xdc9615aaUL, 0x1b0054e5UL, 0x5a314ffcUL, 0x996262d7UL,
+    0xd85379ceUL, 0x174fe149UL, 0x567efa50UL, 0x952dd77bUL, 0xd41ccc62UL,
+    0x138a8d2dUL, 0x52bb9634UL, 0x91e8bb1fUL, 0xd0d9a006UL, 0xecf37e5eUL,
+    0xadc26547UL, 0x6e91486cUL, 0x2fa05375UL, 0xe836123aUL, 0xa9070923UL,
+    0x6a542408UL, 0x2b653f11UL, 0xe479a796UL, 0xa548bc8fUL, 0x661b91a4UL,
+    0x272a8abdUL, 0xe0bccbf2UL, 0xa18dd0ebUL, 0x62defdc0UL, 0x23efe6d9UL,
+    0xbde1bc14UL, 0xfcd0a70dUL, 0x3f838a26UL, 0x7eb2913fUL, 0xb924d070UL,
+    0xf815cb69UL, 0x3b46e642UL, 0x7a77fd5bUL, 0xb56b65dcUL, 0xf45a7ec5UL,
+    0x370953eeUL, 0x763848f7UL, 0xb1ae09b8UL, 0xf09f12a1UL, 0x33cc3f8aUL,
+    0x72fd2493UL
+  },
+  {
+    0x00000000UL, 0x376ac201UL, 0x6ed48403UL, 0x59be4602UL, 0xdca80907UL,
+    0xebc2cb06UL, 0xb27c8d04UL, 0x85164f05UL, 0xb851130eUL, 0x8f3bd10fUL,
+    0xd685970dUL, 0xe1ef550cUL, 0x64f91a09UL, 0x5393d808UL, 0x0a2d9e0aUL,
+    0x3d475c0bUL, 0x70a3261cUL, 0x47c9e41dUL, 0x1e77a21fUL, 0x291d601eUL,
+    0xac0b2f1bUL, 0x9b61ed1aUL, 0xc2dfab18UL, 0xf5b56919UL, 0xc8f23512UL,
+    0xff98f713UL, 0xa626b111UL, 0x914c7310UL, 0x145a3c15UL, 0x2330fe14UL,
+    0x7a8eb816UL, 0x4de47a17UL, 0xe0464d38UL, 0xd72c8f39UL, 0x8e92c93bUL,
+    0xb9f80b3aUL, 0x3cee443fUL, 0x0b84863eUL, 0x523ac03cUL, 0x6550023dUL,
+    0x58175e36UL, 0x6f7d9c37UL, 0x36c3da35UL, 0x01a91834UL, 0x84bf5731UL,
+    0xb3d59530UL, 0xea6bd332UL, 0xdd011133UL, 0x90e56b24UL, 0xa78fa925UL,
+    0xfe31ef27UL, 0xc95b2d26UL, 0x4c4d6223UL, 0x7b27a022UL, 0x2299e620UL,
+    0x15f32421UL, 0x28b4782aUL, 0x1fdeba2bUL, 0x4660fc29UL, 0x710a3e28UL,
+    0xf41c712dUL, 0xc376b32cUL, 0x9ac8f52eUL, 0xada2372fUL, 0xc08d9a70UL,
+    0xf7e75871UL, 0xae591e73UL, 0x9933dc72UL, 0x1c259377UL, 0x2b4f5176UL,
+    0x72f11774UL, 0x459bd575UL, 0x78dc897eUL, 0x4fb64b7fUL, 0x16080d7dUL,
+    0x2162cf7cUL, 0xa4748079UL, 0x931e4278UL, 0xcaa0047aUL, 0xfdcac67bUL,
+    0xb02ebc6cUL, 0x87447e6dUL, 0xdefa386fUL, 0xe990fa6eUL, 0x6c86b56bUL,
+    0x5bec776aUL, 0x02523168UL, 0x3538f369UL, 0x087faf62UL, 0x3f156d63UL,
+    0x66ab2b61UL, 0x51c1e960UL, 0xd4d7a665UL, 0xe3bd6464UL, 0xba032266UL,
+    0x8d69e067UL, 0x20cbd748UL, 0x17a11549UL, 0x4e1f534bUL, 0x7975914aUL,
+    0xfc63de4fUL, 0xcb091c4eUL, 0x92b75a4cUL, 0xa5dd984dUL, 0x989ac446UL,
+    0xaff00647UL, 0xf64e4045UL, 0xc1248244UL, 0x4432cd41UL, 0x73580f40UL,
+    0x2ae64942UL, 0x1d8c8b43UL, 0x5068f154UL, 0x67023355UL, 0x3ebc7557UL,
+    0x09d6b756UL, 0x8cc0f853UL, 0xbbaa3a52UL, 0xe2147c50UL, 0xd57ebe51UL,
+    0xe839e25aUL, 0xdf53205bUL, 0x86ed6659UL, 0xb187a458UL, 0x3491eb5dUL,
+    0x03fb295cUL, 0x5a456f5eUL, 0x6d2fad5fUL, 0x801b35e1UL, 0xb771f7e0UL,
+    0xeecfb1e2UL, 0xd9a573e3UL, 0x5cb33ce6UL, 0x6bd9fee7UL, 0x3267b8e5UL,
+    0x050d7ae4UL, 0x384a26efUL, 0x0f20e4eeUL, 0x569ea2ecUL, 0x61f460edUL,
+    0xe4e22fe8UL, 0xd388ede9UL, 0x8a36abebUL, 0xbd5c69eaUL, 0xf0b813fdUL,
+    0xc7d2d1fcUL, 0x9e6c97feUL, 0xa90655ffUL, 0x2c101afaUL, 0x1b7ad8fbUL,
+    0x42c49ef9UL, 0x75ae5cf8UL, 0x48e900f3UL, 0x7f83c2f2UL, 0x263d84f0UL,
+    0x115746f1UL, 0x944109f4UL, 0xa32bcbf5UL, 0xfa958df7UL, 0xcdff4ff6UL,
+    0x605d78d9UL, 0x5737bad8UL, 0x0e89fcdaUL, 0x39e33edbUL, 0xbcf571deUL,
+    0x8b9fb3dfUL, 0xd221f5ddUL, 0xe54b37dcUL, 0xd80c6bd7UL, 0xef66a9d6UL,
+    0xb6d8efd4UL, 0x81b22dd5UL, 0x04a462d0UL, 0x33cea0d1UL, 0x6a70e6d3UL,
+    0x5d1a24d2UL, 0x10fe5ec5UL, 0x27949cc4UL, 0x7e2adac6UL, 0x494018c7UL,
+    0xcc5657c2UL, 0xfb3c95c3UL, 0xa282d3c1UL, 0x95e811c0UL, 0xa8af4dcbUL,
+    0x9fc58fcaUL, 0xc67bc9c8UL, 0xf1110bc9UL, 0x740744ccUL, 0x436d86cdUL,
+    0x1ad3c0cfUL, 0x2db902ceUL, 0x4096af91UL, 0x77fc6d90UL, 0x2e422b92UL,
+    0x1928e993UL, 0x9c3ea696UL, 0xab546497UL, 0xf2ea2295UL, 0xc580e094UL,
+    0xf8c7bc9fUL, 0xcfad7e9eUL, 0x9613389cUL, 0xa179fa9dUL, 0x246fb598UL,
+    0x13057799UL, 0x4abb319bUL, 0x7dd1f39aUL, 0x3035898dUL, 0x075f4b8cUL,
+    0x5ee10d8eUL, 0x698bcf8fUL, 0xec9d808aUL, 0xdbf7428bUL, 0x82490489UL,
+    0xb523c688UL, 0x88649a83UL, 0xbf0e5882UL, 0xe6b01e80UL, 0xd1dadc81UL,
+    0x54cc9384UL, 0x63a65185UL, 0x3a181787UL, 0x0d72d586UL, 0xa0d0e2a9UL,
+    0x97ba20a8UL, 0xce0466aaUL, 0xf96ea4abUL, 0x7c78ebaeUL, 0x4b1229afUL,
+    0x12ac6fadUL, 0x25c6adacUL, 0x1881f1a7UL, 0x2feb33a6UL, 0x765575a4UL,
+    0x413fb7a5UL, 0xc429f8a0UL, 0xf3433aa1UL, 0xaafd7ca3UL, 0x9d97bea2UL,
+    0xd073c4b5UL, 0xe71906b4UL, 0xbea740b6UL, 0x89cd82b7UL, 0x0cdbcdb2UL,
+    0x3bb10fb3UL, 0x620f49b1UL, 0x55658bb0UL, 0x6822d7bbUL, 0x5f4815baUL,
+    0x06f653b8UL, 0x319c91b9UL, 0xb48adebcUL, 0x83e01cbdUL, 0xda5e5abfUL,
+    0xed3498beUL
+  },
+  {
+    0x00000000UL, 0x6567bcb8UL, 0x8bc809aaUL, 0xeeafb512UL, 0x5797628fUL,
+    0x32f0de37UL, 0xdc5f6b25UL, 0xb938d79dUL, 0xef28b4c5UL, 0x8a4f087dUL,
+    0x64e0bd6fUL, 0x018701d7UL, 0xb8bfd64aUL, 0xddd86af2UL, 0x3377dfe0UL,
+    0x56106358UL, 0x9f571950UL, 0xfa30a5e8UL, 0x149f10faUL, 0x71f8ac42UL,
+    0xc8c07bdfUL, 0xada7c767UL, 0x43087275UL, 0x266fcecdUL, 0x707fad95UL,
+    0x1518112dUL, 0xfbb7a43fUL, 0x9ed01887UL, 0x27e8cf1aUL, 0x428f73a2UL,
+    0xac20c6b0UL, 0xc9477a08UL, 0x3eaf32a0UL, 0x5bc88e18UL, 0xb5673b0aUL,
+    0xd00087b2UL, 0x6938502fUL, 0x0c5fec97UL, 0xe2f05985UL, 0x8797e53dUL,
+    0xd1878665UL, 0xb4e03addUL, 0x5a4f8fcfUL, 0x3f283377UL, 0x8610e4eaUL,
+    0xe3775852UL, 0x0dd8ed40UL, 0x68bf51f8UL, 0xa1f82bf0UL, 0xc49f9748UL,
+    0x2a30225aUL, 0x4f579ee2UL, 0xf66f497fUL, 0x9308f5c7UL, 0x7da740d5UL,
+    0x18c0fc6dUL, 0x4ed09f35UL, 0x2bb7238dUL, 0xc518969fUL, 0xa07f2a27UL,
+    0x1947fdbaUL, 0x7c204102UL, 0x928ff410UL, 0xf7e848a8UL, 0x3d58149bUL,
+    0x583fa823UL, 0xb6901d31UL, 0xd3f7a189UL, 0x6acf7614UL, 0x0fa8caacUL,
+    0xe1077fbeUL, 0x8460c306UL, 0xd270a05eUL, 0xb7171ce6UL, 0x59b8a9f4UL,
+    0x3cdf154cUL, 0x85e7c2d1UL, 0xe0807e69UL, 0x0e2fcb7bUL, 0x6b4877c3UL,
+    0xa20f0dcbUL, 0xc768b173UL, 0x29c70461UL, 0x4ca0b8d9UL, 0xf5986f44UL,
+    0x90ffd3fcUL, 0x7e5066eeUL, 0x1b37da56UL, 0x4d27b90eUL, 0x284005b6UL,
+    0xc6efb0a4UL, 0xa3880c1cUL, 0x1ab0db81UL, 0x7fd76739UL, 0x9178d22bUL,
+    0xf41f6e93UL, 0x03f7263bUL, 0x66909a83UL, 0x883f2f91UL, 0xed589329UL,
+    0x546044b4UL, 0x3107f80cUL, 0xdfa84d1eUL, 0xbacff1a6UL, 0xecdf92feUL,
+    0x89b82e46UL, 0x67179b54UL, 0x027027ecUL, 0xbb48f071UL, 0xde2f4cc9UL,
+    0x3080f9dbUL, 0x55e74563UL, 0x9ca03f6bUL, 0xf9c783d3UL, 0x176836c1UL,
+    0x720f8a79UL, 0xcb375de4UL, 0xae50e15cUL, 0x40ff544eUL, 0x2598e8f6UL,
+    0x73888baeUL, 0x16ef3716UL, 0xf8408204UL, 0x9d273ebcUL, 0x241fe921UL,
+    0x41785599UL, 0xafd7e08bUL, 0xcab05c33UL, 0x3bb659edUL, 0x5ed1e555UL,
+    0xb07e5047UL, 0xd519ecffUL, 0x6c213b62UL, 0x094687daUL, 0xe7e932c8UL,
+    0x828e8e70UL, 0xd49eed28UL, 0xb1f95190UL, 0x5f56e482UL, 0x3a31583aUL,
+    0x83098fa7UL, 0xe66e331fUL, 0x08c1860dUL, 0x6da63ab5UL, 0xa4e140bdUL,
+    0xc186fc05UL, 0x2f294917UL, 0x4a4ef5afUL, 0xf3762232UL, 0x96119e8aUL,
+    0x78be2b98UL, 0x1dd99720UL, 0x4bc9f478UL, 0x2eae48c0UL, 0xc001fdd2UL,
+    0xa566416aUL, 0x1c5e96f7UL, 0x79392a4fUL, 0x97969f5dUL, 0xf2f123e5UL,
+    0x05196b4dUL, 0x607ed7f5UL, 0x8ed162e7UL, 0xebb6de5fUL, 0x528e09c2UL,
+    0x37e9b57aUL, 0xd9460068UL, 0xbc21bcd0UL, 0xea31df88UL, 0x8f566330UL,
+    0x61f9d622UL, 0x049e6a9aUL, 0xbda6bd07UL, 0xd8c101bfUL, 0x366eb4adUL,
+    0x53090815UL, 0x9a4e721dUL, 0xff29cea5UL, 0x11867bb7UL, 0x74e1c70fUL,
+    0xcdd91092UL, 0xa8beac2aUL, 0x46111938UL, 0x2376a580UL, 0x7566c6d8UL,
+    0x10017a60UL, 0xfeaecf72UL, 0x9bc973caUL, 0x22f1a457UL, 0x479618efUL,
+    0xa939adfdUL, 0xcc5e1145UL, 0x06ee4d76UL, 0x6389f1ceUL, 0x8d2644dcUL,
+    0xe841f864UL, 0x51792ff9UL, 0x341e9341UL, 0xdab12653UL, 0xbfd69aebUL,
+    0xe9c6f9b3UL, 0x8ca1450bUL, 0x620ef019UL, 0x07694ca1UL, 0xbe519b3cUL,
+    0xdb362784UL, 0x35999296UL, 0x50fe2e2eUL, 0x99b95426UL, 0xfcdee89eUL,
+    0x12715d8cUL, 0x7716e134UL, 0xce2e36a9UL, 0xab498a11UL, 0x45e63f03UL,
+    0x208183bbUL, 0x7691e0e3UL, 0x13f65c5bUL, 0xfd59e949UL, 0x983e55f1UL,
+    0x2106826cUL, 0x44613ed4UL, 0xaace8bc6UL, 0xcfa9377eUL, 0x38417fd6UL,
+    0x5d26c36eUL, 0xb389767cUL, 0xd6eecac4UL, 0x6fd61d59UL, 0x0ab1a1e1UL,
+    0xe41e14f3UL, 0x8179a84bUL, 0xd769cb13UL, 0xb20e77abUL, 0x5ca1c2b9UL,
+    0x39c67e01UL, 0x80fea99cUL, 0xe5991524UL, 0x0b36a036UL, 0x6e511c8eUL,
+    0xa7166686UL, 0xc271da3eUL, 0x2cde6f2cUL, 0x49b9d394UL, 0xf0810409UL,
+    0x95e6b8b1UL, 0x7b490da3UL, 0x1e2eb11bUL, 0x483ed243UL, 0x2d596efbUL,
+    0xc3f6dbe9UL, 0xa6916751UL, 0x1fa9b0ccUL, 0x7ace0c74UL, 0x9461b966UL,
+    0xf10605deUL
+#endif
+  }
+};
diff --git a/src/plugins/cfitsio/deflate.c b/src/plugins/cfitsio/deflate.c
new file mode 100644
index 0000000..1c6a00c
--- /dev/null
+++ b/src/plugins/cfitsio/deflate.c
@@ -0,0 +1,1832 @@
+/* deflate.c -- compress data using the deflation algorithm
+ * Copyright (C) 1995-2010 Jean-loup Gailly and Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/*
+ *  ALGORITHM
+ *
+ *      The "deflation" process depends on being able to identify portions
+ *      of the input text which are identical to earlier input (within a
+ *      sliding window trailing behind the input currently being processed).
+ *
+ *      The most straightforward technique turns out to be the fastest for
+ *      most input files: try all possible matches and select the longest.
+ *      The key feature of this algorithm is that insertions into the string
+ *      dictionary are very simple and thus fast, and deletions are avoided
+ *      completely. Insertions are performed at each input character, whereas
+ *      string matches are performed only when the previous match ends. So it
+ *      is preferable to spend more time in matches to allow very fast string
+ *      insertions and avoid deletions. The matching algorithm for small
+ *      strings is inspired from that of Rabin & Karp. A brute force approach
+ *      is used to find longer strings when a small match has been found.
+ *      A similar algorithm is used in comic (by Jan-Mark Wams) and freeze
+ *      (by Leonid Broukhis).
+ *         A previous version of this file used a more sophisticated algorithm
+ *      (by Fiala and Greene) which is guaranteed to run in linear amortized
+ *      time, but has a larger average cost, uses more memory and is patented.
+ *      However the F&G algorithm may be faster for some highly redundant
+ *      files if the parameter max_chain_length (described below) is too large.
+ *
+ *  ACKNOWLEDGEMENTS
+ *
+ *      The idea of lazy evaluation of matches is due to Jan-Mark Wams, and
+ *      I found it in 'freeze' written by Leonid Broukhis.
+ *      Thanks to many people for bug reports and testing.
+ *
+ *  REFERENCES
+ *
+ *      Deutsch, L.P.,"DEFLATE Compressed Data Format Specification".
+ *      Available in http://www.ietf.org/rfc/rfc1951.txt
+ *
+ *      A description of the Rabin and Karp algorithm is given in the book
+ *         "Algorithms" by R. Sedgewick, Addison-Wesley, p252.
+ *
+ *      Fiala,E.R., and Greene,D.H.
+ *         Data Compression with Finite Windows, Comm.ACM, 32,4 (1989) 490-595
+ *
+ */
+
+#include "deflate.h"
+
+const char deflate_copyright[] =
+   " deflate 1.2.5 Copyright 1995-2010 Jean-loup Gailly and Mark Adler ";
+/*
+  If you use the zlib library in a product, an acknowledgment is welcome
+  in the documentation of your product. If for some reason you cannot
+  include such an acknowledgment, I would appreciate that you keep this
+  copyright string in the executable of your product.
+ */
+
+/* ===========================================================================
+ *  Function prototypes.
+ */
+typedef enum {
+    need_more,      /* block not completed, need more input or more output */
+    block_done,     /* block flush performed */
+    finish_started, /* finish started, need only more output at next deflate */
+    finish_done     /* finish done, accept no more input or output */
+} block_state;
+
+typedef block_state (*compress_func) OF((deflate_state *s, int flush));
+/* Compression function. Returns the block state after the call. */
+
+local void fill_window    OF((deflate_state *s));
+local block_state deflate_stored OF((deflate_state *s, int flush));
+local block_state deflate_fast   OF((deflate_state *s, int flush));
+#ifndef FASTEST
+local block_state deflate_slow   OF((deflate_state *s, int flush));
+#endif
+local block_state deflate_rle    OF((deflate_state *s, int flush));
+local block_state deflate_huff   OF((deflate_state *s, int flush));
+local void lm_init        OF((deflate_state *s));
+local void putShortMSB    OF((deflate_state *s, uInt b));
+local void flush_pending  OF((z_streamp strm));
+local int read_buf        OF((z_streamp strm, Bytef *buf, unsigned size));
+#ifdef ASMV
+      void match_init OF((void)); /* asm code initialization */
+      uInt longest_match  OF((deflate_state *s, IPos cur_match));
+#else
+local uInt longest_match  OF((deflate_state *s, IPos cur_match));
+#endif
+
+#ifdef DEBUG
+local  void check_match OF((deflate_state *s, IPos start, IPos match,
+                            int length));
+#endif
+
+/* ===========================================================================
+ * Local data
+ */
+
+#define NIL 0
+/* Tail of hash chains */
+
+#ifndef TOO_FAR
+#  define TOO_FAR 4096
+#endif
+/* Matches of length 3 are discarded if their distance exceeds TOO_FAR */
+
+/* Values for max_lazy_match, good_match and max_chain_length, depending on
+ * the desired pack level (0..9). The values given below have been tuned to
+ * exclude worst case performance for pathological files. Better values may be
+ * found for specific files.
+ */
+typedef struct config_s {
+   ush good_length; /* reduce lazy search above this match length */
+   ush max_lazy;    /* do not perform lazy search above this match length */
+   ush nice_length; /* quit search above this match length */
+   ush max_chain;
+   compress_func func;
+} config;
+
+#ifdef FASTEST
+local const config configuration_table[2] = {
+/*      good lazy nice chain */
+/* 0 */ {0,    0,  0,    0, deflate_stored},  /* store only */
+/* 1 */ {4,    4,  8,    4, deflate_fast}}; /* max speed, no lazy matches */
+#else
+local const config configuration_table[10] = {
+/*      good lazy nice chain */
+/* 0 */ {0,    0,  0,    0, deflate_stored},  /* store only */
+/* 1 */ {4,    4,  8,    4, deflate_fast}, /* max speed, no lazy matches */
+/* 2 */ {4,    5, 16,    8, deflate_fast},
+/* 3 */ {4,    6, 32,   32, deflate_fast},
+
+/* 4 */ {4,    4, 16,   16, deflate_slow},  /* lazy matches */
+/* 5 */ {8,   16, 32,   32, deflate_slow},
+/* 6 */ {8,   16, 128, 128, deflate_slow},
+/* 7 */ {8,   32, 128, 256, deflate_slow},
+/* 8 */ {32, 128, 258, 1024, deflate_slow},
+/* 9 */ {32, 258, 258, 4096, deflate_slow}}; /* max compression */
+#endif
+
+/* Note: the deflate() code requires max_lazy >= MIN_MATCH and max_chain >= 4
+ * For deflate_fast() (levels <= 3) good is ignored and lazy has a different
+ * meaning.
+ */
+
+#define EQUAL 0
+/* result of memcmp for equal strings */
+
+#ifndef NO_DUMMY_DECL
+struct static_tree_desc_s {int dummy;}; /* for buggy compilers */
+#endif
+
+/* ===========================================================================
+ * Update a hash value with the given input byte
+ * IN  assertion: all calls to to UPDATE_HASH are made with consecutive
+ *    input characters, so that a running hash key can be computed from the
+ *    previous key instead of complete recalculation each time.
+ */
+#define UPDATE_HASH(s,h,c) (h = (((h)<<s->hash_shift) ^ (c)) & s->hash_mask)
+
+
+/* ===========================================================================
+ * Insert string str in the dictionary and set match_head to the previous head
+ * of the hash chain (the most recent string with same hash key). Return
+ * the previous length of the hash chain.
+ * If this file is compiled with -DFASTEST, the compression level is forced
+ * to 1, and no hash chains are maintained.
+ * IN  assertion: all calls to to INSERT_STRING are made with consecutive
+ *    input characters and the first MIN_MATCH bytes of str are valid
+ *    (except for the last MIN_MATCH-1 bytes of the input file).
+ */
+#ifdef FASTEST
+#define INSERT_STRING(s, str, match_head) \
+   (UPDATE_HASH(s, s->ins_h, s->window[(str) + (MIN_MATCH-1)]), \
+    match_head = s->head[s->ins_h], \
+    s->head[s->ins_h] = (Pos)(str))
+#else
+#define INSERT_STRING(s, str, match_head) \
+   (UPDATE_HASH(s, s->ins_h, s->window[(str) + (MIN_MATCH-1)]), \
+    match_head = s->prev[(str) & s->w_mask] = s->head[s->ins_h], \
+    s->head[s->ins_h] = (Pos)(str))
+#endif
+
+/* ===========================================================================
+ * Initialize the hash table (avoiding 64K overflow for 16 bit systems).
+ * prev[] will be initialized on the fly.
+ */
+#define CLEAR_HASH(s) \
+    s->head[s->hash_size-1] = NIL; \
+    zmemzero((Bytef *)s->head, (unsigned)(s->hash_size-1)*sizeof(*s->head));
+
+/* ========================================================================= */
+int ZEXPORT deflateInit_(strm, level, version, stream_size)
+    z_streamp strm;
+    int level;
+    const char *version;
+    int stream_size;
+{
+    return deflateInit2_(strm, level, Z_DEFLATED, MAX_WBITS, DEF_MEM_LEVEL,
+                         Z_DEFAULT_STRATEGY, version, stream_size);
+    /* To do: ignore strm->next_in if we use it as window */
+}
+
+/* ========================================================================= */
+int ZEXPORT deflateInit2_(strm, level, method, windowBits, memLevel, strategy,
+                  version, stream_size)
+    z_streamp strm;
+    int  level;
+    int  method;
+    int  windowBits;
+    int  memLevel;
+    int  strategy;
+    const char *version;
+    int stream_size;
+{
+    deflate_state *s;
+    int wrap = 1;
+    static const char my_version[] = ZLIB_VERSION;
+
+    ushf *overlay;
+    /* We overlay pending_buf and d_buf+l_buf. This works since the average
+     * output size for (length,distance) codes is <= 24 bits.
+     */
+
+    if (version == Z_NULL || version[0] != my_version[0] ||
+        stream_size != sizeof(z_stream)) {
+        return Z_VERSION_ERROR;
+    }
+    if (strm == Z_NULL) return Z_STREAM_ERROR;
+
+    strm->msg = Z_NULL;
+    if (strm->zalloc == (alloc_func)0) {
+        strm->zalloc = zcalloc;
+        strm->opaque = (voidpf)0;
+    }
+    if (strm->zfree == (free_func)0) strm->zfree = zcfree;
+
+#ifdef FASTEST
+    if (level != 0) level = 1;
+#else
+    if (level == Z_DEFAULT_COMPRESSION) level = 6;
+#endif
+
+    if (windowBits < 0) { /* suppress zlib wrapper */
+        wrap = 0;
+        windowBits = -windowBits;
+    }
+#ifdef GZIP
+    else if (windowBits > 15) {
+        wrap = 2;       /* write gzip wrapper instead */
+        windowBits -= 16;
+    }
+#endif
+    if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || method != Z_DEFLATED ||
+        windowBits < 8 || windowBits > 15 || level < 0 || level > 9 ||
+        strategy < 0 || strategy > Z_FIXED) {
+        return Z_STREAM_ERROR;
+    }
+    if (windowBits == 8) windowBits = 9;  /* until 256-byte window bug fixed */
+    s = (deflate_state *) ZALLOC(strm, 1, sizeof(deflate_state));
+    if (s == Z_NULL) return Z_MEM_ERROR;
+    strm->state = (struct internal_state FAR *)s;
+    s->strm = strm;
+
+    s->wrap = wrap;
+    s->gzhead = Z_NULL;
+    s->w_bits = windowBits;
+    s->w_size = 1 << s->w_bits;
+    s->w_mask = s->w_size - 1;
+
+    s->hash_bits = memLevel + 7;
+    s->hash_size = 1 << s->hash_bits;
+    s->hash_mask = s->hash_size - 1;
+    s->hash_shift =  ((s->hash_bits+MIN_MATCH-1)/MIN_MATCH);
+
+    s->window = (Bytef *) ZALLOC(strm, s->w_size, 2*sizeof(Byte));
+    s->prev   = (Posf *)  ZALLOC(strm, s->w_size, sizeof(Pos));
+    s->head   = (Posf *)  ZALLOC(strm, s->hash_size, sizeof(Pos));
+
+    s->high_water = 0;      /* nothing written to s->window yet */
+
+    s->lit_bufsize = 1 << (memLevel + 6); /* 16K elements by default */
+
+    overlay = (ushf *) ZALLOC(strm, s->lit_bufsize, sizeof(ush)+2);
+    s->pending_buf = (uchf *) overlay;
+    s->pending_buf_size = (ulg)s->lit_bufsize * (sizeof(ush)+2L);
+
+    if (s->window == Z_NULL || s->prev == Z_NULL || s->head == Z_NULL ||
+        s->pending_buf == Z_NULL) {
+        s->status = FINISH_STATE;
+        strm->msg = (char*)ERR_MSG(Z_MEM_ERROR);
+        deflateEnd (strm);
+        return Z_MEM_ERROR;
+    }
+    s->d_buf = overlay + s->lit_bufsize/sizeof(ush);
+    s->l_buf = s->pending_buf + (1+sizeof(ush))*s->lit_bufsize;
+
+    s->level = level;
+    s->strategy = strategy;
+    s->method = (Byte)method;
+
+    return deflateReset(strm);
+}
+
+/* ========================================================================= */
+int ZEXPORT deflateSetDictionary (strm, dictionary, dictLength)
+    z_streamp strm;
+    const Bytef *dictionary;
+    uInt  dictLength;
+{
+    deflate_state *s;
+    uInt length = dictLength;
+    uInt n;
+    IPos hash_head = 0;
+
+    if (strm == Z_NULL || strm->state == Z_NULL || dictionary == Z_NULL ||
+        strm->state->wrap == 2 ||
+        (strm->state->wrap == 1 && strm->state->status != INIT_STATE))
+        return Z_STREAM_ERROR;
+
+    s = strm->state;
+    if (s->wrap)
+        strm->adler = adler32(strm->adler, dictionary, dictLength);
+
+    if (length < MIN_MATCH) return Z_OK;
+    if (length > s->w_size) {
+        length = s->w_size;
+        dictionary += dictLength - length; /* use the tail of the dictionary */
+    }
+    zmemcpy(s->window, dictionary, length);
+    s->strstart = length;
+    s->block_start = (long)length;
+
+    /* Insert all strings in the hash table (except for the last two bytes).
+     * s->lookahead stays null, so s->ins_h will be recomputed at the next
+     * call of fill_window.
+     */
+    s->ins_h = s->window[0];
+    UPDATE_HASH(s, s->ins_h, s->window[1]);
+    for (n = 0; n <= length - MIN_MATCH; n++) {
+        INSERT_STRING(s, n, hash_head);
+    }
+    if (hash_head) hash_head = 0;  /* to make compiler happy */
+    return Z_OK;
+}
+
+/* ========================================================================= */
+int ZEXPORT deflateReset (strm)
+    z_streamp strm;
+{
+    deflate_state *s;
+
+    if (strm == Z_NULL || strm->state == Z_NULL ||
+        strm->zalloc == (alloc_func)0 || strm->zfree == (free_func)0) {
+        return Z_STREAM_ERROR;
+    }
+
+    strm->total_in = strm->total_out = 0;
+    strm->msg = Z_NULL; /* use zfree if we ever allocate msg dynamically */
+    strm->data_type = Z_UNKNOWN;
+
+    s = (deflate_state *)strm->state;
+    s->pending = 0;
+    s->pending_out = s->pending_buf;
+
+    if (s->wrap < 0) {
+        s->wrap = -s->wrap; /* was made negative by deflate(..., Z_FINISH); */
+    }
+    s->status = s->wrap ? INIT_STATE : BUSY_STATE;
+    strm->adler =
+#ifdef GZIP
+        s->wrap == 2 ? crc32(0L, Z_NULL, 0) :
+#endif
+        adler32(0L, Z_NULL, 0);
+    s->last_flush = Z_NO_FLUSH;
+
+    _tr_init(s);
+    lm_init(s);
+
+    return Z_OK;
+}
+
+/* ========================================================================= */
+int ZEXPORT deflateSetHeader (strm, head)
+    z_streamp strm;
+    gz_headerp head;
+{
+    if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+    if (strm->state->wrap != 2) return Z_STREAM_ERROR;
+    strm->state->gzhead = head;
+    return Z_OK;
+}
+
+/* ========================================================================= */
+int ZEXPORT deflatePrime (strm, bits, value)
+    z_streamp strm;
+    int bits;
+    int value;
+{
+    if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+    strm->state->bi_valid = bits;
+    strm->state->bi_buf = (ush)(value & ((1 << bits) - 1));
+    return Z_OK;
+}
+
+/* ========================================================================= */
+int ZEXPORT deflateParams(strm, level, strategy)
+    z_streamp strm;
+    int level;
+    int strategy;
+{
+    deflate_state *s;
+    compress_func func;
+    int err = Z_OK;
+
+    if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+    s = strm->state;
+
+#ifdef FASTEST
+    if (level != 0) level = 1;
+#else
+    if (level == Z_DEFAULT_COMPRESSION) level = 6;
+#endif
+    if (level < 0 || level > 9 || strategy < 0 || strategy > Z_FIXED) {
+        return Z_STREAM_ERROR;
+    }
+    func = configuration_table[s->level].func;
+
+    if ((strategy != s->strategy || func != configuration_table[level].func) &&
+        strm->total_in != 0) {
+        /* Flush the last buffer: */
+        err = deflate(strm, Z_BLOCK);
+    }
+    if (s->level != level) {
+        s->level = level;
+        s->max_lazy_match   = configuration_table[level].max_lazy;
+        s->good_match       = configuration_table[level].good_length;
+        s->nice_match       = configuration_table[level].nice_length;
+        s->max_chain_length = configuration_table[level].max_chain;
+    }
+    s->strategy = strategy;
+    return err;
+}
+
+/* ========================================================================= */
+int ZEXPORT deflateTune(strm, good_length, max_lazy, nice_length, max_chain)
+    z_streamp strm;
+    int good_length;
+    int max_lazy;
+    int nice_length;
+    int max_chain;
+{
+    deflate_state *s;
+
+    if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+    s = strm->state;
+    s->good_match = good_length;
+    s->max_lazy_match = max_lazy;
+    s->nice_match = nice_length;
+    s->max_chain_length = max_chain;
+    return Z_OK;
+}
+
+/* =========================================================================
+ * For the default windowBits of 15 and memLevel of 8, this function returns
+ * a close to exact, as well as small, upper bound on the compressed size.
+ * They are coded as constants here for a reason--if the #define's are
+ * changed, then this function needs to be changed as well.  The return
+ * value for 15 and 8 only works for those exact settings.
+ *
+ * For any setting other than those defaults for windowBits and memLevel,
+ * the value returned is a conservative worst case for the maximum expansion
+ * resulting from using fixed blocks instead of stored blocks, which deflate
+ * can emit on compressed data for some combinations of the parameters.
+ *
+ * This function could be more sophisticated to provide closer upper bounds for
+ * every combination of windowBits and memLevel.  But even the conservative
+ * upper bound of about 14% expansion does not seem onerous for output buffer
+ * allocation.
+ */
+uLong ZEXPORT deflateBound(strm, sourceLen)
+    z_streamp strm;
+    uLong sourceLen;
+{
+    deflate_state *s;
+    uLong complen, wraplen;
+    Bytef *str;
+
+    /* conservative upper bound for compressed data */
+    complen = sourceLen +
+              ((sourceLen + 7) >> 3) + ((sourceLen + 63) >> 6) + 5;
+
+    /* if can't get parameters, return conservative bound plus zlib wrapper */
+    if (strm == Z_NULL || strm->state == Z_NULL)
+        return complen + 6;
+
+    /* compute wrapper length */
+    s = strm->state;
+    switch (s->wrap) {
+    case 0:                                 /* raw deflate */
+        wraplen = 0;
+        break;
+    case 1:                                 /* zlib wrapper */
+        wraplen = 6 + (s->strstart ? 4 : 0);
+        break;
+    case 2:                                 /* gzip wrapper */
+        wraplen = 18;
+        if (s->gzhead != Z_NULL) {          /* user-supplied gzip header */
+            if (s->gzhead->extra != Z_NULL)
+                wraplen += 2 + s->gzhead->extra_len;
+            str = s->gzhead->name;
+            if (str != Z_NULL)
+                do {
+                    wraplen++;
+                } while (*str++);
+            str = s->gzhead->comment;
+            if (str != Z_NULL)
+                do {
+                    wraplen++;
+                } while (*str++);
+            if (s->gzhead->hcrc)
+                wraplen += 2;
+        }
+        break;
+    default:                                /* for compiler happiness */
+        wraplen = 6;
+    }
+
+    /* if not default parameters, return conservative bound */
+    if (s->w_bits != 15 || s->hash_bits != 8 + 7)
+        return complen + wraplen;
+
+    /* default settings: return tight bound for that case */
+    return sourceLen + (sourceLen >> 12) + (sourceLen >> 14) +
+           (sourceLen >> 25) + 13 - 6 + wraplen;
+}
+
+/* =========================================================================
+ * Put a short in the pending buffer. The 16-bit value is put in MSB order.
+ * IN assertion: the stream state is correct and there is enough room in
+ * pending_buf.
+ */
+local void putShortMSB (s, b)
+    deflate_state *s;
+    uInt b;
+{
+    put_byte(s, (Byte)(b >> 8));
+    put_byte(s, (Byte)(b & 0xff));
+}
+
+/* =========================================================================
+ * Flush as much pending output as possible. All deflate() output goes
+ * through this function so some applications may wish to modify it
+ * to avoid allocating a large strm->next_out buffer and copying into it.
+ * (See also read_buf()).
+ */
+local void flush_pending(strm)
+    z_streamp strm;
+{
+    unsigned len = strm->state->pending;
+
+    if (len > strm->avail_out) len = strm->avail_out;
+    if (len == 0) return;
+
+    zmemcpy(strm->next_out, strm->state->pending_out, len);
+    strm->next_out  += len;
+    strm->state->pending_out  += len;
+    strm->total_out += len;
+    strm->avail_out  -= len;
+    strm->state->pending -= len;
+    if (strm->state->pending == 0) {
+        strm->state->pending_out = strm->state->pending_buf;
+    }
+}
+
+/* ========================================================================= */
+int ZEXPORT deflate (strm, flush)
+    z_streamp strm;
+    int flush;
+{
+    int old_flush; /* value of flush param for previous deflate call */
+    deflate_state *s;
+
+    if (strm == Z_NULL || strm->state == Z_NULL ||
+        flush > Z_BLOCK || flush < 0) {
+        return Z_STREAM_ERROR;
+    }
+    s = strm->state;
+
+    if (strm->next_out == Z_NULL ||
+        (strm->next_in == Z_NULL && strm->avail_in != 0) ||
+        (s->status == FINISH_STATE && flush != Z_FINISH)) {
+        ERR_RETURN(strm, Z_STREAM_ERROR);
+    }
+    if (strm->avail_out == 0) ERR_RETURN(strm, Z_BUF_ERROR);
+
+    s->strm = strm; /* just in case */
+    old_flush = s->last_flush;
+    s->last_flush = flush;
+
+    /* Write the header */
+    if (s->status == INIT_STATE) {
+#ifdef GZIP
+        if (s->wrap == 2) {
+            strm->adler = crc32(0L, Z_NULL, 0);
+            put_byte(s, 31);
+            put_byte(s, 139);
+            put_byte(s, 8);
+            if (s->gzhead == Z_NULL) {
+                put_byte(s, 0);
+                put_byte(s, 0);
+                put_byte(s, 0);
+                put_byte(s, 0);
+                put_byte(s, 0);
+                put_byte(s, s->level == 9 ? 2 :
+                            (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2 ?
+                             4 : 0));
+                put_byte(s, OS_CODE);
+                s->status = BUSY_STATE;
+            }
+            else {
+                put_byte(s, (s->gzhead->text ? 1 : 0) +
+                            (s->gzhead->hcrc ? 2 : 0) +
+                            (s->gzhead->extra == Z_NULL ? 0 : 4) +
+                            (s->gzhead->name == Z_NULL ? 0 : 8) +
+                            (s->gzhead->comment == Z_NULL ? 0 : 16)
+                        );
+                put_byte(s, (Byte)(s->gzhead->time & 0xff));
+                put_byte(s, (Byte)((s->gzhead->time >> 8) & 0xff));
+                put_byte(s, (Byte)((s->gzhead->time >> 16) & 0xff));
+                put_byte(s, (Byte)((s->gzhead->time >> 24) & 0xff));
+                put_byte(s, s->level == 9 ? 2 :
+                            (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2 ?
+                             4 : 0));
+                put_byte(s, s->gzhead->os & 0xff);
+                if (s->gzhead->extra != Z_NULL) {
+                    put_byte(s, s->gzhead->extra_len & 0xff);
+                    put_byte(s, (s->gzhead->extra_len >> 8) & 0xff);
+                }
+                if (s->gzhead->hcrc)
+                    strm->adler = crc32(strm->adler, s->pending_buf,
+                                        s->pending);
+                s->gzindex = 0;
+                s->status = EXTRA_STATE;
+            }
+        }
+        else
+#endif
+        {
+            uInt header = (Z_DEFLATED + ((s->w_bits-8)<<4)) << 8;
+            uInt level_flags;
+
+            if (s->strategy >= Z_HUFFMAN_ONLY || s->level < 2)
+                level_flags = 0;
+            else if (s->level < 6)
+                level_flags = 1;
+            else if (s->level == 6)
+                level_flags = 2;
+            else
+                level_flags = 3;
+            header |= (level_flags << 6);
+            if (s->strstart != 0) header |= PRESET_DICT;
+            header += 31 - (header % 31);
+
+            s->status = BUSY_STATE;
+            putShortMSB(s, header);
+
+            /* Save the adler32 of the preset dictionary: */
+            if (s->strstart != 0) {
+                putShortMSB(s, (uInt)(strm->adler >> 16));
+                putShortMSB(s, (uInt)(strm->adler & 0xffff));
+            }
+            strm->adler = adler32(0L, Z_NULL, 0);
+        }
+    }
+#ifdef GZIP
+    if (s->status == EXTRA_STATE) {
+        if (s->gzhead->extra != Z_NULL) {
+            uInt beg = s->pending;  /* start of bytes to update crc */
+
+            while (s->gzindex < (s->gzhead->extra_len & 0xffff)) {
+                if (s->pending == s->pending_buf_size) {
+                    if (s->gzhead->hcrc && s->pending > beg)
+                        strm->adler = crc32(strm->adler, s->pending_buf + beg,
+                                            s->pending - beg);
+                    flush_pending(strm);
+                    beg = s->pending;
+                    if (s->pending == s->pending_buf_size)
+                        break;
+                }
+                put_byte(s, s->gzhead->extra[s->gzindex]);
+                s->gzindex++;
+            }
+            if (s->gzhead->hcrc && s->pending > beg)
+                strm->adler = crc32(strm->adler, s->pending_buf + beg,
+                                    s->pending - beg);
+            if (s->gzindex == s->gzhead->extra_len) {
+                s->gzindex = 0;
+                s->status = NAME_STATE;
+            }
+        }
+        else
+            s->status = NAME_STATE;
+    }
+    if (s->status == NAME_STATE) {
+        if (s->gzhead->name != Z_NULL) {
+            uInt beg = s->pending;  /* start of bytes to update crc */
+            int val;
+
+            do {
+                if (s->pending == s->pending_buf_size) {
+                    if (s->gzhead->hcrc && s->pending > beg)
+                        strm->adler = crc32(strm->adler, s->pending_buf + beg,
+                                            s->pending - beg);
+                    flush_pending(strm);
+                    beg = s->pending;
+                    if (s->pending == s->pending_buf_size) {
+                        val = 1;
+                        break;
+                    }
+                }
+                val = s->gzhead->name[s->gzindex++];
+                put_byte(s, val);
+            } while (val != 0);
+            if (s->gzhead->hcrc && s->pending > beg)
+                strm->adler = crc32(strm->adler, s->pending_buf + beg,
+                                    s->pending - beg);
+            if (val == 0) {
+                s->gzindex = 0;
+                s->status = COMMENT_STATE;
+            }
+        }
+        else
+            s->status = COMMENT_STATE;
+    }
+    if (s->status == COMMENT_STATE) {
+        if (s->gzhead->comment != Z_NULL) {
+            uInt beg = s->pending;  /* start of bytes to update crc */
+            int val;
+
+            do {
+                if (s->pending == s->pending_buf_size) {
+                    if (s->gzhead->hcrc && s->pending > beg)
+                        strm->adler = crc32(strm->adler, s->pending_buf + beg,
+                                            s->pending - beg);
+                    flush_pending(strm);
+                    beg = s->pending;
+                    if (s->pending == s->pending_buf_size) {
+                        val = 1;
+                        break;
+                    }
+                }
+                val = s->gzhead->comment[s->gzindex++];
+                put_byte(s, val);
+            } while (val != 0);
+            if (s->gzhead->hcrc && s->pending > beg)
+                strm->adler = crc32(strm->adler, s->pending_buf + beg,
+                                    s->pending - beg);
+            if (val == 0)
+                s->status = HCRC_STATE;
+        }
+        else
+            s->status = HCRC_STATE;
+    }
+    if (s->status == HCRC_STATE) {
+        if (s->gzhead->hcrc) {
+            if (s->pending + 2 > s->pending_buf_size)
+                flush_pending(strm);
+            if (s->pending + 2 <= s->pending_buf_size) {
+                put_byte(s, (Byte)(strm->adler & 0xff));
+                put_byte(s, (Byte)((strm->adler >> 8) & 0xff));
+                strm->adler = crc32(0L, Z_NULL, 0);
+                s->status = BUSY_STATE;
+            }
+        }
+        else
+            s->status = BUSY_STATE;
+    }
+#endif
+
+    /* Flush as much pending output as possible */
+    if (s->pending != 0) {
+        flush_pending(strm);
+        if (strm->avail_out == 0) {
+            /* Since avail_out is 0, deflate will be called again with
+             * more output space, but possibly with both pending and
+             * avail_in equal to zero. There won't be anything to do,
+             * but this is not an error situation so make sure we
+             * return OK instead of BUF_ERROR at next call of deflate:
+             */
+            s->last_flush = -1;
+            return Z_OK;
+        }
+
+    /* Make sure there is something to do and avoid duplicate consecutive
+     * flushes. For repeated and useless calls with Z_FINISH, we keep
+     * returning Z_STREAM_END instead of Z_BUF_ERROR.
+     */
+    } else if (strm->avail_in == 0 && flush <= old_flush &&
+               flush != Z_FINISH) {
+        ERR_RETURN(strm, Z_BUF_ERROR);
+    }
+
+    /* User must not provide more input after the first FINISH: */
+    if (s->status == FINISH_STATE && strm->avail_in != 0) {
+        ERR_RETURN(strm, Z_BUF_ERROR);
+    }
+
+    /* Start a new block or continue the current one.
+     */
+    if (strm->avail_in != 0 || s->lookahead != 0 ||
+        (flush != Z_NO_FLUSH && s->status != FINISH_STATE)) {
+        block_state bstate;
+
+        bstate = s->strategy == Z_HUFFMAN_ONLY ? deflate_huff(s, flush) :
+                    (s->strategy == Z_RLE ? deflate_rle(s, flush) :
+                        (*(configuration_table[s->level].func))(s, flush));
+
+        if (bstate == finish_started || bstate == finish_done) {
+            s->status = FINISH_STATE;
+        }
+        if (bstate == need_more || bstate == finish_started) {
+            if (strm->avail_out == 0) {
+                s->last_flush = -1; /* avoid BUF_ERROR next call, see above */
+            }
+            return Z_OK;
+            /* If flush != Z_NO_FLUSH && avail_out == 0, the next call
+             * of deflate should use the same flush parameter to make sure
+             * that the flush is complete. So we don't have to output an
+             * empty block here, this will be done at next call. This also
+             * ensures that for a very small output buffer, we emit at most
+             * one empty block.
+             */
+        }
+        if (bstate == block_done) {
+            if (flush == Z_PARTIAL_FLUSH) {
+                _tr_align(s);
+            } else if (flush != Z_BLOCK) { /* FULL_FLUSH or SYNC_FLUSH */
+                _tr_stored_block(s, (char*)0, 0L, 0);
+                /* For a full flush, this empty block will be recognized
+                 * as a special marker by inflate_sync().
+                 */
+                if (flush == Z_FULL_FLUSH) {
+                    CLEAR_HASH(s);             /* forget history */
+                    if (s->lookahead == 0) {
+                        s->strstart = 0;
+                        s->block_start = 0L;
+                    }
+                }
+            }
+            flush_pending(strm);
+            if (strm->avail_out == 0) {
+              s->last_flush = -1; /* avoid BUF_ERROR at next call, see above */
+              return Z_OK;
+            }
+        }
+    }
+    Assert(strm->avail_out > 0, "bug2");
+
+    if (flush != Z_FINISH) return Z_OK;
+    if (s->wrap <= 0) return Z_STREAM_END;
+
+    /* Write the trailer */
+#ifdef GZIP
+    if (s->wrap == 2) {
+        put_byte(s, (Byte)(strm->adler & 0xff));
+        put_byte(s, (Byte)((strm->adler >> 8) & 0xff));
+        put_byte(s, (Byte)((strm->adler >> 16) & 0xff));
+        put_byte(s, (Byte)((strm->adler >> 24) & 0xff));
+        put_byte(s, (Byte)(strm->total_in & 0xff));
+        put_byte(s, (Byte)((strm->total_in >> 8) & 0xff));
+        put_byte(s, (Byte)((strm->total_in >> 16) & 0xff));
+        put_byte(s, (Byte)((strm->total_in >> 24) & 0xff));
+    }
+    else
+#endif
+    {
+        putShortMSB(s, (uInt)(strm->adler >> 16));
+        putShortMSB(s, (uInt)(strm->adler & 0xffff));
+    }
+    flush_pending(strm);
+    /* If avail_out is zero, the application will call deflate again
+     * to flush the rest.
+     */
+    if (s->wrap > 0) s->wrap = -s->wrap; /* write the trailer only once! */
+    return s->pending != 0 ? Z_OK : Z_STREAM_END;
+}
+
+/* ========================================================================= */
+int ZEXPORT deflateEnd (strm)
+    z_streamp strm;
+{
+    int status;
+
+    if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+
+    status = strm->state->status;
+    if (status != INIT_STATE &&
+        status != EXTRA_STATE &&
+        status != NAME_STATE &&
+        status != COMMENT_STATE &&
+        status != HCRC_STATE &&
+        status != BUSY_STATE &&
+        status != FINISH_STATE) {
+      return Z_STREAM_ERROR;
+    }
+
+    /* Deallocate in reverse order of allocations: */
+    TRY_FREE(strm, strm->state->pending_buf);
+    TRY_FREE(strm, strm->state->head);
+    TRY_FREE(strm, strm->state->prev);
+    TRY_FREE(strm, strm->state->window);
+
+    ZFREE(strm, strm->state);
+    strm->state = Z_NULL;
+
+    return status == BUSY_STATE ? Z_DATA_ERROR : Z_OK;
+}
+
+/* =========================================================================
+ * Copy the source state to the destination state.
+ * To simplify the source, this is not supported for 16-bit MSDOS (which
+ * doesn't have enough memory anyway to duplicate compression states).
+ */
+int ZEXPORT deflateCopy (dest, source)
+    z_streamp dest;
+    z_streamp source;
+{
+#ifdef MAXSEG_64K
+    return Z_STREAM_ERROR;
+#else
+    deflate_state *ds;
+    deflate_state *ss;
+    ushf *overlay;
+
+
+    if (source == Z_NULL || dest == Z_NULL || source->state == Z_NULL) {
+        return Z_STREAM_ERROR;
+    }
+
+    ss = source->state;
+
+    zmemcpy(dest, source, sizeof(z_stream));
+
+    ds = (deflate_state *) ZALLOC(dest, 1, sizeof(deflate_state));
+    if (ds == Z_NULL) return Z_MEM_ERROR;
+    dest->state = (struct internal_state FAR *) ds;
+    zmemcpy(ds, ss, sizeof(deflate_state));
+    ds->strm = dest;
+
+    ds->window = (Bytef *) ZALLOC(dest, ds->w_size, 2*sizeof(Byte));
+    ds->prev   = (Posf *)  ZALLOC(dest, ds->w_size, sizeof(Pos));
+    ds->head   = (Posf *)  ZALLOC(dest, ds->hash_size, sizeof(Pos));
+    overlay = (ushf *) ZALLOC(dest, ds->lit_bufsize, sizeof(ush)+2);
+    ds->pending_buf = (uchf *) overlay;
+
+    if (ds->window == Z_NULL || ds->prev == Z_NULL || ds->head == Z_NULL ||
+        ds->pending_buf == Z_NULL) {
+        deflateEnd (dest);
+        return Z_MEM_ERROR;
+    }
+    /* following zmemcpy do not work for 16-bit MSDOS */
+    zmemcpy(ds->window, ss->window, ds->w_size * 2 * sizeof(Byte));
+    zmemcpy(ds->prev, ss->prev, ds->w_size * sizeof(Pos));
+    zmemcpy(ds->head, ss->head, ds->hash_size * sizeof(Pos));
+    zmemcpy(ds->pending_buf, ss->pending_buf, (uInt)ds->pending_buf_size);
+
+    ds->pending_out = ds->pending_buf + (ss->pending_out - ss->pending_buf);
+    ds->d_buf = overlay + ds->lit_bufsize/sizeof(ush);
+    ds->l_buf = ds->pending_buf + (1+sizeof(ush))*ds->lit_bufsize;
+
+    ds->l_desc.dyn_tree = ds->dyn_ltree;
+    ds->d_desc.dyn_tree = ds->dyn_dtree;
+    ds->bl_desc.dyn_tree = ds->bl_tree;
+
+    return Z_OK;
+#endif /* MAXSEG_64K */
+}
+
+/* ===========================================================================
+ * Read a new buffer from the current input stream, update the adler32
+ * and total number of bytes read.  All deflate() input goes through
+ * this function so some applications may wish to modify it to avoid
+ * allocating a large strm->next_in buffer and copying from it.
+ * (See also flush_pending()).
+ */
+local int read_buf(strm, buf, size)
+    z_streamp strm;
+    Bytef *buf;
+    unsigned size;
+{
+    unsigned len = strm->avail_in;
+
+    if (len > size) len = size;
+    if (len == 0) return 0;
+
+    strm->avail_in  -= len;
+
+    if (strm->state->wrap == 1) {
+        strm->adler = adler32(strm->adler, strm->next_in, len);
+    }
+#ifdef GZIP
+    else if (strm->state->wrap == 2) {
+        strm->adler = crc32(strm->adler, strm->next_in, len);
+    }
+#endif
+    zmemcpy(buf, strm->next_in, len);
+    strm->next_in  += len;
+    strm->total_in += len;
+
+    return (int)len;
+}
+
+/* ===========================================================================
+ * Initialize the "longest match" routines for a new zlib stream
+ */
+local void lm_init (s)
+    deflate_state *s;
+{
+    s->window_size = (ulg)2L*s->w_size;
+
+    CLEAR_HASH(s);
+
+    /* Set the default configuration parameters:
+     */
+    s->max_lazy_match   = configuration_table[s->level].max_lazy;
+    s->good_match       = configuration_table[s->level].good_length;
+    s->nice_match       = configuration_table[s->level].nice_length;
+    s->max_chain_length = configuration_table[s->level].max_chain;
+
+    s->strstart = 0;
+    s->block_start = 0L;
+    s->lookahead = 0;
+    s->match_length = s->prev_length = MIN_MATCH-1;
+    s->match_available = 0;
+    s->ins_h = 0;
+#ifndef FASTEST
+#ifdef ASMV
+    match_init(); /* initialize the asm code */
+#endif
+#endif
+}
+
+#ifndef FASTEST
+/* ===========================================================================
+ * Set match_start to the longest match starting at the given string and
+ * return its length. Matches shorter or equal to prev_length are discarded,
+ * in which case the result is equal to prev_length and match_start is
+ * garbage.
+ * IN assertions: cur_match is the head of the hash chain for the current
+ *   string (strstart) and its distance is <= MAX_DIST, and prev_length >= 1
+ * OUT assertion: the match length is not greater than s->lookahead.
+ */
+#ifndef ASMV
+/* For 80x86 and 680x0, an optimized version will be provided in match.asm or
+ * match.S. The code will be functionally equivalent.
+ */
+local uInt longest_match(s, cur_match)
+    deflate_state *s;
+    IPos cur_match;                             /* current match */
+{
+    unsigned chain_length = s->max_chain_length;/* max hash chain length */
+    register Bytef *scan = s->window + s->strstart; /* current string */
+    register Bytef *match;                       /* matched string */
+    register int len;                           /* length of current match */
+    int best_len = s->prev_length;              /* best match length so far */
+    int nice_match = s->nice_match;             /* stop if match long enough */
+    IPos limit = s->strstart > (IPos)MAX_DIST(s) ?
+        s->strstart - (IPos)MAX_DIST(s) : NIL;
+    /* Stop when cur_match becomes <= limit. To simplify the code,
+     * we prevent matches with the string of window index 0.
+     */
+    Posf *prev = s->prev;
+    uInt wmask = s->w_mask;
+
+#ifdef UNALIGNED_OK
+    /* Compare two bytes at a time. Note: this is not always beneficial.
+     * Try with and without -DUNALIGNED_OK to check.
+     */
+    register Bytef *strend = s->window + s->strstart + MAX_MATCH - 1;
+    register ush scan_start = *(ushf*)scan;
+    register ush scan_end   = *(ushf*)(scan+best_len-1);
+#else
+    register Bytef *strend = s->window + s->strstart + MAX_MATCH;
+    register Byte scan_end1  = scan[best_len-1];
+    register Byte scan_end   = scan[best_len];
+#endif
+
+    /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16.
+     * It is easy to get rid of this optimization if necessary.
+     */
+    Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever");
+
+    /* Do not waste too much time if we already have a good match: */
+    if (s->prev_length >= s->good_match) {
+        chain_length >>= 2;
+    }
+    /* Do not look for matches beyond the end of the input. This is necessary
+     * to make deflate deterministic.
+     */
+    if ((uInt)nice_match > s->lookahead) nice_match = s->lookahead;
+
+    Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead");
+
+    do {
+        Assert(cur_match < s->strstart, "no future");
+        match = s->window + cur_match;
+
+        /* Skip to next match if the match length cannot increase
+         * or if the match length is less than 2.  Note that the checks below
+         * for insufficient lookahead only occur occasionally for performance
+         * reasons.  Therefore uninitialized memory will be accessed, and
+         * conditional jumps will be made that depend on those values.
+         * However the length of the match is limited to the lookahead, so
+         * the output of deflate is not affected by the uninitialized values.
+         */
+#if (defined(UNALIGNED_OK) && MAX_MATCH == 258)
+        /* This code assumes sizeof(unsigned short) == 2. Do not use
+         * UNALIGNED_OK if your compiler uses a different size.
+         */
+        if (*(ushf*)(match+best_len-1) != scan_end ||
+            *(ushf*)match != scan_start) continue;
+
+        /* It is not necessary to compare scan[2] and match[2] since they are
+         * always equal when the other bytes match, given that the hash keys
+         * are equal and that HASH_BITS >= 8. Compare 2 bytes at a time at
+         * strstart+3, +5, ... up to strstart+257. We check for insufficient
+         * lookahead only every 4th comparison; the 128th check will be made
+         * at strstart+257. If MAX_MATCH-2 is not a multiple of 8, it is
+         * necessary to put more guard bytes at the end of the window, or
+         * to check more often for insufficient lookahead.
+         */
+        Assert(scan[2] == match[2], "scan[2]?");
+        scan++, match++;
+        do {
+        } while (*(ushf*)(scan+=2) == *(ushf*)(match+=2) &&
+                 *(ushf*)(scan+=2) == *(ushf*)(match+=2) &&
+                 *(ushf*)(scan+=2) == *(ushf*)(match+=2) &&
+                 *(ushf*)(scan+=2) == *(ushf*)(match+=2) &&
+                 scan < strend);
+        /* The funny "do {}" generates better code on most compilers */
+
+        /* Here, scan <= window+strstart+257 */
+        Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan");
+        if (*scan == *match) scan++;
+
+        len = (MAX_MATCH - 1) - (int)(strend-scan);
+        scan = strend - (MAX_MATCH-1);
+
+#else /* UNALIGNED_OK */
+
+        if (match[best_len]   != scan_end  ||
+            match[best_len-1] != scan_end1 ||
+            *match            != *scan     ||
+            *++match          != scan[1])      continue;
+
+        /* The check at best_len-1 can be removed because it will be made
+         * again later. (This heuristic is not always a win.)
+         * It is not necessary to compare scan[2] and match[2] since they
+         * are always equal when the other bytes match, given that
+         * the hash keys are equal and that HASH_BITS >= 8.
+         */
+        scan += 2, match++;
+        Assert(*scan == *match, "match[2]?");
+
+        /* We check for insufficient lookahead only every 8th comparison;
+         * the 256th check will be made at strstart+258.
+         */
+        do {
+        } while (*++scan == *++match && *++scan == *++match &&
+                 *++scan == *++match && *++scan == *++match &&
+                 *++scan == *++match && *++scan == *++match &&
+                 *++scan == *++match && *++scan == *++match &&
+                 scan < strend);
+
+        Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan");
+
+        len = MAX_MATCH - (int)(strend - scan);
+        scan = strend - MAX_MATCH;
+
+#endif /* UNALIGNED_OK */
+
+        if (len > best_len) {
+            s->match_start = cur_match;
+            best_len = len;
+            if (len >= nice_match) break;
+#ifdef UNALIGNED_OK
+            scan_end = *(ushf*)(scan+best_len-1);
+#else
+            scan_end1  = scan[best_len-1];
+            scan_end   = scan[best_len];
+#endif
+        }
+    } while ((cur_match = prev[cur_match & wmask]) > limit
+             && --chain_length != 0);
+
+    if ((uInt)best_len <= s->lookahead) return (uInt)best_len;
+    return s->lookahead;
+}
+#endif /* ASMV */
+
+#else /* FASTEST */
+
+/* ---------------------------------------------------------------------------
+ * Optimized version for FASTEST only
+ */
+local uInt longest_match(s, cur_match)
+    deflate_state *s;
+    IPos cur_match;                             /* current match */
+{
+    register Bytef *scan = s->window + s->strstart; /* current string */
+    register Bytef *match;                       /* matched string */
+    register int len;                           /* length of current match */
+    register Bytef *strend = s->window + s->strstart + MAX_MATCH;
+
+    /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16.
+     * It is easy to get rid of this optimization if necessary.
+     */
+    Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever");
+
+    Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead");
+
+    Assert(cur_match < s->strstart, "no future");
+
+    match = s->window + cur_match;
+
+    /* Return failure if the match length is less than 2:
+     */
+    if (match[0] != scan[0] || match[1] != scan[1]) return MIN_MATCH-1;
+
+    /* The check at best_len-1 can be removed because it will be made
+     * again later. (This heuristic is not always a win.)
+     * It is not necessary to compare scan[2] and match[2] since they
+     * are always equal when the other bytes match, given that
+     * the hash keys are equal and that HASH_BITS >= 8.
+     */
+    scan += 2, match += 2;
+    Assert(*scan == *match, "match[2]?");
+
+    /* We check for insufficient lookahead only every 8th comparison;
+     * the 256th check will be made at strstart+258.
+     */
+    do {
+    } while (*++scan == *++match && *++scan == *++match &&
+             *++scan == *++match && *++scan == *++match &&
+             *++scan == *++match && *++scan == *++match &&
+             *++scan == *++match && *++scan == *++match &&
+             scan < strend);
+
+    Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan");
+
+    len = MAX_MATCH - (int)(strend - scan);
+
+    if (len < MIN_MATCH) return MIN_MATCH - 1;
+
+    s->match_start = cur_match;
+    return (uInt)len <= s->lookahead ? (uInt)len : s->lookahead;
+}
+
+#endif /* FASTEST */
+
+#ifdef DEBUG
+/* ===========================================================================
+ * Check that the match at match_start is indeed a match.
+ */
+local void check_match(s, start, match, length)
+    deflate_state *s;
+    IPos start, match;
+    int length;
+{
+    /* check that the match is indeed a match */
+    if (zmemcmp(s->window + match,
+                s->window + start, length) != EQUAL) {
+        fprintf(stderr, " start %u, match %u, length %d\n",
+                start, match, length);
+        do {
+            fprintf(stderr, "%c%c", s->window[match++], s->window[start++]);
+        } while (--length != 0);
+        z_error("invalid match");
+    }
+    if (z_verbose > 1) {
+        fprintf(stderr,"\\[%d,%d]", start-match, length);
+        do { putc(s->window[start++], stderr); } while (--length != 0);
+    }
+}
+#else
+#  define check_match(s, start, match, length)
+#endif /* DEBUG */
+
+/* ===========================================================================
+ * Fill the window when the lookahead becomes insufficient.
+ * Updates strstart and lookahead.
+ *
+ * IN assertion: lookahead < MIN_LOOKAHEAD
+ * OUT assertions: strstart <= window_size-MIN_LOOKAHEAD
+ *    At least one byte has been read, or avail_in == 0; reads are
+ *    performed for at least two bytes (required for the zip translate_eol
+ *    option -- not supported here).
+ */
+local void fill_window(s)
+    deflate_state *s;
+{
+    register unsigned n, m;
+    register Posf *p;
+    unsigned more;    /* Amount of free space at the end of the window. */
+    uInt wsize = s->w_size;
+
+    do {
+        more = (unsigned)(s->window_size -(ulg)s->lookahead -(ulg)s->strstart);
+
+        /* Deal with ! #$% 64K limit: */
+        if (sizeof(int) <= 2) {
+            if (more == 0 && s->strstart == 0 && s->lookahead == 0) {
+                more = wsize;
+
+            } else if (more == (unsigned)(-1)) {
+                /* Very unlikely, but possible on 16 bit machine if
+                 * strstart == 0 && lookahead == 1 (input done a byte at time)
+                 */
+                more--;
+            }
+        }
+
+        /* If the window is almost full and there is insufficient lookahead,
+         * move the upper half to the lower one to make room in the upper half.
+         */
+        if (s->strstart >= wsize+MAX_DIST(s)) {
+
+            zmemcpy(s->window, s->window+wsize, (unsigned)wsize);
+            s->match_start -= wsize;
+            s->strstart    -= wsize; /* we now have strstart >= MAX_DIST */
+            s->block_start -= (long) wsize;
+
+            /* Slide the hash table (could be avoided with 32 bit values
+               at the expense of memory usage). We slide even when level == 0
+               to keep the hash table consistent if we switch back to level > 0
+               later. (Using level 0 permanently is not an optimal usage of
+               zlib, so we don't care about this pathological case.)
+             */
+            n = s->hash_size;
+            p = &s->head[n];
+            do {
+                m = *--p;
+                *p = (Pos)(m >= wsize ? m-wsize : NIL);
+            } while (--n);
+
+            n = wsize;
+#ifndef FASTEST
+            p = &s->prev[n];
+            do {
+                m = *--p;
+                *p = (Pos)(m >= wsize ? m-wsize : NIL);
+                /* If n is not on any hash chain, prev[n] is garbage but
+                 * its value will never be used.
+                 */
+            } while (--n);
+#endif
+            more += wsize;
+        }
+        if (s->strm->avail_in == 0) return;
+
+        /* If there was no sliding:
+         *    strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 &&
+         *    more == window_size - lookahead - strstart
+         * => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1)
+         * => more >= window_size - 2*WSIZE + 2
+         * In the BIG_MEM or MMAP case (not yet supported),
+         *   window_size == input_size + MIN_LOOKAHEAD  &&
+         *   strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD.
+         * Otherwise, window_size == 2*WSIZE so more >= 2.
+         * If there was sliding, more >= WSIZE. So in all cases, more >= 2.
+         */
+        Assert(more >= 2, "more < 2");
+
+        n = read_buf(s->strm, s->window + s->strstart + s->lookahead, more);
+        s->lookahead += n;
+
+        /* Initialize the hash value now that we have some input: */
+        if (s->lookahead >= MIN_MATCH) {
+            s->ins_h = s->window[s->strstart];
+            UPDATE_HASH(s, s->ins_h, s->window[s->strstart+1]);
+#if MIN_MATCH != 3
+            Call UPDATE_HASH() MIN_MATCH-3 more times
+#endif
+        }
+        /* If the whole input has less than MIN_MATCH bytes, ins_h is garbage,
+         * but this is not important since only literal bytes will be emitted.
+         */
+
+    } while (s->lookahead < MIN_LOOKAHEAD && s->strm->avail_in != 0);
+
+    /* If the WIN_INIT bytes after the end of the current data have never been
+     * written, then zero those bytes in order to avoid memory check reports of
+     * the use of uninitialized (or uninitialised as Julian writes) bytes by
+     * the longest match routines.  Update the high water mark for the next
+     * time through here.  WIN_INIT is set to MAX_MATCH since the longest match
+     * routines allow scanning to strstart + MAX_MATCH, ignoring lookahead.
+     */
+    if (s->high_water < s->window_size) {
+        ulg curr = s->strstart + (ulg)(s->lookahead);
+        ulg init;
+
+        if (s->high_water < curr) {
+            /* Previous high water mark below current data -- zero WIN_INIT
+             * bytes or up to end of window, whichever is less.
+             */
+            init = s->window_size - curr;
+            if (init > WIN_INIT)
+                init = WIN_INIT;
+            zmemzero(s->window + curr, (unsigned)init);
+            s->high_water = curr + init;
+        }
+        else if (s->high_water < (ulg)curr + WIN_INIT) {
+            /* High water mark at or above current data, but below current data
+             * plus WIN_INIT -- zero out to current data plus WIN_INIT, or up
+             * to end of window, whichever is less.
+             */
+            init = (ulg)curr + WIN_INIT - s->high_water;
+            if (init > s->window_size - s->high_water)
+                init = s->window_size - s->high_water;
+            zmemzero(s->window + s->high_water, (unsigned)init);
+            s->high_water += init;
+        }
+    }
+}
+
+/* ===========================================================================
+ * Flush the current block, with given end-of-file flag.
+ * IN assertion: strstart is set to the end of the current match.
+ */
+#define FLUSH_BLOCK_ONLY(s, last) { \
+   _tr_flush_block(s, (s->block_start >= 0L ? \
+                   (charf *)&s->window[(unsigned)s->block_start] : \
+                   (charf *)Z_NULL), \
+                (ulg)((long)s->strstart - s->block_start), \
+                (last)); \
+   s->block_start = s->strstart; \
+   flush_pending(s->strm); \
+   Tracev((stderr,"[FLUSH]")); \
+}
+
+/* Same but force premature exit if necessary. */
+#define FLUSH_BLOCK(s, last) { \
+   FLUSH_BLOCK_ONLY(s, last); \
+   if (s->strm->avail_out == 0) return (last) ? finish_started : need_more; \
+}
+
+/* ===========================================================================
+ * Copy without compression as much as possible from the input stream, return
+ * the current block state.
+ * This function does not insert new strings in the dictionary since
+ * uncompressible data is probably not useful. This function is used
+ * only for the level=0 compression option.
+ * NOTE: this function should be optimized to avoid extra copying from
+ * window to pending_buf.
+ */
+local block_state deflate_stored(s, flush)
+    deflate_state *s;
+    int flush;
+{
+    /* Stored blocks are limited to 0xffff bytes, pending_buf is limited
+     * to pending_buf_size, and each stored block has a 5 byte header:
+     */
+    ulg max_block_size = 0xffff;
+    ulg max_start;
+
+    if (max_block_size > s->pending_buf_size - 5) {
+        max_block_size = s->pending_buf_size - 5;
+    }
+
+    /* Copy as much as possible from input to output: */
+    for (;;) {
+        /* Fill the window as much as possible: */
+        if (s->lookahead <= 1) {
+
+            Assert(s->strstart < s->w_size+MAX_DIST(s) ||
+                   s->block_start >= (long)s->w_size, "slide too late");
+
+            fill_window(s);
+            if (s->lookahead == 0 && flush == Z_NO_FLUSH) return need_more;
+
+            if (s->lookahead == 0) break; /* flush the current block */
+        }
+        Assert(s->block_start >= 0L, "block gone");
+
+        s->strstart += s->lookahead;
+        s->lookahead = 0;
+
+        /* Emit a stored block if pending_buf will be full: */
+        max_start = s->block_start + max_block_size;
+        if (s->strstart == 0 || (ulg)s->strstart >= max_start) {
+            /* strstart == 0 is possible when wraparound on 16-bit machine */
+            s->lookahead = (uInt)(s->strstart - max_start);
+            s->strstart = (uInt)max_start;
+            FLUSH_BLOCK(s, 0);
+        }
+        /* Flush if we may have to slide, otherwise block_start may become
+         * negative and the data will be gone:
+         */
+        if (s->strstart - (uInt)s->block_start >= MAX_DIST(s)) {
+            FLUSH_BLOCK(s, 0);
+        }
+    }
+    FLUSH_BLOCK(s, flush == Z_FINISH);
+    return flush == Z_FINISH ? finish_done : block_done;
+}
+
+/* ===========================================================================
+ * Compress as much as possible from the input stream, return the current
+ * block state.
+ * This function does not perform lazy evaluation of matches and inserts
+ * new strings in the dictionary only for unmatched strings or for short
+ * matches. It is used only for the fast compression options.
+ */
+local block_state deflate_fast(s, flush)
+    deflate_state *s;
+    int flush;
+{
+    IPos hash_head;       /* head of the hash chain */
+    int bflush;           /* set if current block must be flushed */
+
+    for (;;) {
+        /* Make sure that we always have enough lookahead, except
+         * at the end of the input file. We need MAX_MATCH bytes
+         * for the next match, plus MIN_MATCH bytes to insert the
+         * string following the next match.
+         */
+        if (s->lookahead < MIN_LOOKAHEAD) {
+            fill_window(s);
+            if (s->lookahead < MIN_LOOKAHEAD && flush == Z_NO_FLUSH) {
+                return need_more;
+            }
+            if (s->lookahead == 0) break; /* flush the current block */
+        }
+
+        /* Insert the string window[strstart .. strstart+2] in the
+         * dictionary, and set hash_head to the head of the hash chain:
+         */
+        hash_head = NIL;
+        if (s->lookahead >= MIN_MATCH) {
+            INSERT_STRING(s, s->strstart, hash_head);
+        }
+
+        /* Find the longest match, discarding those <= prev_length.
+         * At this point we have always match_length < MIN_MATCH
+         */
+        if (hash_head != NIL && s->strstart - hash_head <= MAX_DIST(s)) {
+            /* To simplify the code, we prevent matches with the string
+             * of window index 0 (in particular we have to avoid a match
+             * of the string with itself at the start of the input file).
+             */
+            s->match_length = longest_match (s, hash_head);
+            /* longest_match() sets match_start */
+        }
+        if (s->match_length >= MIN_MATCH) {
+            check_match(s, s->strstart, s->match_start, s->match_length);
+
+            _tr_tally_dist(s, s->strstart - s->match_start,
+                           s->match_length - MIN_MATCH, bflush);
+
+            s->lookahead -= s->match_length;
+
+            /* Insert new strings in the hash table only if the match length
+             * is not too large. This saves time but degrades compression.
+             */
+#ifndef FASTEST
+            if (s->match_length <= s->max_insert_length &&
+                s->lookahead >= MIN_MATCH) {
+                s->match_length--; /* string at strstart already in table */
+                do {
+                    s->strstart++;
+                    INSERT_STRING(s, s->strstart, hash_head);
+                    /* strstart never exceeds WSIZE-MAX_MATCH, so there are
+                     * always MIN_MATCH bytes ahead.
+                     */
+                } while (--s->match_length != 0);
+                s->strstart++;
+            } else
+#endif
+            {
+                s->strstart += s->match_length;
+                s->match_length = 0;
+                s->ins_h = s->window[s->strstart];
+                UPDATE_HASH(s, s->ins_h, s->window[s->strstart+1]);
+#if MIN_MATCH != 3
+                Call UPDATE_HASH() MIN_MATCH-3 more times
+#endif
+                /* If lookahead < MIN_MATCH, ins_h is garbage, but it does not
+                 * matter since it will be recomputed at next deflate call.
+                 */
+            }
+        } else {
+            /* No match, output a literal byte */
+            Tracevv((stderr,"%c", s->window[s->strstart]));
+            _tr_tally_lit (s, s->window[s->strstart], bflush);
+            s->lookahead--;
+            s->strstart++;
+        }
+        if (bflush) FLUSH_BLOCK(s, 0);
+    }
+    FLUSH_BLOCK(s, flush == Z_FINISH);
+    return flush == Z_FINISH ? finish_done : block_done;
+}
+
+#ifndef FASTEST
+/* ===========================================================================
+ * Same as above, but achieves better compression. We use a lazy
+ * evaluation for matches: a match is finally adopted only if there is
+ * no better match at the next window position.
+ */
+local block_state deflate_slow(s, flush)
+    deflate_state *s;
+    int flush;
+{
+    IPos hash_head;          /* head of hash chain */
+    int bflush;              /* set if current block must be flushed */
+
+    /* Process the input block. */
+    for (;;) {
+        /* Make sure that we always have enough lookahead, except
+         * at the end of the input file. We need MAX_MATCH bytes
+         * for the next match, plus MIN_MATCH bytes to insert the
+         * string following the next match.
+         */
+        if (s->lookahead < MIN_LOOKAHEAD) {
+            fill_window(s);
+            if (s->lookahead < MIN_LOOKAHEAD && flush == Z_NO_FLUSH) {
+                return need_more;
+            }
+            if (s->lookahead == 0) break; /* flush the current block */
+        }
+
+        /* Insert the string window[strstart .. strstart+2] in the
+         * dictionary, and set hash_head to the head of the hash chain:
+         */
+        hash_head = NIL;
+        if (s->lookahead >= MIN_MATCH) {
+            INSERT_STRING(s, s->strstart, hash_head);
+        }
+
+        /* Find the longest match, discarding those <= prev_length.
+         */
+        s->prev_length = s->match_length, s->prev_match = s->match_start;
+        s->match_length = MIN_MATCH-1;
+
+        if (hash_head != NIL && s->prev_length < s->max_lazy_match &&
+            s->strstart - hash_head <= MAX_DIST(s)) {
+            /* To simplify the code, we prevent matches with the string
+             * of window index 0 (in particular we have to avoid a match
+             * of the string with itself at the start of the input file).
+             */
+            s->match_length = longest_match (s, hash_head);
+            /* longest_match() sets match_start */
+
+            if (s->match_length <= 5 && (s->strategy == Z_FILTERED
+#if TOO_FAR <= 32767
+                || (s->match_length == MIN_MATCH &&
+                    s->strstart - s->match_start > TOO_FAR)
+#endif
+                )) {
+
+                /* If prev_match is also MIN_MATCH, match_start is garbage
+                 * but we will ignore the current match anyway.
+                 */
+                s->match_length = MIN_MATCH-1;
+            }
+        }
+        /* If there was a match at the previous step and the current
+         * match is not better, output the previous match:
+         */
+        if (s->prev_length >= MIN_MATCH && s->match_length <= s->prev_length) {
+            uInt max_insert = s->strstart + s->lookahead - MIN_MATCH;
+            /* Do not insert strings in hash table beyond this. */
+
+            check_match(s, s->strstart-1, s->prev_match, s->prev_length);
+
+            _tr_tally_dist(s, s->strstart -1 - s->prev_match,
+                           s->prev_length - MIN_MATCH, bflush);
+
+            /* Insert in hash table all strings up to the end of the match.
+             * strstart-1 and strstart are already inserted. If there is not
+             * enough lookahead, the last two strings are not inserted in
+             * the hash table.
+             */
+            s->lookahead -= s->prev_length-1;
+            s->prev_length -= 2;
+            do {
+                if (++s->strstart <= max_insert) {
+                    INSERT_STRING(s, s->strstart, hash_head);
+                }
+            } while (--s->prev_length != 0);
+            s->match_available = 0;
+            s->match_length = MIN_MATCH-1;
+            s->strstart++;
+
+            if (bflush) FLUSH_BLOCK(s, 0);
+
+        } else if (s->match_available) {
+            /* If there was no match at the previous position, output a
+             * single literal. If there was a match but the current match
+             * is longer, truncate the previous match to a single literal.
+             */
+            Tracevv((stderr,"%c", s->window[s->strstart-1]));
+            _tr_tally_lit(s, s->window[s->strstart-1], bflush);
+            if (bflush) {
+                FLUSH_BLOCK_ONLY(s, 0);
+            }
+            s->strstart++;
+            s->lookahead--;
+            if (s->strm->avail_out == 0) return need_more;
+        } else {
+            /* There is no previous match to compare with, wait for
+             * the next step to decide.
+             */
+            s->match_available = 1;
+            s->strstart++;
+            s->lookahead--;
+        }
+    }
+    Assert (flush != Z_NO_FLUSH, "no flush?");
+    if (s->match_available) {
+        Tracevv((stderr,"%c", s->window[s->strstart-1]));
+        _tr_tally_lit(s, s->window[s->strstart-1], bflush);
+        s->match_available = 0;
+    }
+    FLUSH_BLOCK(s, flush == Z_FINISH);
+    return flush == Z_FINISH ? finish_done : block_done;
+}
+#endif /* FASTEST */
+
+/* ===========================================================================
+ * For Z_RLE, simply look for runs of bytes, generate matches only of distance
+ * one.  Do not maintain a hash table.  (It will be regenerated if this run of
+ * deflate switches away from Z_RLE.)
+ */
+local block_state deflate_rle(s, flush)
+    deflate_state *s;
+    int flush;
+{
+    int bflush;             /* set if current block must be flushed */
+    uInt prev;              /* byte at distance one to match */
+    Bytef *scan, *strend;   /* scan goes up to strend for length of run */
+
+    for (;;) {
+        /* Make sure that we always have enough lookahead, except
+         * at the end of the input file. We need MAX_MATCH bytes
+         * for the longest encodable run.
+         */
+        if (s->lookahead < MAX_MATCH) {
+            fill_window(s);
+            if (s->lookahead < MAX_MATCH && flush == Z_NO_FLUSH) {
+                return need_more;
+            }
+            if (s->lookahead == 0) break; /* flush the current block */
+        }
+
+        /* See how many times the previous byte repeats */
+        s->match_length = 0;
+        if (s->lookahead >= MIN_MATCH && s->strstart > 0) {
+            scan = s->window + s->strstart - 1;
+            prev = *scan;
+            if (prev == *++scan && prev == *++scan && prev == *++scan) {
+                strend = s->window + s->strstart + MAX_MATCH;
+                do {
+                } while (prev == *++scan && prev == *++scan &&
+                         prev == *++scan && prev == *++scan &&
+                         prev == *++scan && prev == *++scan &&
+                         prev == *++scan && prev == *++scan &&
+                         scan < strend);
+                s->match_length = MAX_MATCH - (int)(strend - scan);
+                if (s->match_length > s->lookahead)
+                    s->match_length = s->lookahead;
+            }
+        }
+
+        /* Emit match if have run of MIN_MATCH or longer, else emit literal */
+        if (s->match_length >= MIN_MATCH) {
+            check_match(s, s->strstart, s->strstart - 1, s->match_length);
+
+            _tr_tally_dist(s, 1, s->match_length - MIN_MATCH, bflush);
+
+            s->lookahead -= s->match_length;
+            s->strstart += s->match_length;
+            s->match_length = 0;
+        } else {
+            /* No match, output a literal byte */
+            Tracevv((stderr,"%c", s->window[s->strstart]));
+            _tr_tally_lit (s, s->window[s->strstart], bflush);
+            s->lookahead--;
+            s->strstart++;
+        }
+        if (bflush) FLUSH_BLOCK(s, 0);
+    }
+    FLUSH_BLOCK(s, flush == Z_FINISH);
+    return flush == Z_FINISH ? finish_done : block_done;
+}
+
+/* ===========================================================================
+ * For Z_HUFFMAN_ONLY, do not look for matches.  Do not maintain a hash table.
+ * (It will be regenerated if this run of deflate switches away from Huffman.)
+ */
+local block_state deflate_huff(s, flush)
+    deflate_state *s;
+    int flush;
+{
+    int bflush;             /* set if current block must be flushed */
+
+    for (;;) {
+        /* Make sure that we have a literal to write. */
+        if (s->lookahead == 0) {
+            fill_window(s);
+            if (s->lookahead == 0) {
+                if (flush == Z_NO_FLUSH)
+                    return need_more;
+                break;      /* flush the current block */
+            }
+        }
+
+        /* Output a literal byte */
+        s->match_length = 0;
+        Tracevv((stderr,"%c", s->window[s->strstart]));
+        _tr_tally_lit (s, s->window[s->strstart], bflush);
+        s->lookahead--;
+        s->strstart++;
+        if (bflush) FLUSH_BLOCK(s, 0);
+    }
+    FLUSH_BLOCK(s, flush == Z_FINISH);
+    return flush == Z_FINISH ? finish_done : block_done;
+}
diff --git a/src/plugins/cfitsio/deflate.h b/src/plugins/cfitsio/deflate.h
new file mode 100644
index 0000000..6ac0a1e
--- /dev/null
+++ b/src/plugins/cfitsio/deflate.h
@@ -0,0 +1,340 @@
+/* deflate.h -- internal compression state
+ * Copyright (C) 1995-2010 Jean-loup Gailly
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* WARNING: this file should *not* be used by applications. It is
+   part of the implementation of the compression library and is
+   subject to change. Applications should only use zlib.h.
+ */
+
+#ifndef DEFLATE_H
+#define DEFLATE_H
+
+#include "zutil.h"
+
+/* define NO_GZIP when compiling if you want to disable gzip header and
+   trailer creation by deflate().  NO_GZIP would be used to avoid linking in
+   the crc code when it is not needed.  For shared libraries, gzip encoding
+   should be left enabled. */
+#ifndef NO_GZIP
+#  define GZIP
+#endif
+
+/* ===========================================================================
+ * Internal compression state.
+ */
+
+#define LENGTH_CODES 29
+/* number of length codes, not counting the special END_BLOCK code */
+
+#define LITERALS  256
+/* number of literal bytes 0..255 */
+
+#define L_CODES (LITERALS+1+LENGTH_CODES)
+/* number of Literal or Length codes, including the END_BLOCK code */
+
+#define D_CODES   30
+/* number of distance codes */
+
+#define BL_CODES  19
+/* number of codes used to transfer the bit lengths */
+
+#define HEAP_SIZE (2*L_CODES+1)
+/* maximum heap size */
+
+#define MAX_BITS 15
+/* All codes must not exceed MAX_BITS bits */
+
+#define INIT_STATE    42
+#define EXTRA_STATE   69
+#define NAME_STATE    73
+#define COMMENT_STATE 91
+#define HCRC_STATE   103
+#define BUSY_STATE   113
+#define FINISH_STATE 666
+/* Stream status */
+
+
+/* Data structure describing a single value and its code string. */
+typedef struct ct_data_s {
+    union {
+        ush  freq;       /* frequency count */
+        ush  code;       /* bit string */
+    } fc;
+    union {
+        ush  dad;        /* father node in Huffman tree */
+        ush  len;        /* length of bit string */
+    } dl;
+} FAR ct_data;
+
+#define Freq fc.freq
+#define Code fc.code
+#define Dad  dl.dad
+#define Len  dl.len
+
+typedef struct static_tree_desc_s  static_tree_desc;
+
+typedef struct tree_desc_s {
+    ct_data *dyn_tree;           /* the dynamic tree */
+    int     max_code;            /* largest code with non zero frequency */
+    static_tree_desc *stat_desc; /* the corresponding static tree */
+} FAR tree_desc;
+
+typedef ush Pos;
+typedef Pos FAR Posf;
+typedef unsigned IPos;
+
+/* A Pos is an index in the character window. We use short instead of int to
+ * save space in the various tables. IPos is used only for parameter passing.
+ */
+
+typedef struct internal_state {
+    z_streamp strm;      /* pointer back to this zlib stream */
+    int   status;        /* as the name implies */
+    Bytef *pending_buf;  /* output still pending */
+    ulg   pending_buf_size; /* size of pending_buf */
+    Bytef *pending_out;  /* next pending byte to output to the stream */
+    uInt   pending;      /* nb of bytes in the pending buffer */
+    int   wrap;          /* bit 0 true for zlib, bit 1 true for gzip */
+    gz_headerp  gzhead;  /* gzip header information to write */
+    uInt   gzindex;      /* where in extra, name, or comment */
+    Byte  method;        /* STORED (for zip only) or DEFLATED */
+    int   last_flush;    /* value of flush param for previous deflate call */
+
+                /* used by deflate.c: */
+
+    uInt  w_size;        /* LZ77 window size (32K by default) */
+    uInt  w_bits;        /* log2(w_size)  (8..16) */
+    uInt  w_mask;        /* w_size - 1 */
+
+    Bytef *window;
+    /* Sliding window. Input bytes are read into the second half of the window,
+     * and move to the first half later to keep a dictionary of at least wSize
+     * bytes. With this organization, matches are limited to a distance of
+     * wSize-MAX_MATCH bytes, but this ensures that IO is always
+     * performed with a length multiple of the block size. Also, it limits
+     * the window size to 64K, which is quite useful on MSDOS.
+     * To do: use the user input buffer as sliding window.
+     */
+
+    ulg window_size;
+    /* Actual size of window: 2*wSize, except when the user input buffer
+     * is directly used as sliding window.
+     */
+
+    Posf *prev;
+    /* Link to older string with same hash index. To limit the size of this
+     * array to 64K, this link is maintained only for the last 32K strings.
+     * An index in this array is thus a window index modulo 32K.
+     */
+
+    Posf *head; /* Heads of the hash chains or NIL. */
+
+    uInt  ins_h;          /* hash index of string to be inserted */
+    uInt  hash_size;      /* number of elements in hash table */
+    uInt  hash_bits;      /* log2(hash_size) */
+    uInt  hash_mask;      /* hash_size-1 */
+
+    uInt  hash_shift;
+    /* Number of bits by which ins_h must be shifted at each input
+     * step. It must be such that after MIN_MATCH steps, the oldest
+     * byte no longer takes part in the hash key, that is:
+     *   hash_shift * MIN_MATCH >= hash_bits
+     */
+
+    long block_start;
+    /* Window position at the beginning of the current output block. Gets
+     * negative when the window is moved backwards.
+     */
+
+    uInt match_length;           /* length of best match */
+    IPos prev_match;             /* previous match */
+    int match_available;         /* set if previous match exists */
+    uInt strstart;               /* start of string to insert */
+    uInt match_start;            /* start of matching string */
+    uInt lookahead;              /* number of valid bytes ahead in window */
+
+    uInt prev_length;
+    /* Length of the best match at previous step. Matches not greater than this
+     * are discarded. This is used in the lazy match evaluation.
+     */
+
+    uInt max_chain_length;
+    /* To speed up deflation, hash chains are never searched beyond this
+     * length.  A higher limit improves compression ratio but degrades the
+     * speed.
+     */
+
+    uInt max_lazy_match;
+    /* Attempt to find a better match only when the current match is strictly
+     * smaller than this value. This mechanism is used only for compression
+     * levels >= 4.
+     */
+#   define max_insert_length  max_lazy_match
+    /* Insert new strings in the hash table only if the match length is not
+     * greater than this length. This saves time but degrades compression.
+     * max_insert_length is used only for compression levels <= 3.
+     */
+
+    int level;    /* compression level (1..9) */
+    int strategy; /* favor or force Huffman coding*/
+
+    uInt good_match;
+    /* Use a faster search when the previous match is longer than this */
+
+    int nice_match; /* Stop searching when current match exceeds this */
+
+                /* used by trees.c: */
+    /* Didn't use ct_data typedef below to supress compiler warning */
+    struct ct_data_s dyn_ltree[HEAP_SIZE];   /* literal and length tree */
+    struct ct_data_s dyn_dtree[2*D_CODES+1]; /* distance tree */
+    struct ct_data_s bl_tree[2*BL_CODES+1];  /* Huffman tree for bit lengths */
+
+    struct tree_desc_s l_desc;               /* desc. for literal tree */
+    struct tree_desc_s d_desc;               /* desc. for distance tree */
+    struct tree_desc_s bl_desc;              /* desc. for bit length tree */
+
+    ush bl_count[MAX_BITS+1];
+    /* number of codes at each bit length for an optimal tree */
+
+    int heap[2*L_CODES+1];      /* heap used to build the Huffman trees */
+    int heap_len;               /* number of elements in the heap */
+    int heap_max;               /* element of largest frequency */
+    /* The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used.
+     * The same heap array is used to build all trees.
+     */
+
+    uch depth[2*L_CODES+1];
+    /* Depth of each subtree used as tie breaker for trees of equal frequency
+     */
+
+    uchf *l_buf;          /* buffer for literals or lengths */
+
+    uInt  lit_bufsize;
+    /* Size of match buffer for literals/lengths.  There are 4 reasons for
+     * limiting lit_bufsize to 64K:
+     *   - frequencies can be kept in 16 bit counters
+     *   - if compression is not successful for the first block, all input
+     *     data is still in the window so we can still emit a stored block even
+     *     when input comes from standard input.  (This can also be done for
+     *     all blocks if lit_bufsize is not greater than 32K.)
+     *   - if compression is not successful for a file smaller than 64K, we can
+     *     even emit a stored file instead of a stored block (saving 5 bytes).
+     *     This is applicable only for zip (not gzip or zlib).
+     *   - creating new Huffman trees less frequently may not provide fast
+     *     adaptation to changes in the input data statistics. (Take for
+     *     example a binary file with poorly compressible code followed by
+     *     a highly compressible string table.) Smaller buffer sizes give
+     *     fast adaptation but have of course the overhead of transmitting
+     *     trees more frequently.
+     *   - I can't count above 4
+     */
+
+    uInt last_lit;      /* running index in l_buf */
+
+    ushf *d_buf;
+    /* Buffer for distances. To simplify the code, d_buf and l_buf have
+     * the same number of elements. To use different lengths, an extra flag
+     * array would be necessary.
+     */
+
+    ulg opt_len;        /* bit length of current block with optimal trees */
+    ulg static_len;     /* bit length of current block with static trees */
+    uInt matches;       /* number of string matches in current block */
+    int last_eob_len;   /* bit length of EOB code for last block */
+
+#ifdef DEBUG
+    ulg compressed_len; /* total bit length of compressed file mod 2^32 */
+    ulg bits_sent;      /* bit length of compressed data sent mod 2^32 */
+#endif
+
+    ush bi_buf;
+    /* Output buffer. bits are inserted starting at the bottom (least
+     * significant bits).
+     */
+    int bi_valid;
+    /* Number of valid bits in bi_buf.  All bits above the last valid bit
+     * are always zero.
+     */
+
+    ulg high_water;
+    /* High water mark offset in window for initialized bytes -- bytes above
+     * this are set to zero in order to avoid memory check warnings when
+     * longest match routines access bytes past the input.  This is then
+     * updated to the new high water mark.
+     */
+
+} FAR deflate_state;
+
+/* Output a byte on the stream.
+ * IN assertion: there is enough room in pending_buf.
+ */
+#define put_byte(s, c) {s->pending_buf[s->pending++] = (c);}
+
+
+#define MIN_LOOKAHEAD (MAX_MATCH+MIN_MATCH+1)
+/* Minimum amount of lookahead, except at the end of the input file.
+ * See deflate.c for comments about the MIN_MATCH+1.
+ */
+
+#define MAX_DIST(s)  ((s)->w_size-MIN_LOOKAHEAD)
+/* In order to simplify the code, particularly on 16 bit machines, match
+ * distances are limited to MAX_DIST instead of WSIZE.
+ */
+
+#define WIN_INIT MAX_MATCH
+/* Number of bytes after end of data in window to initialize in order to avoid
+   memory checker errors from longest match routines */
+
+        /* in trees.c */
+void ZLIB_INTERNAL _tr_init OF((deflate_state *s));
+int ZLIB_INTERNAL _tr_tally OF((deflate_state *s, unsigned dist, unsigned lc));
+void ZLIB_INTERNAL _tr_flush_block OF((deflate_state *s, charf *buf,
+                        ulg stored_len, int last));
+void ZLIB_INTERNAL _tr_align OF((deflate_state *s));
+void ZLIB_INTERNAL _tr_stored_block OF((deflate_state *s, charf *buf,
+                        ulg stored_len, int last));
+
+#define d_code(dist) \
+   ((dist) < 256 ? _dist_code[dist] : _dist_code[256+((dist)>>7)])
+/* Mapping from a distance to a distance code. dist is the distance - 1 and
+ * must not have side effects. _dist_code[256] and _dist_code[257] are never
+ * used.
+ */
+
+#ifndef DEBUG
+/* Inline versions of _tr_tally for speed: */
+
+#if defined(GEN_TREES_H) || !defined(STDC)
+  extern uch ZLIB_INTERNAL _length_code[];
+  extern uch ZLIB_INTERNAL _dist_code[];
+#else
+  extern const uch ZLIB_INTERNAL _length_code[];
+  extern const uch ZLIB_INTERNAL _dist_code[];
+#endif
+
+# define _tr_tally_lit(s, c, flush) \
+  { uch cc = (c); \
+    s->d_buf[s->last_lit] = 0; \
+    s->l_buf[s->last_lit++] = cc; \
+    s->dyn_ltree[cc].Freq++; \
+    flush = (s->last_lit == s->lit_bufsize-1); \
+   }
+# define _tr_tally_dist(s, distance, length, flush) \
+  { uch len = (length); \
+    ush dist = (distance); \
+    s->d_buf[s->last_lit] = dist; \
+    s->l_buf[s->last_lit++] = len; \
+    dist--; \
+    s->dyn_ltree[_length_code[len]+LITERALS+1].Freq++; \
+    s->dyn_dtree[d_code(dist)].Freq++; \
+    flush = (s->last_lit == s->lit_bufsize-1); \
+  }
+#else
+# define _tr_tally_lit(s, c, flush) flush = _tr_tally(s, 0, c)
+# define _tr_tally_dist(s, distance, length, flush) \
+              flush = _tr_tally(s, distance, length)
+#endif
+
+#endif /* DEFLATE_H */
diff --git a/src/plugins/cfitsio/drvrfile.c b/src/plugins/cfitsio/drvrfile.c
new file mode 100644
index 0000000..7cfa9c1
--- /dev/null
+++ b/src/plugins/cfitsio/drvrfile.c
@@ -0,0 +1,900 @@
+/*  This file, drvrfile.c contains driver routines for disk files.         */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+
+#include <string.h>
+#include <stdlib.h>
+#include "fitsio2.h"
+
+#if defined(unix) || defined(__unix__)  || defined(__unix)
+#include <pwd.h>         /* needed in file_openfile */
+
+#ifdef REPLACE_LINKS
+#include <sys/types.h>
+#include <sys/stat.h>
+#endif
+
+#endif
+
+#ifdef HAVE_FTRUNCATE
+#if defined(unix) || defined(__unix__)  || defined(__unix)
+#include <unistd.h>  /* needed for getcwd prototype on unix machines */
+#endif
+#endif
+
+#define IO_SEEK 0        /* last file I/O operation was a seek */
+#define IO_READ 1        /* last file I/O operation was a read */
+#define IO_WRITE 2       /* last file I/O operation was a write */
+
+static char file_outfile[FLEN_FILENAME];
+
+typedef struct    /* structure containing disk file structure */ 
+{
+    FILE *fileptr;
+    LONGLONG currentpos;
+    int last_io_op;
+} diskdriver;
+
+static diskdriver handleTable[NMAXFILES]; /* allocate diskfile handle tables */
+
+/*--------------------------------------------------------------------------*/
+int file_init(void)
+{
+    int ii;
+
+    for (ii = 0; ii < NMAXFILES; ii++) /* initialize all empty slots in table */
+    {
+       handleTable[ii].fileptr = 0;
+    }
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int file_setoptions(int options)
+{
+  /* do something with the options argument, to stop compiler warning */
+  options = 0;
+  return(options);
+}
+/*--------------------------------------------------------------------------*/
+int file_getoptions(int *options)
+{
+  *options = 0;
+  return(0);
+}
+/*--------------------------------------------------------------------------*/
+int file_getversion(int *version)
+{
+    *version = 10;
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int file_shutdown(void)
+{
+  return(0);
+}
+/*--------------------------------------------------------------------------*/
+int file_open(char *filename, int rwmode, int *handle)
+{
+    FILE *diskfile;
+    int copyhandle, ii, status;
+    char recbuf[2880];
+    size_t nread;
+
+    /*
+       if an output filename has been specified as part of the input
+       file, as in "inputfile.fits(outputfile.fit)" then we have to
+       create the output file, copy the input to it, then reopen the
+       the new copy.
+    */
+
+    if (*file_outfile)
+    {
+      /* open the original file, with readonly access */
+      status = file_openfile(filename, READONLY, &diskfile);
+      if (status) {
+        file_outfile[0] = '\0';
+        return(status);
+      }
+      
+      /* create the output file */
+      status =  file_create(file_outfile,handle);
+      if (status)
+      {
+        ffpmsg("Unable to create output file for copy of input file:");
+        ffpmsg(file_outfile);
+        file_outfile[0] = '\0';
+        return(status);
+      }
+
+      /* copy the file from input to output */
+      while(0 != (nread = fread(recbuf,1,2880, diskfile)))
+      {
+        status = file_write(*handle, recbuf, nread);
+        if (status) {
+	   file_outfile[0] = '\0';
+           return(status);
+        }
+      }
+
+      /* close both files */
+      fclose(diskfile);
+      copyhandle = *handle;
+      file_close(*handle);
+      *handle = copyhandle;  /* reuse the old file handle */
+
+      /* reopen the new copy, with correct rwmode */
+      status = file_openfile(file_outfile, rwmode, &diskfile);
+      file_outfile[0] = '\0';
+    }
+    else
+    {
+      *handle = -1;
+      for (ii = 0; ii < NMAXFILES; ii++)  /* find empty slot in table */
+      {
+        if (handleTable[ii].fileptr == 0)
+        {
+            *handle = ii;
+            break;
+        }
+      }
+
+      if (*handle == -1)
+       return(TOO_MANY_FILES);    /* too many files opened */
+
+      /*open the file */
+      status = file_openfile(filename, rwmode, &diskfile);
+    }
+
+    handleTable[*handle].fileptr = diskfile;
+    handleTable[*handle].currentpos = 0;
+    handleTable[*handle].last_io_op = IO_SEEK;
+
+    return(status);
+}
+/*--------------------------------------------------------------------------*/
+int file_openfile(char *filename, int rwmode, FILE **diskfile)
+/*
+   lowest level routine to physically open a disk file
+*/
+{
+    char mode[4];
+
+#if defined(unix) || defined(__unix__) || defined(__unix)
+    char tempname[1024], *cptr, user[80];
+    struct passwd *pwd;
+    int ii = 0;
+
+#if defined(REPLACE_LINKS)
+    struct stat stbuf;
+    int success = 0;
+    size_t n;
+    FILE *f1, *f2;
+    char buf[BUFSIZ];
+#endif
+
+#endif
+
+    if (rwmode == READWRITE)
+    {
+          strcpy(mode, "r+b");    /* open existing file with read-write */
+    }
+    else
+    {
+          strcpy(mode, "rb");     /* open existing file readonly */
+    }
+
+#if MACHINE == ALPHAVMS || MACHINE == VAXVMS
+        /* specify VMS record structure: fixed format, 2880 byte records */
+        /* but force stream mode access to enable random I/O access      */
+    *diskfile = fopen(filename, mode, "rfm=fix", "mrs=2880", "ctx=stm"); 
+
+#elif defined(unix) || defined(__unix__) || defined(__unix)
+
+    /* support the ~user/file.fits or ~/file.fits filenames in UNIX */
+
+    if (*filename == '~')
+    {
+        if (filename[1] == '/')
+        {
+            cptr = getenv("HOME");
+            if (cptr)
+            {
+                 if (strlen(cptr) + strlen(filename+1) > 1023)
+		      return(FILE_NOT_OPENED); 
+
+                 strcpy(tempname, cptr);
+                 strcat(tempname, filename+1);
+            }
+            else
+            {
+                 if (strlen(filename) > 1023)
+		      return(FILE_NOT_OPENED); 
+
+                 strcpy(tempname, filename);
+            }
+        }
+        else
+        {
+            /* copy user name */
+            cptr = filename+1;
+            while (*cptr && (*cptr != '/'))
+            {
+                user[ii] = *cptr;
+                cptr++;
+                ii++;
+            }
+            user[ii] = '\0';
+
+            /* get structure that includes name of user's home directory */
+            pwd = getpwnam(user);
+
+            /* copy user's home directory */
+            if (strlen(pwd->pw_dir) + strlen(cptr) > 1023)
+		      return(FILE_NOT_OPENED); 
+
+            strcpy(tempname, pwd->pw_dir);
+            strcat(tempname, cptr);
+        }
+
+        *diskfile = fopen(tempname, mode); 
+    }
+    else
+    {
+        /* don't need to expand the input file name */
+        *diskfile = fopen(filename, mode); 
+
+#if defined(REPLACE_LINKS)
+
+        if (!(*diskfile) && (rwmode == READWRITE))  
+        {
+           /* failed to open file with READWRITE privilege.  Test if  */
+           /* the file we are trying to open is a soft link to a file that */
+           /* doesn't have write privilege.  */
+
+           lstat(filename, &stbuf);
+           if ((stbuf.st_mode & S_IFMT) == S_IFLNK) /* is this a soft link? */
+           {
+              if ((f1 = fopen(filename, "rb")) != 0) /* try opening READONLY */
+              {
+
+                 if (strlen(filename) + 7 > 1023)
+		      return(FILE_NOT_OPENED); 
+
+                 strcpy(tempname, filename);
+                 strcat(tempname, ".TmxFil");
+                 if ((f2 = fopen(tempname, "wb")) != 0) /* create temp file */
+                 {
+                    success = 1;
+                    while ((n = fread(buf, 1, BUFSIZ, f1)) > 0)
+                    {
+                       /* copy linked file to local temporary file */
+                       if (fwrite(buf, 1, n, f2) != n) 
+                       {
+                          success = 0;
+                          break;
+                       } 
+                    }
+                    fclose(f2);
+                 }
+                 fclose(f1);
+  
+                 if (success)
+                 {
+                    /* delete link and rename temp file to previous link name */
+                    remove(filename);
+                    rename(tempname, filename);
+
+                    /* try once again to open the file with write access */
+                    *diskfile = fopen(filename, mode); 
+                 }
+                 else
+                    remove(tempname);  /* clean up the failed copy */
+              }
+           }
+        }
+#endif
+
+    }
+
+#else
+
+    /* other non-UNIX machines */
+    *diskfile = fopen(filename, mode); 
+
+#endif
+
+    if (!(*diskfile))           /* couldn't open file */
+    {
+            return(FILE_NOT_OPENED); 
+    }
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int file_create(char *filename, int *handle)
+{
+    FILE *diskfile;
+    int ii;
+    char mode[4];
+
+    *handle = -1;
+    for (ii = 0; ii < NMAXFILES; ii++)  /* find empty slot in table */
+    {
+        if (handleTable[ii].fileptr == 0)
+        {
+            *handle = ii;
+            break;
+        }
+    }
+    if (*handle == -1)
+       return(TOO_MANY_FILES);    /* too many files opened */
+
+    strcpy(mode, "w+b");    /* create new file with read-write */
+
+    diskfile = fopen(filename, "r"); /* does file already exist? */
+
+    if (diskfile)
+    {
+        fclose(diskfile);         /* close file and exit with error */
+        return(FILE_NOT_CREATED); 
+    }
+
+#if MACHINE == ALPHAVMS || MACHINE == VAXVMS
+        /* specify VMS record structure: fixed format, 2880 byte records */
+        /* but force stream mode access to enable random I/O access      */
+    diskfile = fopen(filename, mode, "rfm=fix", "mrs=2880", "ctx=stm"); 
+#else
+    diskfile = fopen(filename, mode); 
+#endif
+
+    if (!(diskfile))           /* couldn't create file */
+    {
+            return(FILE_NOT_CREATED); 
+    }
+
+    handleTable[ii].fileptr = diskfile;
+    handleTable[ii].currentpos = 0;
+    handleTable[ii].last_io_op = IO_SEEK;
+
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int file_truncate(int handle, LONGLONG filesize)
+/*
+  truncate the diskfile to a new smaller size
+*/
+{
+
+#ifdef HAVE_FTRUNCATE
+    int fdesc;
+
+    fdesc = fileno(handleTable[handle].fileptr);
+    ftruncate(fdesc, (OFF_T) filesize);
+    file_seek(handle, filesize);
+
+    handleTable[handle].currentpos = filesize;
+    handleTable[handle].last_io_op = IO_SEEK;
+
+#endif
+
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int file_size(int handle, LONGLONG *filesize)
+/*
+  return the size of the file in bytes
+*/
+{
+    OFF_T position1,position2;
+    FILE *diskfile;
+
+    diskfile = handleTable[handle].fileptr;
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1400)
+ 
+/* call the VISUAL C++ version of the routines which support */
+/*  Large Files (> 2GB) if they are supported (since VC 8.0)  */
+
+    position1 = _ftelli64(diskfile);   /* save current postion */
+    if (position1 < 0)
+        return(SEEK_ERROR);
+
+    if (_fseeki64(diskfile, 0, 2) != 0)  /* seek to end of file */
+        return(SEEK_ERROR);
+
+    position2 = _ftelli64(diskfile);     /* get file size */
+    if (position2 < 0)
+        return(SEEK_ERROR);
+
+    if (_fseeki64(diskfile, position1, 0) != 0)  /* seek back to original pos */
+        return(SEEK_ERROR);
+
+#elif _FILE_OFFSET_BITS - 0 == 64
+
+/* call the newer ftello and fseeko routines , which support */
+/*  Large Files (> 2GB) if they are supported.  */
+
+    position1 = ftello(diskfile);   /* save current postion */
+    if (position1 < 0)
+        return(SEEK_ERROR);
+
+    if (fseeko(diskfile, 0, 2) != 0)  /* seek to end of file */
+        return(SEEK_ERROR);
+
+    position2 = ftello(diskfile);     /* get file size */
+    if (position2 < 0)
+        return(SEEK_ERROR);
+
+    if (fseeko(diskfile, position1, 0) != 0)  /* seek back to original pos */
+        return(SEEK_ERROR);
+
+#else
+
+    position1 = ftell(diskfile);   /* save current postion */
+    if (position1 < 0)
+        return(SEEK_ERROR);
+
+    if (fseek(diskfile, 0, 2) != 0)  /* seek to end of file */
+        return(SEEK_ERROR);
+
+    position2 = ftell(diskfile);     /* get file size */
+    if (position2 < 0)
+        return(SEEK_ERROR);
+
+    if (fseek(diskfile, position1, 0) != 0)  /* seek back to original pos */
+        return(SEEK_ERROR);
+
+#endif
+
+    *filesize = (LONGLONG) position2;
+    
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int file_close(int handle)
+/*
+  close the file
+*/
+{
+    
+    if (fclose(handleTable[handle].fileptr) )
+        return(FILE_NOT_CLOSED);
+
+    handleTable[handle].fileptr = 0;
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int file_remove(char *filename)
+/*
+  delete the file from disk
+*/
+{
+    remove(filename);
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int file_flush(int handle)
+/*
+  flush the file
+*/
+{
+    if (fflush(handleTable[handle].fileptr) )
+        return(WRITE_ERROR);
+
+    /* The flush operation is not supposed to move the internal */
+    /* file pointer, but it does on some Windows-95 compilers and */
+    /* perhaps others, so seek to original position to be sure. */
+    /* This seek will do no harm on other systems.   */
+
+#if MACHINE == IBMPC
+
+    if (file_seek(handle, handleTable[handle].currentpos))
+            return(SEEK_ERROR);
+
+#endif
+
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int file_seek(int handle, LONGLONG offset)
+/*
+  seek to position relative to start of the file
+*/
+{
+
+#if defined(_MSC_VER) && (_MSC_VER >= 1400)
+    
+     /* Microsoft visual studio C++ */
+     /* _fseeki64 supported beginning with version 8.0 */
+ 
+    if (_fseeki64(handleTable[handle].fileptr, (OFF_T) offset, 0) != 0)
+        return(SEEK_ERROR);
+	
+#elif _FILE_OFFSET_BITS - 0 == 64
+
+    if (fseeko(handleTable[handle].fileptr, (OFF_T) offset, 0) != 0)
+        return(SEEK_ERROR);
+
+#else
+
+    if (fseek(handleTable[handle].fileptr, (OFF_T) offset, 0) != 0)
+        return(SEEK_ERROR);
+
+#endif
+
+    handleTable[handle].currentpos = offset;
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int file_read(int hdl, void *buffer, long nbytes)
+/*
+  read bytes from the current position in the file
+*/
+{
+    long nread;
+    char *cptr;
+
+    if (handleTable[hdl].last_io_op == IO_WRITE)
+    {
+        if (file_seek(hdl, handleTable[hdl].currentpos))
+            return(SEEK_ERROR);
+    }
+  
+    nread = (long) fread(buffer, 1, nbytes, handleTable[hdl].fileptr);
+
+    if (nread == 1)
+    {
+         cptr = (char *) buffer;
+
+         /* some editors will add a single end-of-file character to a file */
+         /* Ignore it if the character is a zero, 10, or 32 */
+         if (*cptr == 0 || *cptr == 10 || *cptr == 32)
+             return(END_OF_FILE);
+         else
+             return(READ_ERROR);
+    }
+    else if (nread != nbytes)
+    {
+        return(READ_ERROR);
+    }
+
+    handleTable[hdl].currentpos += nbytes;
+    handleTable[hdl].last_io_op = IO_READ;
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int file_write(int hdl, void *buffer, long nbytes)
+/*
+  write bytes at the current position in the file
+*/
+{
+    if (handleTable[hdl].last_io_op == IO_READ) 
+    {
+        if (file_seek(hdl, handleTable[hdl].currentpos))
+            return(SEEK_ERROR);
+    }
+
+    if((long) fwrite(buffer, 1, nbytes, handleTable[hdl].fileptr) != nbytes)
+        return(WRITE_ERROR);
+
+    handleTable[hdl].currentpos += nbytes;
+    handleTable[hdl].last_io_op = IO_WRITE;
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int file_compress_open(char *filename, int rwmode, int *hdl)
+/*
+  This routine opens the compressed diskfile by creating a new uncompressed
+  file then opening it.  The input file name (the name of the compressed
+  file) gets replaced with the name of the uncompressed file, which is
+  initially stored in the global file_outfile string.   file_outfile
+  then gets set to a null string.
+*/
+{
+    FILE *indiskfile, *outdiskfile;
+    int status;
+    char *cptr;
+
+    /* open the compressed disk file */
+    status = file_openfile(filename, READONLY, &indiskfile);
+    if (status)
+    {
+        ffpmsg("failed to open compressed disk file (file_compress_open)");
+        ffpmsg(filename);
+        return(status);
+    }
+
+    /* name of the output uncompressed file is stored in the */
+    /* global variable called 'file_outfile'.                */
+
+    cptr = file_outfile;
+    if (*cptr == '!')
+    {
+        /* clobber any existing file with the same name */
+        cptr++;
+        remove(cptr);
+    }
+    else
+    {
+        outdiskfile = fopen(file_outfile, "r"); /* does file already exist? */
+
+        if (outdiskfile)
+        {
+          ffpmsg("uncompressed file already exists: (file_compress_open)");
+          ffpmsg(file_outfile);
+          fclose(outdiskfile);         /* close file and exit with error */
+	  file_outfile[0] = '\0';
+          return(FILE_NOT_CREATED); 
+        }
+    }
+
+    outdiskfile = fopen(cptr, "w+b"); /* create new file */
+    if (!outdiskfile)
+    {
+        ffpmsg("could not create uncompressed file: (file_compress_open)");
+        ffpmsg(file_outfile);
+	file_outfile[0] = '\0';
+        return(FILE_NOT_CREATED); 
+    }
+
+    /* uncompress file into another file */
+    uncompress2file(filename, indiskfile, outdiskfile, &status);
+    fclose(indiskfile);
+    fclose(outdiskfile);
+
+    if (status)
+    {
+        ffpmsg("error in file_compress_open: failed to uncompressed file:");
+        ffpmsg(filename);
+        ffpmsg(" into new output file:");
+        ffpmsg(file_outfile);
+	file_outfile[0] = '\0';
+        return(status);
+    }
+
+    strcpy(filename, cptr);  /* switch the names */
+    file_outfile[0] = '\0';
+
+    status = file_open(filename, rwmode, hdl);
+
+    return(status);
+}
+/*--------------------------------------------------------------------------*/
+int file_is_compressed(char *filename) /* I - FITS file name          */
+/*
+  Test if the disk file is compressed.  Returns 1 if compressed, 0 if not.
+  This may modify the filename string by appending a compression suffex.
+*/
+{
+    FILE *diskfile;
+    unsigned char buffer[2];
+    char tmpfilename[FLEN_FILENAME];
+
+    /* Open file.  Try various suffix combinations */  
+    if (file_openfile(filename, 0, &diskfile))
+    {
+      if (strlen(filename) > FLEN_FILENAME - 1)
+          return(0);
+
+      strcpy(tmpfilename,filename);
+      strcat(filename,".gz");
+      if (file_openfile(filename, 0, &diskfile))
+      {
+        strcpy(filename, tmpfilename);
+        strcat(filename,".Z");
+        if (file_openfile(filename, 0, &diskfile))
+        {
+          strcpy(filename, tmpfilename);
+          strcat(filename,".z");   /* it's often lower case on CDROMs */
+          if (file_openfile(filename, 0, &diskfile))
+          {
+            strcpy(filename, tmpfilename);
+            strcat(filename,".zip");
+            if (file_openfile(filename, 0, &diskfile))
+            {
+              strcpy(filename, tmpfilename);
+              strcat(filename,"-z");      /* VMS suffix */
+              if (file_openfile(filename, 0, &diskfile))
+              {
+                strcpy(filename, tmpfilename);
+                strcat(filename,"-gz");    /* VMS suffix */
+                if (file_openfile(filename, 0, &diskfile))
+                {
+                  strcpy(filename,tmpfilename);  /* restore original name */
+                  return(0);    /* file not found */
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+
+    if (fread(buffer, 1, 2, diskfile) != 2)  /* read 2 bytes */
+    {
+        fclose(diskfile);   /* error reading file so just return */
+        return(0);
+    }
+
+    fclose(diskfile);
+
+       /* see if the 2 bytes have the magic values for a compressed file */
+    if ( (memcmp(buffer, "\037\213", 2) == 0) ||  /* GZIP  */
+         (memcmp(buffer, "\120\113", 2) == 0) ||  /* PKZIP */
+         (memcmp(buffer, "\037\036", 2) == 0) ||  /* PACK  */
+         (memcmp(buffer, "\037\235", 2) == 0) ||  /* LZW   */
+         (memcmp(buffer, "\037\240", 2) == 0) )   /* LZH   */
+        {
+            return(1);  /* this is a compressed file */
+        }
+    else
+        {
+            return(0);  /* not a compressed file */
+        }
+}
+/*--------------------------------------------------------------------------*/
+int file_checkfile (char *urltype, char *infile, char *outfile) 
+{
+    /* special case: if file:// driver, check if the file is compressed */
+    if ( file_is_compressed(infile) )
+    {
+      /* if output file has been specified, save the name for future use: */
+      /* This is the name of the uncompressed file to be created on disk. */
+      if (strlen(outfile))
+      {
+        if (!strncmp(outfile, "mem:", 4) )
+        {
+           /* uncompress the file in memory, with READ and WRITE access */
+           strcpy(urltype, "compressmem://");  /* use special driver */
+           *file_outfile = '\0';  
+        }
+        else
+        {
+          strcpy(urltype, "compressfile://");  /* use special driver */
+
+          /* don't copy the "file://" prefix, if present.  */
+          if (!strncmp(outfile, "file://", 7) )
+             strcpy(file_outfile,outfile+7);
+          else
+             strcpy(file_outfile,outfile);
+        }
+      }
+      else
+      {
+        /* uncompress the file in memory */
+        strcpy(urltype, "compress://");  /* use special driver */
+        *file_outfile = '\0';  /* no output file was specified */
+      }
+    }
+    else  /* an ordinary, uncompressed FITS file on disk */
+    {
+        /* save the output file name for later use when opening the file. */
+        /* In this case, the file to be opened will be opened READONLY,   */
+        /* and copied to this newly created output file.  The original file */
+        /* will be closed, and the copy will be opened by CFITSIO for     */
+        /* subsequent processing (possibly with READWRITE access).        */
+        if (strlen(outfile)) {
+	    file_outfile[0] = '\0';
+            strncat(file_outfile,outfile,FLEN_FILENAME-1);
+        }
+    }
+
+    return 0;
+}
+/**********************************************************************/
+/**********************************************************************/
+/**********************************************************************/
+
+/****  driver routines for stream//: device (stdin or stdout)  ********/
+
+
+/*--------------------------------------------------------------------------*/
+int stream_open(char *filename, int rwmode, int *handle)
+{
+    /*
+        read from stdin
+    */
+    if (filename)
+      rwmode = 1;  /* dummy statement to suppress unused parameter compiler warning */
+
+    *handle = 1;     /*  1 = stdin */   
+
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int stream_create(char *filename, int *handle)
+{
+    /*
+        write to stdout
+    */
+
+    if (filename)  /* dummy statement to suppress unused parameter compiler warning */
+       *handle = 2;
+    else
+       *handle = 2;         /*  2 = stdout */       
+
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int stream_size(int handle, LONGLONG *filesize)
+/*
+  return the size of the file in bytes
+*/
+{
+    handle = 0;  /* suppress unused parameter compiler warning */
+    
+    /* this operation is not supported in a stream; return large value */
+    *filesize = LONG_MAX;
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int stream_close(int handle)
+/*
+     don't have to close stdin or stdout 
+*/
+{
+    handle = 0;  /* suppress unused parameter compiler warning */
+    
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int stream_flush(int handle)
+/*
+  flush the file
+*/
+{
+    if (handle == 2)
+       fflush(stdout);  
+
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int stream_seek(int handle, LONGLONG offset)
+   /* 
+      seeking is not allowed in a stream
+   */
+{
+    offset = handle;  /* suppress unused parameter compiler warning */
+    return(1);
+}
+/*--------------------------------------------------------------------------*/
+int stream_read(int hdl, void *buffer, long nbytes)
+/*
+     reading from stdin stream 
+*/
+
+{
+    long nread;
+    
+    if (hdl != 1)
+       return(1);  /* can only read from stdin */
+
+    nread = (long) fread(buffer, 1, nbytes, stdin);
+
+    if (nread != nbytes)
+    {
+/*        return(READ_ERROR); */
+        return(END_OF_FILE);
+    }
+
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int stream_write(int hdl, void *buffer, long nbytes)
+/*
+  write bytes at the current position in the file
+*/
+{
+    if (hdl != 2)
+       return(1);  /* can only write to stdout */
+
+    if((long) fwrite(buffer, 1, nbytes, stdout) != nbytes)
+        return(WRITE_ERROR);
+
+    return(0);
+}
+
+
+
+
diff --git a/src/plugins/cfitsio/drvrgsiftp.c b/src/plugins/cfitsio/drvrgsiftp.c
new file mode 100644
index 0000000..ab9aaed
--- /dev/null
+++ b/src/plugins/cfitsio/drvrgsiftp.c
@@ -0,0 +1,522 @@
+
+/*  This file, drvrgsiftp.c contains driver routines for gsiftp files. */
+/*  Andrea Barisani <lcars si inaf it>                                 */
+/* Taffoni Giuliano <taffoni oats inaf it>                             */
+#ifdef HAVE_NET_SERVICES
+#ifdef HAVE_GSIFTP
+
+#include <sys/types.h>
+#include <string.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <setjmp.h>
+#include "fitsio2.h"
+
+#include <globus_ftp_client.h>
+
+#define MAXLEN 1200
+#define NETTIMEOUT 80
+#define MAX_BUFFER_SIZE_R 1024
+#define MAX_BUFFER_SIZE_W (64*1024)
+
+static int gsiftpopen = 0;
+static int global_offset = 0;
+static int gsiftp_get(char *filename, FILE **gsiftpfile, int num_streams);
+
+static globus_mutex_t lock;
+static globus_cond_t cond;
+static globus_bool_t done;
+
+static char *gsiftp_tmpfile;
+static char *gsiftpurl = NULL;
+static char gsiftp_tmpdir[MAXLEN];
+
+static jmp_buf env; /* holds the jump buffer for setjmp/longjmp pairs */
+static void signal_handler(int sig);
+
+int gsiftp_init(void)
+{
+
+  if (getenv("GSIFTP_TMPFILE")) {
+    gsiftp_tmpfile = getenv("GSIFTP_TMPFILE");
+  } else {
+    strncpy(gsiftp_tmpdir, "/tmp/gsiftp_XXXXXX", sizeof gsiftp_tmpdir);
+    if (mkdtemp(gsiftp_tmpdir) == NULL) {
+        ffpmsg("Cannot create temporary directory!");
+        return (FILE_NOT_OPENED);
+    }
+    gsiftp_tmpfile = malloc(strlen(gsiftp_tmpdir) + strlen("/gsiftp_buffer.tmp"));
+    strcat(gsiftp_tmpfile, gsiftp_tmpdir);
+    strcat(gsiftp_tmpfile, "/gsiftp_buffer.tmp");
+  }
+
+  return file_init();
+}
+
+int gsiftp_shutdown(void)
+{
+  free(gsiftpurl);
+  free(gsiftp_tmpfile);
+  free(gsiftp_tmpdir);
+
+  return file_shutdown();
+}
+
+int gsiftp_setoptions(int options)
+{
+  return file_setoptions(options);
+}
+
+int gsiftp_getoptions(int *options)
+{
+  return file_getoptions(options);
+}
+
+int gsiftp_getversion(int *version)
+{
+  return file_getversion(version);
+}
+
+int gsiftp_checkfile(char *urltype, char *infile, char *outfile)
+{
+  return file_checkfile(urltype, infile, outfile);
+}
+
+int gsiftp_open(char *filename, int rwmode, int *handle)
+{
+  FILE *gsiftpfile;
+  int num_streams;
+
+  if (getenv("GSIFTP_STREAMS")) {
+    num_streams = (int)getenv("GSIFTP_STREAMS");
+  } else {
+    num_streams = 1;
+  }
+  
+  if (rwmode) {
+    gsiftpopen = 2;
+  } else {
+    gsiftpopen = 1;
+  }
+ 
+  if (gsiftpurl)
+    free(gsiftpurl);
+ 
+  gsiftpurl = strdup(filename);
+
+  if (setjmp(env) != 0) {
+    ffpmsg("Timeout (gsiftp_open)");
+    goto error;
+  }
+  
+  signal(SIGALRM, signal_handler);
+  alarm(NETTIMEOUT);
+
+  if (gsiftp_get(filename,&gsiftpfile,num_streams)) {
+    alarm(0);
+    ffpmsg("Unable to open gsiftp file (gsiftp_open)");
+    ffpmsg(filename);
+    goto error;
+  } 
+  
+  fclose(gsiftpfile);
+  
+  signal(SIGALRM, SIG_DFL);
+  alarm(0);
+
+  return file_open(gsiftp_tmpfile, rwmode, handle);
+
+  error:
+   alarm(0);
+   signal(SIGALRM, SIG_DFL);
+   return (FILE_NOT_OPENED);
+}
+
+int gsiftp_create(char *filename, int *handle)
+{
+  if (gsiftpurl)
+    free(gsiftpurl);
+
+  gsiftpurl = strdup(filename);
+
+  return file_create(gsiftp_tmpfile, handle);
+}
+
+int gsiftp_truncate(int handle, LONGLONG filesize)
+{
+  return file_truncate(handle, filesize);
+}
+
+int gsiftp_size(int handle, LONGLONG *filesize)
+{
+  return file_size(handle, filesize);
+}
+
+int gsiftp_flush(int handle)
+{
+  FILE *gsiftpfile;
+  int num_streams;
+
+  if (getenv("GSIFTP_STREAMS")) {
+    num_streams = (int)getenv("GSIFTP_STREAMS");
+  } else {
+    num_streams = 1;
+  }
+  
+  int rc = file_flush(handle);
+
+  if (gsiftpopen != 1) {
+  
+    if (setjmp(env) != 0) {
+      ffpmsg("Timeout (gsiftp_write)");
+      goto error;
+    }
+  
+    signal(SIGALRM, signal_handler);
+    alarm(NETTIMEOUT);
+
+    if (gsiftp_put(gsiftpurl,&gsiftpfile,num_streams)) {
+      alarm(0);
+      ffpmsg("Unable to open gsiftp file (gsiftp_flush)");
+      ffpmsg(gsiftpurl);
+      goto error;
+    } 
+  
+    fclose(gsiftpfile);
+  
+    signal(SIGALRM, SIG_DFL);
+    alarm(0);
+  }
+  
+  return rc;
+
+  error:
+   alarm(0);
+   signal(SIGALRM, SIG_DFL);
+   return (FILE_NOT_OPENED);
+}
+
+int gsiftp_seek(int handle, LONGLONG offset)
+{
+  return file_seek(handle, offset);
+}
+
+int gsiftp_read(int hdl, void *buffer, long nbytes)
+{
+  return file_read(hdl, buffer, nbytes);
+}
+
+int gsiftp_write(int hdl, void *buffer, long nbytes)
+{
+  return file_write(hdl, buffer, nbytes);
+}
+
+int gsiftp_close(int handle)
+{
+    unlink(gsiftp_tmpfile);
+    
+    if (gsiftp_tmpdir)
+        rmdir(gsiftp_tmpdir);
+
+    return file_close(handle);
+}
+
+static void done_cb( void * 				user_arg,
+                     globus_ftp_client_handle_t * 	handle,
+                     globus_object_t * 			err)
+{
+
+    if(err){
+        fprintf(stderr, "%s", globus_object_printable_to_string(err));
+    }
+    
+    globus_mutex_lock(&lock);
+    done = GLOBUS_TRUE;
+    globus_cond_signal(&cond);
+    globus_mutex_unlock(&lock);
+    return;
+}
+
+static void data_cb_read( void * 			user_arg,
+                          globus_ftp_client_handle_t * 	handle,
+                          globus_object_t * 		err,
+                          globus_byte_t * 		buffer,
+                          globus_size_t 		length,
+                          globus_off_t 			offset,
+                          globus_bool_t 		eof)
+{
+    if(err) {
+        fprintf(stderr, "%s", globus_object_printable_to_string(err));
+    }
+    else {
+        FILE* fd = (FILE*) user_arg;
+        int rc = fwrite(buffer, 1, length, fd);
+        if (ferror(fd)) {
+            printf("Read error in function data_cb_read; errno = %d\n", errno);
+            return;
+        }
+
+        if (!eof) {
+            globus_ftp_client_register_read(handle,
+                                            buffer,
+                                            MAX_BUFFER_SIZE_R,
+                                            data_cb_read,
+                                            (void*) fd);
+        }
+    }
+    return;
+}
+
+static void data_cb_write( void * 			user_arg,
+                           globus_ftp_client_handle_t * handle,
+                           globus_object_t * 		err,
+                           globus_byte_t * 		buffer,
+                           globus_size_t 		length,
+                           globus_off_t 		offset,
+                           globus_bool_t 		eof)
+{
+    int curr_offset;
+    if(err) {
+        fprintf(stderr, "%s", globus_object_printable_to_string(err));
+    }
+    else {
+        if (!eof) {
+            FILE* fd = (FILE*) user_arg;
+            int rc;
+            globus_mutex_lock(&lock);
+            curr_offset = global_offset;
+            rc = fread(buffer, 1, MAX_BUFFER_SIZE_W, fd);
+            global_offset += rc;
+            globus_mutex_unlock(&lock);
+            if (ferror(fd)) {
+                printf("Read error in function data_cb_write; errno = %d\n", errno);
+                return;
+            }
+
+            globus_ftp_client_register_write(handle,
+                                             buffer,
+					     rc,
+					     curr_offset,
+                                             feof(fd) != 0,
+                                             data_cb_write,
+                                             (void*) fd);
+        } else {
+            globus_libc_free(buffer);
+        }
+    }
+    return;
+}
+
+int gsiftp_get(char *filename, FILE **gsiftpfile, int num_streams)
+{
+    char gsiurl[MAXLEN];
+
+    globus_ftp_client_handle_t 		handle;
+    globus_ftp_client_operationattr_t 	attr;
+    globus_ftp_client_handleattr_t 	handle_attr;
+    globus_ftp_control_parallelism_t   	parallelism;
+    globus_ftp_control_layout_t		layout;
+    globus_byte_t 			buffer[MAX_BUFFER_SIZE_R];
+    globus_size_t buffer_length = sizeof(buffer);
+    globus_result_t 			result;
+    globus_ftp_client_restart_marker_t	restart;
+    globus_ftp_control_type_t 		filetype;
+   
+    globus_module_activate(GLOBUS_FTP_CLIENT_MODULE);
+    globus_mutex_init(&lock, GLOBUS_NULL);
+    globus_cond_init(&cond, GLOBUS_NULL);
+    globus_ftp_client_handle_init(&handle,  GLOBUS_NULL);
+    globus_ftp_client_handleattr_init(&handle_attr);
+    globus_ftp_client_operationattr_init(&attr);
+    layout.mode = GLOBUS_FTP_CONTROL_STRIPING_NONE;
+    globus_ftp_client_restart_marker_init(&restart);
+    globus_ftp_client_operationattr_set_mode(
+            &attr,
+            GLOBUS_FTP_CONTROL_MODE_EXTENDED_BLOCK);
+   
+    if (num_streams >= 1)
+    {
+        parallelism.mode = GLOBUS_FTP_CONTROL_PARALLELISM_FIXED;
+        parallelism.fixed.size = num_streams;
+       
+        globus_ftp_client_operationattr_set_parallelism(
+            &attr,
+            &parallelism);
+    }
+   
+    globus_ftp_client_operationattr_set_layout(&attr,
+                                               &layout);
+   
+    filetype = GLOBUS_FTP_CONTROL_TYPE_IMAGE;
+    globus_ftp_client_operationattr_set_type (&attr,
+                                              filetype);
+   
+    globus_ftp_client_handle_init(&handle, &handle_attr);
+   
+    done = GLOBUS_FALSE;
+
+    strcpy(gsiurl,"gsiftp://";);
+    strcat(gsiurl,filename);
+
+    *gsiftpfile = fopen(gsiftp_tmpfile,"w+");
+    
+    if (!*gsiftpfile) {
+        ffpmsg("Unable to open temporary file!");
+        return (FILE_NOT_OPENED);
+    }
+   
+    result = globus_ftp_client_get(&handle,
+                                   gsiurl,
+                                   &attr,
+                                   &restart,
+                                   done_cb,
+                                   0);
+    if(result != GLOBUS_SUCCESS) {
+        globus_object_t * err;
+        err = globus_error_get(result);
+        fprintf(stderr, "%s", globus_object_printable_to_string(err));
+        done = GLOBUS_TRUE;
+    }
+    else {
+        globus_ftp_client_register_read(&handle,
+                                        buffer,
+                                        buffer_length,
+                                        data_cb_read,
+                                        (void*) *gsiftpfile);
+    }
+   
+    globus_mutex_lock(&lock);
+
+    while(!done) {
+        globus_cond_wait(&cond, &lock);
+    }
+
+    globus_mutex_unlock(&lock);
+    globus_ftp_client_handle_destroy(&handle);
+    globus_module_deactivate_all();
+   
+    return 0;
+}
+
+int gsiftp_put(char *filename, FILE **gsiftpfile, int num_streams)
+{
+    int i;
+    char gsiurl[MAXLEN];
+
+    globus_ftp_client_handle_t 		handle;
+    globus_ftp_client_operationattr_t 	attr;
+    globus_ftp_client_handleattr_t 	handle_attr;
+    globus_ftp_control_parallelism_t   	parallelism;
+    globus_ftp_control_layout_t		layout;
+    globus_byte_t * 			buffer;
+    globus_size_t buffer_length = sizeof(buffer);
+    globus_result_t 			result;
+    globus_ftp_client_restart_marker_t	restart;
+    globus_ftp_control_type_t 		filetype;
+   
+    globus_module_activate(GLOBUS_FTP_CLIENT_MODULE);
+    globus_mutex_init(&lock, GLOBUS_NULL);
+    globus_cond_init(&cond, GLOBUS_NULL);
+    globus_ftp_client_handle_init(&handle,  GLOBUS_NULL);
+    globus_ftp_client_handleattr_init(&handle_attr);
+    globus_ftp_client_operationattr_init(&attr);
+    layout.mode = GLOBUS_FTP_CONTROL_STRIPING_NONE;
+    globus_ftp_client_restart_marker_init(&restart);
+    globus_ftp_client_operationattr_set_mode(
+            &attr,
+            GLOBUS_FTP_CONTROL_MODE_EXTENDED_BLOCK);
+   
+    if (num_streams >= 1)
+    {
+        parallelism.mode = GLOBUS_FTP_CONTROL_PARALLELISM_FIXED;
+        parallelism.fixed.size = num_streams;
+       
+        globus_ftp_client_operationattr_set_parallelism(
+            &attr,
+            &parallelism);
+    }
+   
+    globus_ftp_client_operationattr_set_layout(&attr,
+                                               &layout);
+   
+    filetype = GLOBUS_FTP_CONTROL_TYPE_IMAGE;
+    globus_ftp_client_operationattr_set_type (&attr,
+                                              filetype);
+   
+    globus_ftp_client_handle_init(&handle, &handle_attr);
+   
+    done = GLOBUS_FALSE;
+    
+    strcpy(gsiurl,"gsiftp://";);
+    strcat(gsiurl,filename);
+
+    *gsiftpfile = fopen(gsiftp_tmpfile,"r");
+
+    if (!*gsiftpfile) {
+        ffpmsg("Unable to open temporary file!");
+        return (FILE_NOT_OPENED);
+    }
+   
+    result = globus_ftp_client_put(&handle,
+                                   gsiurl,
+                                   &attr,
+                                   &restart,
+                                   done_cb,
+                                   0);
+    if(result != GLOBUS_SUCCESS) {
+        globus_object_t * err;
+        err = globus_error_get(result);
+        fprintf(stderr, "%s", globus_object_printable_to_string(err));
+        done = GLOBUS_TRUE;
+    }
+    else {
+        int rc;
+        int curr_offset;
+
+	for (i = 0; i< 2 * num_streams && feof(*gsiftpfile) == 0; i++)
+        {
+            buffer = malloc(MAX_BUFFER_SIZE_W);
+            globus_mutex_lock(&lock);
+            curr_offset = global_offset;
+            rc = fread(buffer, 1, MAX_BUFFER_SIZE_W, *gsiftpfile);
+            global_offset += rc;
+            globus_mutex_unlock(&lock);
+            globus_ftp_client_register_write(
+                &handle,
+                buffer,
+                rc,
+                curr_offset,
+                feof(*gsiftpfile) != 0,
+                data_cb_write,
+                (void*) *gsiftpfile);
+        }
+    }
+   
+    globus_mutex_lock(&lock);
+
+    while(!done) {
+        globus_cond_wait(&cond, &lock);
+    }
+
+    globus_mutex_unlock(&lock);
+    globus_ftp_client_handle_destroy(&handle);
+    globus_module_deactivate_all();
+   
+    return 0;
+}
+
+static void signal_handler(int sig) {
+
+  switch (sig) {
+  case SIGALRM:    /* process for alarm */
+    longjmp(env,sig);
+    
+  default: {
+      /* Hmm, shouldn't have happend */
+      exit(sig);
+    }
+  }
+}
+
+#endif
+#endif
diff --git a/src/plugins/cfitsio/drvrgsiftp.h b/src/plugins/cfitsio/drvrgsiftp.h
new file mode 100644
index 0000000..bd0ec0d
--- /dev/null
+++ b/src/plugins/cfitsio/drvrgsiftp.h
@@ -0,0 +1,21 @@
+#ifndef _GSIFTP_H
+#define _GSIFTP_H
+
+int gsiftp_init(void);
+int gsiftp_setoptions(int options);
+int gsiftp_getoptions(int *options);
+int gsiftp_getversion(int *version);
+int gsiftp_shutdown(void);
+int gsiftp_checkfile(char *urltype, char *infile, char *outfile);
+int gsiftp_open(char *filename, int rwmode, int *driverhandle);
+int gsiftp_create(char *filename, int *driverhandle);
+int gsiftp_truncate(int driverhandle, LONGLONG filesize);
+int gsiftp_size(int driverhandle, LONGLONG *filesize);
+int gsiftp_close(int driverhandle);
+int gsiftp_remove(char *filename);
+int gsiftp_flush(int driverhandle);
+int gsiftp_seek(int driverhandle, LONGLONG offset);
+int gsiftp_read (int driverhandle, void *buffer, long nbytes);
+int gsiftp_write(int driverhandle, void *buffer, long nbytes);
+
+#endif
diff --git a/src/plugins/cfitsio/drvrmem.c b/src/plugins/cfitsio/drvrmem.c
new file mode 100644
index 0000000..4ef23b7
--- /dev/null
+++ b/src/plugins/cfitsio/drvrmem.c
@@ -0,0 +1,1184 @@
+/*  This file, drvrmem.c, contains driver routines for memory files.        */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+
+#include <string.h>
+#include <stdlib.h>
+#include <stddef.h>  /* apparently needed to define size_t */
+#include "fitsio2.h"
+
+/* prototype for .Z file uncompression function in zuncompress.c */
+int zuncompress2mem(char *filename, 
+             FILE *diskfile, 
+             char **buffptr, 
+             size_t *buffsize, 
+             void *(*mem_realloc)(void *p, size_t newsize),
+             size_t *filesize,
+             int *status);
+
+#define RECBUFLEN 1000
+
+static char stdin_outfile[FLEN_FILENAME];
+
+typedef struct    /* structure containing mem file structure */ 
+{
+    char **memaddrptr;   /* Pointer to memory address pointer; */
+                         /* This may or may not point to memaddr. */
+    char *memaddr;       /* Pointer to starting memory address; may */
+                         /* not always be used, so use *memaddrptr instead */
+    size_t *memsizeptr;  /* Pointer to the size of the memory allocation. */
+                         /* This may or may not point to memsize. */
+    size_t memsize;      /* Size of the memory allocation; this may not */
+                         /* always be used, so use *memsizeptr instead. */
+    size_t deltasize;    /* Suggested increment for reallocating memory */
+    void *(*mem_realloc)(void *p, size_t newsize);  /* realloc function */
+    LONGLONG currentpos;   /* current file position, relative to start */
+    LONGLONG fitsfilesize; /* size of the FITS file (always <= *memsizeptr) */
+    FILE *fileptr;      /* pointer to compressed output disk file */
+} memdriver;
+
+static memdriver memTable[NMAXFILES];  /* allocate mem file handle tables */
+
+/*--------------------------------------------------------------------------*/
+int mem_init(void)
+{
+    int ii;
+
+    for (ii = 0; ii < NMAXFILES; ii++) /* initialize all empty slots in table */
+    {
+       memTable[ii].memaddrptr = 0;
+       memTable[ii].memaddr = 0;
+    }
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int mem_setoptions(int options)
+{
+  /* do something with the options argument, to stop compiler warning */
+  options = 0;
+  return(options);
+}
+/*--------------------------------------------------------------------------*/
+int mem_getoptions(int *options)
+{
+  *options = 0;
+  return(0);
+}
+/*--------------------------------------------------------------------------*/
+int mem_getversion(int *version)
+{
+    *version = 10;
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int mem_shutdown(void)
+{
+  return(0);
+}
+/*--------------------------------------------------------------------------*/
+int mem_create(char *filename, int *handle)
+/*
+  Create a new empty memory file for subsequent writes.
+  The file name is ignored in this case.
+*/
+{
+    int status;
+
+    /* initially allocate 1 FITS block = 2880 bytes */
+    status = mem_createmem(2880L, handle);
+
+    if (status)
+    {
+        ffpmsg("failed to create empty memory file (mem_create)");
+        return(status);
+    }
+
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int mem_create_comp(char *filename, int *handle)
+/*
+  Create a new empty memory file for subsequent writes.
+  Also create an empty compressed .gz file.  The memory file
+  will be compressed and written to the disk file when the file is closed.
+*/
+{
+    FILE *diskfile;
+    char mode[4];
+    int  status;
+
+    /* first, create disk file for the compressed output */
+
+
+    if ( !strcmp(filename, "-.gz") || !strcmp(filename, "stdout.gz") ||
+         !strcmp(filename, "STDOUT.gz") )
+    {
+       /* special case: create uncompressed FITS file in memory, then
+          compress it an write it out to 'stdout' when it is closed.  */
+
+       diskfile = stdout;
+    }
+    else
+    {
+        /* normal case: create disk file for the compressed output */
+
+        strcpy(mode, "w+b");    /* create file with read-write */
+
+        diskfile = fopen(filename, "r"); /* does file already exist? */
+
+        if (diskfile)
+        {
+            fclose(diskfile);         /* close file and exit with error */
+            return(FILE_NOT_CREATED); 
+        }
+
+#if MACHINE == ALPHAVMS || MACHINE == VAXVMS
+        /* specify VMS record structure: fixed format, 2880 byte records */
+        /* but force stream mode access to enable random I/O access      */
+        diskfile = fopen(filename, mode, "rfm=fix", "mrs=2880", "ctx=stm"); 
+#else
+        diskfile = fopen(filename, mode); 
+#endif
+
+        if (!(diskfile))           /* couldn't create file */
+        {
+            return(FILE_NOT_CREATED); 
+        }
+    }
+
+    /* now create temporary memory file */
+
+    /* initially allocate 1 FITS block = 2880 bytes */
+    status = mem_createmem(2880L, handle);
+
+    if (status)
+    {
+        ffpmsg("failed to create empty memory file (mem_create_comp)");
+        return(status);
+    }
+
+    memTable[*handle].fileptr = diskfile;
+
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int mem_openmem(void **buffptr,   /* I - address of memory pointer          */
+                size_t *buffsize, /* I - size of buffer, in bytes           */
+                size_t deltasize, /* I - increment for future realloc's     */
+                void *(*memrealloc)(void *p, size_t newsize),  /* function  */
+                int *handle)
+/* 
+  lowest level routine to open a pre-existing memory file.
+*/
+{
+    int ii;
+
+    *handle = -1;
+    for (ii = 0; ii < NMAXFILES; ii++)  /* find empty slot in handle table */
+    {
+        if (memTable[ii].memaddrptr == 0)
+        {
+            *handle = ii;
+            break;
+        }
+    }
+    if (*handle == -1)
+       return(TOO_MANY_FILES);    /* too many files opened */
+
+    memTable[ii].memaddrptr = (char **) buffptr; /* pointer to start addres */
+    memTable[ii].memsizeptr = buffsize;     /* allocated size of memory */
+    memTable[ii].deltasize = deltasize;     /* suggested realloc increment */
+    memTable[ii].fitsfilesize = *buffsize;  /* size of FITS file (upper limit) */
+    memTable[ii].currentpos = 0;            /* at beginning of the file */
+    memTable[ii].mem_realloc = memrealloc;  /* memory realloc function */
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int mem_createmem(size_t msize, int *handle)
+/* 
+  lowest level routine to allocate a memory file.
+*/
+{
+    int ii;
+
+    *handle = -1;
+    for (ii = 0; ii < NMAXFILES; ii++)  /* find empty slot in handle table */
+    {
+        if (memTable[ii].memaddrptr == 0)
+        {
+            *handle = ii;
+            break;
+        }
+    }
+    if (*handle == -1)
+       return(TOO_MANY_FILES);    /* too many files opened */
+
+    /* use the internally allocated memaddr and memsize variables */
+    memTable[ii].memaddrptr = &memTable[ii].memaddr;
+    memTable[ii].memsizeptr = &memTable[ii].memsize;
+
+    /* allocate initial block of memory for the file */
+    if (msize > 0)
+    {
+        memTable[ii].memaddr = (char *) malloc(msize); 
+        if ( !(memTable[ii].memaddr) )
+        {
+            ffpmsg("malloc of initial memory failed (mem_createmem)");
+            return(FILE_NOT_OPENED);
+        }
+    }
+
+    /* set initial state of the file */
+    memTable[ii].memsize = msize;
+    memTable[ii].deltasize = 2880;
+    memTable[ii].fitsfilesize = 0;
+    memTable[ii].currentpos = 0;
+    memTable[ii].mem_realloc = realloc;
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int mem_truncate(int handle, LONGLONG filesize)
+/*
+  truncate the file to a new size
+*/
+{
+    char *ptr;
+
+    /* call the memory reallocation function, if defined */
+    if ( memTable[handle].mem_realloc )
+    {    /* explicit LONGLONG->size_t cast */
+        ptr = (memTable[handle].mem_realloc)(
+                                *(memTable[handle].memaddrptr),
+                                 (size_t) filesize);
+        if (!ptr)
+        {
+            ffpmsg("Failed to reallocate memory (mem_truncate)");
+            return(MEMORY_ALLOCATION);
+        }
+
+        /* if allocated more memory, initialize it to zero */
+        if ( filesize > *(memTable[handle].memsizeptr) )
+        {
+             memset(ptr + *(memTable[handle].memsizeptr),
+                    0,
+                ((size_t) filesize) - *(memTable[handle].memsizeptr) );
+        }
+
+        *(memTable[handle].memaddrptr) = ptr;
+        *(memTable[handle].memsizeptr) = (size_t) (filesize);
+    }
+
+    memTable[handle].currentpos = filesize;
+    memTable[handle].fitsfilesize = filesize;
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int stdin_checkfile(char *urltype, char *infile, char *outfile)
+/*
+   do any special case checking when opening a file on the stdin stream
+*/
+{
+    if (strlen(outfile))
+    {
+        stdin_outfile[0] = '\0';
+        strncat(stdin_outfile,outfile,FLEN_FILENAME-1); /* an output file is specified */
+	strcpy(urltype,"stdinfile://");
+    }
+    else
+        *stdin_outfile = '\0';  /* no output file was specified */
+
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int stdin_open(char *filename, int rwmode, int *handle)
+/*
+  open a FITS file from the stdin file stream by copying it into memory
+  The file name is ignored in this case.
+*/
+{
+    int status;
+    char cbuff;
+
+    if (*stdin_outfile)
+    {
+      /* copy the stdin stream to the specified disk file then open the file */
+
+      /* Create the output file */
+      status =  file_create(stdin_outfile,handle);
+
+      if (status)
+      {
+        ffpmsg("Unable to create output file to copy stdin (stdin_open):");
+        ffpmsg(stdin_outfile);
+        return(status);
+      }
+ 
+      /* copy the whole stdin stream to the file */
+      status = stdin2file(*handle);
+      file_close(*handle);
+
+      if (status)
+      {
+        ffpmsg("failed to copy stdin to file (stdin_open)");
+        ffpmsg(stdin_outfile);
+        return(status);
+      }
+
+      /* reopen file with proper rwmode attribute */
+      status = file_open(stdin_outfile, rwmode, handle);
+    }
+    else
+    {
+   
+      /* get the first character, then put it back */
+      cbuff = fgetc(stdin);
+      ungetc(cbuff, stdin);
+    
+      /* compressed files begin with 037 or 'P' */
+      if (cbuff == 31 || cbuff == 75)
+      {
+         /* looks like the input stream is compressed */
+         status = mem_compress_stdin_open(filename, rwmode, handle);
+	 
+      }
+      else
+      {
+        /* copy the stdin stream into memory then open file in memory */
+
+        if (rwmode != READONLY)
+        {
+          ffpmsg("cannot open stdin with WRITE access");
+          return(READONLY_FILE);
+        }
+
+        status = mem_createmem(2880L, handle);
+
+        if (status)
+        {
+          ffpmsg("failed to create empty memory file (stdin_open)");
+          return(status);
+        }
+ 
+        /* copy the whole stdin stream into memory */
+        status = stdin2mem(*handle);
+
+        if (status)
+        {
+          ffpmsg("failed to copy stdin into memory (stdin_open)");
+          free(memTable[*handle].memaddr);
+        }
+      }
+    }
+
+    return(status);
+}
+/*--------------------------------------------------------------------------*/
+int stdin2mem(int hd)  /* handle number */
+/*
+  Copy the stdin stream into memory.  Fill whatever amount of memory
+  has already been allocated, then realloc more memory if necessary.
+*/
+{
+    size_t nread, memsize, delta;
+    LONGLONG filesize;
+    char *memptr;
+    char simple[] = "SIMPLE";
+    int c, ii, jj;
+
+    memptr = *memTable[hd].memaddrptr;
+    memsize = *memTable[hd].memsizeptr;
+    delta = memTable[hd].deltasize;
+
+    filesize = 0;
+    ii = 0;
+
+    for(jj = 0; (c = fgetc(stdin)) != EOF && jj < 2000; jj++)
+    {
+       /* Skip over any garbage at the beginning of the stdin stream by */
+       /* reading 1 char at a time, looking for 'S', 'I', 'M', 'P', 'L', 'E' */
+       /* Give up if not found in the first 2000 characters */
+
+       if (c == simple[ii])
+       {
+           ii++;
+           if (ii == 6)   /* found the complete string? */
+           {
+              memcpy(memptr, simple, 6);  /* copy "SIMPLE" to buffer */
+              filesize = 6;
+              break;
+           }
+       }
+       else
+          ii = 0;  /* reset search to beginning of the string */
+    }
+
+   if (filesize == 0)
+   {
+       ffpmsg("Couldn't find the string 'SIMPLE' in the stdin stream.");
+       ffpmsg("This does not look like a FITS file.");
+       return(FILE_NOT_OPENED);
+   }
+
+    /* fill up the remainder of the initial memory allocation */
+    nread = fread(memptr + 6, 1, memsize - 6, stdin);
+    nread += 6;  /* add in the 6 characters in 'SIMPLE' */
+
+    if (nread < memsize)    /* reached the end? */
+    {
+       memTable[hd].fitsfilesize = nread;
+       return(0);
+    }
+
+    filesize = nread;
+
+    while (1)
+    {
+        /* allocate memory for another FITS block */
+        memptr = realloc(memptr, memsize + delta);
+
+        if (!memptr)
+        {
+            ffpmsg("realloc failed while copying stdin (stdin2mem)");
+            return(MEMORY_ALLOCATION);
+        }
+        memsize += delta;
+
+        /* read another FITS block */
+        nread = fread(memptr + filesize, 1, delta, stdin);
+
+        filesize += nread;
+
+        if (nread < delta)    /* reached the end? */
+           break;
+    }
+
+     memTable[hd].fitsfilesize = filesize;
+    *memTable[hd].memaddrptr = memptr;
+    *memTable[hd].memsizeptr = memsize;
+
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int stdin2file(int handle)  /* handle number */
+/*
+  Copy the stdin stream to a file.  .
+*/
+{
+    size_t nread;
+    char simple[] = "SIMPLE";
+    int c, ii, jj, status;
+    char recbuf[RECBUFLEN];
+
+    ii = 0;
+    for(jj = 0; (c = fgetc(stdin)) != EOF && jj < 2000; jj++)
+    {
+       /* Skip over any garbage at the beginning of the stdin stream by */
+       /* reading 1 char at a time, looking for 'S', 'I', 'M', 'P', 'L', 'E' */
+       /* Give up if not found in the first 2000 characters */
+
+       if (c == simple[ii])
+       {
+           ii++;
+           if (ii == 6)   /* found the complete string? */
+           {
+              memcpy(recbuf, simple, 6);  /* copy "SIMPLE" to buffer */
+              break;
+           }
+       }
+       else
+          ii = 0;  /* reset search to beginning of the string */
+    }
+
+   if (ii != 6)
+   {
+       ffpmsg("Couldn't find the string 'SIMPLE' in the stdin stream");
+       return(FILE_NOT_OPENED);
+   }
+
+    /* fill up the remainder of the buffer */
+    nread = fread(recbuf + 6, 1, RECBUFLEN - 6, stdin);
+    nread += 6;  /* add in the 6 characters in 'SIMPLE' */
+
+    status = file_write(handle, recbuf, nread);
+    if (status)
+       return(status);
+
+    /* copy the rest of stdin stream */
+    while(0 != (nread = fread(recbuf,1,RECBUFLEN, stdin)))
+    {
+        status = file_write(handle, recbuf, nread);
+        if (status)
+           return(status);
+    }
+
+    return(status);
+}
+/*--------------------------------------------------------------------------*/
+int stdout_close(int handle)
+/*
+  copy the memory file to stdout, then free the memory
+*/
+{
+    int status = 0;
+
+    /* copy from memory to standard out.  explicit LONGLONG->size_t cast */
+    if(fwrite(memTable[handle].memaddr, 1,
+              ((size_t) memTable[handle].fitsfilesize), stdout) !=
+              (size_t) memTable[handle].fitsfilesize )
+    {
+                ffpmsg("failed to copy memory file to stdout (stdout_close)");
+                status = WRITE_ERROR;
+    }
+
+    free( memTable[handle].memaddr );   /* free the memory */
+    memTable[handle].memaddrptr = 0;
+    memTable[handle].memaddr = 0;
+    return(status);
+}
+/*--------------------------------------------------------------------------*/
+int mem_compress_openrw(char *filename, int rwmode, int *hdl)
+/*
+  This routine opens the compressed diskfile and creates an empty memory
+  buffer with an appropriate size, then calls mem_uncompress2mem. It allows
+  the memory 'file' to be opened with READWRITE access.
+*/
+{
+   return(mem_compress_open(filename, READONLY, hdl));  
+}
+/*--------------------------------------------------------------------------*/
+int mem_compress_open(char *filename, int rwmode, int *hdl)
+/*
+  This routine opens the compressed diskfile and creates an empty memory
+  buffer with an appropriate size, then calls mem_uncompress2mem.
+*/
+{
+    FILE *diskfile;
+    int status, estimated = 1;
+    unsigned char buffer[4];
+    size_t finalsize;
+    char *ptr;
+
+    if (rwmode != READONLY)
+    {
+        ffpmsg(
+  "cannot open compressed file with WRITE access (mem_compress_open)");
+        ffpmsg(filename);
+        return(READONLY_FILE);
+    }
+
+    /* open the compressed disk file */
+    status = file_openfile(filename, READONLY, &diskfile);
+    if (status)
+    {
+        ffpmsg("failed to open compressed disk file (compress_open)");
+        ffpmsg(filename);
+        return(status);
+    }
+
+    if (fread(buffer, 1, 2, diskfile) != 2)  /* read 2 bytes */
+    {
+        fclose(diskfile);
+        return(READ_ERROR);
+    }
+
+    if (memcmp(buffer, "\037\213", 2) == 0)  /* GZIP */
+    {
+        /* the uncompressed file size is give at the end of the file */
+
+        fseek(diskfile, 0, 2);            /* move to end of file */
+        fseek(diskfile, -4L, 1);          /* move back 4 bytes */
+        fread(buffer, 1, 4L, diskfile);   /* read 4 bytes */
+
+        /* have to worry about integer byte order */
+	finalsize  = buffer[0];
+	finalsize |= buffer[1] << 8;
+	finalsize |= buffer[2] << 16;
+	finalsize |= buffer[3] << 24;
+
+        estimated = 0;  /* file size is known, not estimated */
+    }
+    else if (memcmp(buffer, "\120\113", 2) == 0)   /* PKZIP */
+    {
+        /* the uncompressed file size is give at byte 22 the file */
+
+        fseek(diskfile, 22L, 0);            /* move to byte 22 */
+        fread(buffer, 1, 4L, diskfile);   /* read 4 bytes */
+
+        /* have to worry about integer byte order */
+	finalsize  = buffer[0];
+	finalsize |= buffer[1] << 8;
+	finalsize |= buffer[2] << 16;
+	finalsize |= buffer[3] << 24;
+
+        estimated = 0;  /* file size is known, not estimated */
+    }
+    else if (memcmp(buffer, "\037\036", 2) == 0)  /* PACK */
+        finalsize = 0;  /* for most methods we can't determine final size */
+    else if (memcmp(buffer, "\037\235", 2) == 0)  /* LZW */
+        finalsize = 0;  /* for most methods we can't determine final size */
+    else if (memcmp(buffer, "\037\240", 2) == 0)  /* LZH */
+        finalsize = 0;  /* for most methods we can't determine final size */
+    else
+    {
+        /* not a compressed file; this should never happen */
+        fclose(diskfile);
+        return(1);
+    }
+
+    if (finalsize == 0)  /* estimate uncompressed file size */
+    {
+            fseek(diskfile, 0, 2);   /* move to end of the compressed file */
+            finalsize = ftell(diskfile);  /* position = size of file */
+            finalsize = finalsize * 3;   /* assume factor of 3 compression */
+    }
+
+    fseek(diskfile, 0, 0);   /* move back to beginning of file */
+
+    /* create a memory file big enough (hopefully) for the uncompressed file */
+    status = mem_createmem(finalsize, hdl);
+
+    if (status && estimated)
+    {
+        /* memory allocation failed, so try a smaller estimated size */
+        finalsize = finalsize / 3;
+        status = mem_createmem(finalsize, hdl);
+    }
+
+    if (status)
+    {
+        fclose(diskfile);
+        ffpmsg("failed to create empty memory file (compress_open)");
+        return(status);
+    }
+
+    /* uncompress file into memory */
+    status = mem_uncompress2mem(filename, diskfile, *hdl);
+
+    fclose(diskfile);
+
+    if (status)
+    {
+        mem_close_free(*hdl);   /* free up the memory */
+        ffpmsg("failed to uncompress file into memory (compress_open)");
+        return(status);
+    }
+
+    /* if we allocated too much memory initially, then free it */
+    if (*(memTable[*hdl].memsizeptr) > 
+       (( (size_t) memTable[*hdl].fitsfilesize) + 256L) ) 
+    {
+        ptr = realloc(*(memTable[*hdl].memaddrptr), 
+                     ((size_t) memTable[*hdl].fitsfilesize) );
+        if (!ptr)
+        {
+            ffpmsg("Failed to reduce size of allocated memory (compress_open)");
+            return(MEMORY_ALLOCATION);
+        }
+
+        *(memTable[*hdl].memaddrptr) = ptr;
+        *(memTable[*hdl].memsizeptr) = (size_t) (memTable[*hdl].fitsfilesize);
+    }
+
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int mem_compress_stdin_open(char *filename, int rwmode, int *hdl)
+/*
+  This routine reads the compressed input stream and creates an empty memory
+  buffer, then calls mem_uncompress2mem.
+*/
+{
+    int status;
+    char *ptr;
+
+    if (rwmode != READONLY)
+    {
+        ffpmsg(
+  "cannot open compressed input stream with WRITE access (mem_compress_stdin_open)");
+        return(READONLY_FILE);
+    }
+ 
+    /* create a memory file for the uncompressed file */
+    status = mem_createmem(28800, hdl);
+
+    if (status)
+    {
+        ffpmsg("failed to create empty memory file (compress_stdin_open)");
+        return(status);
+    }
+
+    /* uncompress file into memory */
+    status = mem_uncompress2mem(filename, stdin, *hdl);
+
+    if (status)
+    {
+        mem_close_free(*hdl);   /* free up the memory */
+        ffpmsg("failed to uncompress stdin into memory (compress_stdin_open)");
+        return(status);
+    }
+
+    /* if we allocated too much memory initially, then free it */
+    if (*(memTable[*hdl].memsizeptr) > 
+       (( (size_t) memTable[*hdl].fitsfilesize) + 256L) ) 
+    {
+        ptr = realloc(*(memTable[*hdl].memaddrptr), 
+                      ((size_t) memTable[*hdl].fitsfilesize) );
+        if (!ptr)
+        {
+            ffpmsg("Failed to reduce size of allocated memory (compress_stdin_open)");
+            return(MEMORY_ALLOCATION);
+        }
+
+        *(memTable[*hdl].memaddrptr) = ptr;
+        *(memTable[*hdl].memsizeptr) = (size_t) (memTable[*hdl].fitsfilesize);
+    }
+
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int mem_iraf_open(char *filename, int rwmode, int *hdl)
+/*
+  This routine creates an empty memory buffer, then calls iraf2mem to
+  open the IRAF disk file and convert it to a FITS file in memeory.
+*/
+{
+    int status;
+    size_t filesize = 0;
+
+    /* create a memory file with size = 0 for the FITS converted IRAF file */
+    status = mem_createmem(filesize, hdl);
+    if (status)
+    {
+        ffpmsg("failed to create empty memory file (mem_iraf_open)");
+        return(status);
+    }
+
+    /* convert the iraf file into a FITS file in memory */
+    status = iraf2mem(filename, memTable[*hdl].memaddrptr,
+                      memTable[*hdl].memsizeptr, &filesize, &status);
+
+    if (status)
+    {
+        mem_close_free(*hdl);   /* free up the memory */
+        ffpmsg("failed to convert IRAF file into memory (mem_iraf_open)");
+        return(status);
+    }
+
+    memTable[*hdl].currentpos = 0;           /* save starting position */
+    memTable[*hdl].fitsfilesize=filesize;   /* and initial file size  */
+
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int mem_rawfile_open(char *filename, int rwmode, int *hdl)
+/*
+  This routine creates an empty memory buffer, writes a minimal
+  image header, then copies the image data from the raw file into
+  memory.  It will byteswap the pixel values if the raw array
+  is in little endian byte order.
+*/
+{
+    FILE *diskfile;
+    fitsfile *fptr;
+    short *sptr;
+    int status, endian, datatype, bytePerPix, naxis;
+    long dim[5] = {1,1,1,1,1}, ii, nvals, offset = 0;
+    size_t filesize = 0, datasize;
+    char rootfile[FLEN_FILENAME], *cptr = 0, *cptr2 = 0;
+    void *ptr;
+
+    if (rwmode != READONLY)
+    {
+        ffpmsg(
+  "cannot open raw binary file with WRITE access (mem_rawfile_open)");
+        ffpmsg(filename);
+        return(READONLY_FILE);
+    }
+
+    cptr = strchr(filename, '[');   /* search for opening bracket [ */
+
+    if (!cptr)
+    {
+        ffpmsg("binary file name missing '[' character (mem_rawfile_open)");
+        ffpmsg(filename);
+        return(URL_PARSE_ERROR);
+    }
+
+    *rootfile = '\0';
+    strncat(rootfile, filename, cptr - filename);  /* store the rootname */
+
+    cptr++;
+
+    while (*cptr == ' ')
+       cptr++;    /* skip leading blanks */
+
+    /* Get the Data Type of the Image */
+
+    if (*cptr == 'b' || *cptr == 'B')
+    {
+      datatype = BYTE_IMG;
+      bytePerPix = 1;
+    }
+    else if (*cptr == 'i' || *cptr == 'I')
+    {
+      datatype = SHORT_IMG;
+      bytePerPix = 2;
+    }
+    else if (*cptr == 'u' || *cptr == 'U')
+    {
+      datatype = USHORT_IMG;
+      bytePerPix = 2;
+
+    }
+    else if (*cptr == 'j' || *cptr == 'J')
+    {
+      datatype = LONG_IMG;
+      bytePerPix = 4;
+    }  
+    else if (*cptr == 'r' || *cptr == 'R' || *cptr == 'f' || *cptr == 'F')
+    {
+      datatype = FLOAT_IMG;
+      bytePerPix = 4;
+    }    
+    else if (*cptr == 'd' || *cptr == 'D')
+    {
+      datatype = DOUBLE_IMG;
+      bytePerPix = 8;
+    }
+    else
+    {
+        ffpmsg("error in raw binary file datatype (mem_rawfile_open)");
+        ffpmsg(filename);
+        return(URL_PARSE_ERROR);
+    }
+
+    cptr++;
+
+    /* get Endian: Big or Little; default is same as the local machine */
+    
+    if (*cptr == 'b' || *cptr == 'B')
+    {
+        endian = 0;
+        cptr++;
+    }
+    else if (*cptr == 'l' || *cptr == 'L')
+    {
+        endian = 1;
+        cptr++;
+    }
+    else
+        endian = BYTESWAPPED; /* byteswapped machines are little endian */
+
+    /* read each dimension (up to 5) */
+
+    naxis = 1;
+    dim[0] = strtol(cptr, &cptr2, 10);
+    
+    if (cptr2 && *cptr2 == ',')
+    {
+      naxis = 2;
+      dim[1] = strtol(cptr2+1, &cptr, 10);
+
+      if (cptr && *cptr == ',')
+      {
+        naxis = 3;
+        dim[2] = strtol(cptr+1, &cptr2, 10);
+
+        if (cptr2 && *cptr2 == ',')
+        {
+          naxis = 4;
+          dim[3] = strtol(cptr2+1, &cptr, 10);
+
+          if (cptr && *cptr == ',')
+            naxis = 5;
+            dim[4] = strtol(cptr+1, &cptr2, 10);
+        }
+      }
+    }
+
+    cptr = maxvalue(cptr, cptr2);
+
+    if (*cptr == ':')   /* read starting offset value */
+        offset = strtol(cptr+1, 0, 10);
+
+    nvals = dim[0] * dim[1] * dim[2] * dim[3] * dim[4];
+    datasize = nvals * bytePerPix;
+    filesize = nvals * bytePerPix + 2880;
+    filesize = ((filesize - 1) / 2880 + 1) * 2880; 
+
+    /* open the raw binary disk file */
+    status = file_openfile(rootfile, READONLY, &diskfile);
+    if (status)
+    {
+        ffpmsg("failed to open raw  binary file (mem_rawfile_open)");
+        ffpmsg(rootfile);
+        return(status);
+    }
+
+    /* create a memory file with corrct size for the FITS converted raw file */
+    status = mem_createmem(filesize, hdl);
+    if (status)
+    {
+        ffpmsg("failed to create memory file (mem_rawfile_open)");
+        fclose(diskfile);
+        return(status);
+    }
+
+    /* open this piece of memory as a new FITS file */
+    ffimem(&fptr, (void **) memTable[*hdl].memaddrptr, &filesize, 0, 0, &status);
+
+    /* write the required header keywords */
+    ffcrim(fptr, datatype, naxis, dim, &status);
+
+    /* close the FITS file, but keep the memory allocated */
+    ffclos(fptr, &status);
+
+    if (status > 0)
+    {
+        ffpmsg("failed to write basic image header (mem_rawfile_open)");
+        fclose(diskfile);
+        mem_close_free(*hdl);   /* free up the memory */
+        return(status);
+    }
+
+    if (offset > 0)
+       fseek(diskfile, offset, 0);   /* offset to start of the data */
+
+    /* read the raw data into memory */
+    ptr = *memTable[*hdl].memaddrptr + 2880;
+
+    if (fread((char *) ptr, 1, datasize, diskfile) != datasize)
+      status = READ_ERROR;
+
+    fclose(diskfile);  /* close the raw binary disk file */
+
+    if (status)
+    {
+        mem_close_free(*hdl);   /* free up the memory */
+        ffpmsg("failed to copy raw file data into memory (mem_rawfile_open)");
+        return(status);
+    }
+
+    if (datatype == USHORT_IMG)  /* have to subtract 32768 from each unsigned */
+    {                            /* value to conform to FITS convention. More */
+                                 /* efficient way to do this is to just flip  */
+                                 /* the most significant bit.                 */
+
+      sptr = (short *) ptr;
+
+      if (endian == BYTESWAPPED)  /* working with native format */
+      {
+        for (ii = 0; ii < nvals; ii++, sptr++)
+        {
+          *sptr =  ( *sptr ) ^ 0x8000;
+        }
+      }
+      else  /* pixels are byteswapped WRT the native format */
+      {
+        for (ii = 0; ii < nvals; ii++, sptr++)
+        {
+          *sptr =  ( *sptr ) ^ 0x80;
+        }
+      }
+    }
+
+    if (endian)  /* swap the bytes if array is in little endian byte order */
+    {
+      if (datatype == SHORT_IMG || datatype == USHORT_IMG)
+      {
+        ffswap2( (short *) ptr, nvals);
+      }
+      else if (datatype == LONG_IMG || datatype == FLOAT_IMG)
+      {
+        ffswap4( (INT32BIT *) ptr, nvals);
+      }
+
+      else if (datatype == DOUBLE_IMG)
+      {
+        ffswap8( (double *) ptr, nvals);
+      }
+    }
+
+    memTable[*hdl].currentpos = 0;           /* save starting position */
+    memTable[*hdl].fitsfilesize=filesize;    /* and initial file size  */
+
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int mem_uncompress2mem(char *filename, FILE *diskfile, int hdl)
+{
+/*
+  lower level routine to uncompress a file into memory.  The file
+  has already been opened and the memory buffer has been allocated.
+*/
+
+  size_t finalsize;
+  int status;
+  /* uncompress file into memory */
+  status = 0;
+
+    if (strstr(filename, ".Z")) {
+         zuncompress2mem(filename, diskfile,
+		 memTable[hdl].memaddrptr,   /* pointer to memory address */
+		 memTable[hdl].memsizeptr,   /* pointer to size of memory */
+		 realloc,                     /* reallocation function */
+		 &finalsize, &status);        /* returned file size nd status*/
+    } else {
+         uncompress2mem(filename, diskfile,
+		 memTable[hdl].memaddrptr,   /* pointer to memory address */
+		 memTable[hdl].memsizeptr,   /* pointer to size of memory */
+		 realloc,                     /* reallocation function */
+		 &finalsize, &status);        /* returned file size nd status*/
+    } 
+
+  memTable[hdl].currentpos = 0;           /* save starting position */
+  memTable[hdl].fitsfilesize=finalsize;   /* and initial file size  */
+  return status;
+}
+/*--------------------------------------------------------------------------*/
+int mem_size(int handle, LONGLONG *filesize)
+/*
+  return the size of the file; only called when the file is first opened
+*/
+{
+    *filesize = memTable[handle].fitsfilesize;
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int mem_close_free(int handle)
+/*
+  close the file and free the memory.
+*/
+{
+    free( *(memTable[handle].memaddrptr) );
+
+    memTable[handle].memaddrptr = 0;
+    memTable[handle].memaddr = 0;
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int mem_close_keep(int handle)
+/*
+  close the memory file but do not free the memory.
+*/
+{
+    memTable[handle].memaddrptr = 0;
+    memTable[handle].memaddr = 0;
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int mem_close_comp(int handle)
+/*
+  compress the memory file, writing it out to the fileptr (which might
+  be stdout)
+*/
+{
+    int status = 0;
+    size_t compsize;
+
+    /* compress file in  memory to a .gz disk file */
+
+    if(compress2file_from_mem(memTable[handle].memaddr,
+              (size_t) (memTable[handle].fitsfilesize), 
+              memTable[handle].fileptr,
+              &compsize, &status ) )
+    {
+            ffpmsg("failed to copy memory file to file (mem_close_comp)");
+            status = WRITE_ERROR;
+    }
+
+    free( memTable[handle].memaddr );   /* free the memory */
+    memTable[handle].memaddrptr = 0;
+    memTable[handle].memaddr = 0;
+
+    /* close the compressed disk file (except if it is 'stdout' */
+    if (memTable[handle].fileptr != stdout)
+        fclose(memTable[handle].fileptr);
+
+    return(status);
+}
+/*--------------------------------------------------------------------------*/
+int mem_seek(int handle, LONGLONG offset)
+/*
+  seek to position relative to start of the file.
+*/
+{
+    if (offset >  memTable[handle].fitsfilesize )
+        return(END_OF_FILE);
+
+    memTable[handle].currentpos = offset;
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int mem_read(int hdl, void *buffer, long nbytes)
+/*
+  read bytes from the current position in the file
+*/
+{
+    if (memTable[hdl].currentpos + nbytes > memTable[hdl].fitsfilesize)
+        return(END_OF_FILE);
+
+    memcpy(buffer,
+           *(memTable[hdl].memaddrptr) + memTable[hdl].currentpos,
+           nbytes);
+
+    memTable[hdl].currentpos += nbytes;
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int mem_write(int hdl, void *buffer, long nbytes)
+/*
+  write bytes at the current position in the file
+*/
+{
+    size_t newsize;
+    char *ptr;
+
+    if ((size_t) (memTable[hdl].currentpos + nbytes) > 
+         *(memTable[hdl].memsizeptr) )
+    {
+               
+        if (!(memTable[hdl].mem_realloc))
+        {
+            ffpmsg("realloc function not defined (mem_write)");
+            return(WRITE_ERROR);
+        }
+
+        /*
+          Attempt to reallocate additional memory:
+          the memory buffer size is incremented by the larger of:
+             1 FITS block (2880 bytes) or
+             the defined 'deltasize' parameter
+         */
+
+        newsize = maxvalue( (size_t)
+            (((memTable[hdl].currentpos + nbytes - 1) / 2880) + 1) * 2880,
+            *(memTable[hdl].memsizeptr) + memTable[hdl].deltasize);
+
+        /* call the realloc function */
+        ptr = (memTable[hdl].mem_realloc)(
+                                    *(memTable[hdl].memaddrptr),
+                                     newsize);
+        if (!ptr)
+        {
+            ffpmsg("Failed to reallocate memory (mem_write)");
+            return(MEMORY_ALLOCATION);
+        }
+
+        *(memTable[hdl].memaddrptr) = ptr;
+        *(memTable[hdl].memsizeptr) = newsize;
+    }
+
+    /* now copy the bytes from the buffer into memory */
+    memcpy( *(memTable[hdl].memaddrptr) + memTable[hdl].currentpos,
+             buffer,
+             nbytes);
+
+    memTable[hdl].currentpos += nbytes;
+    memTable[hdl].fitsfilesize =
+               maxvalue(memTable[hdl].fitsfilesize,
+                        memTable[hdl].currentpos);
+    return(0);
+}
diff --git a/src/plugins/cfitsio/drvrnet.c b/src/plugins/cfitsio/drvrnet.c
new file mode 100644
index 0000000..f0e1a42
--- /dev/null
+++ b/src/plugins/cfitsio/drvrnet.c
@@ -0,0 +1,2741 @@
+/*  This file, drvrhttp.c contains driver routines for http, ftp and root 
+    files. */
+
+/* This file was written by Bruce O'Neel at the ISDC, Switzerland          */
+/*  The FITSIO software is maintained by William Pence at the High Energy  */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+
+
+/* Notes on the drivers:
+
+   The ftp driver uses passive mode exclusivly.  If your remote system can't 
+   deal with passive mode then it'll fail.  Since Netscape Navigator uses 
+   passive mode as well there shouldn't be too many ftp servers which have
+   problems.
+
+
+   The http driver works properly with 301 and 302 redirects.  For many more 
+   gory details see http://www.w3c.org/Protocols/rfc2068/rfc2068.  The only
+   catch to the 301/302 redirects is that they have to redirect to another 
+   http:// url.  If not, things would have to change a lot in cfitsio and this
+   was thought to be too difficult.
+   
+   Redirects look like
+
+
+   <HTML><HEAD>
+   <TITLE>301 Moved Permanently</TITLE>
+   </HEAD><BODY>
+   <H1>Moved Permanently</H1>
+   The document has moved <A HREF="http://heasarc.gsfc.nasa.gov/FTP/software/ftools/release/other/image.fits.gz";>here</A>.<P>
+   </BODY></HTML>
+
+   This redirect was from apache 1.2.5 but most of the other servers produce 
+   something very similiar.  The parser for the redirects finds the first 
+   anchor <A> tag in the body and goes there.  If that wasn't what was intended
+   by the remote system then hopefully the error stack, which includes notes 
+   about the redirect will help the user fix the problem.
+
+
+
+   Root protocal doesn't have any real docs, so, the emperical docs are as 
+   follows.  
+
+   First, you must use a slightly modified rootd server.  The modifications 
+   include implimentation of the stat command which returns the size of the 
+   remote file.  Without that it's impossible for cfitsio to work properly
+   since fitsfiles don't include any information about the size of the files 
+   in the headers.  The rootd server closes the connections on any errors, 
+   including reading beyond the end of the file or seeking beyond the end 
+   of the file.  The rootd:// driver doesn't reopen a closed connection, if
+   the connection is closed you're pretty much done.
+
+   The messages are of the form
+
+   <len><opcode><optional information>
+
+   All binary information is transfered in network format, so use htonl and 
+   ntohl to convert back and forth.
+
+   <len> :== 4 byte length, in network format, the len doesn't include the
+         length of <len>
+   <opcode> :== one of the message opcodes below, 4 bytes, network format
+   <optional info> :== depends on opcode
+
+   The response is of the same form with the same opcode sent.  Success is
+   indicated by <optional info> being 0.
+
+   Root is a NFSish protocol where each read/write includes the byte
+   offset to read or write to.  As a result, seeks will always succeed
+   in the driver even if they would cause a fatal error when you try
+   to read because you're beyond the end of the file.
+
+   There is file locking on the host such that you need to possibly
+   create /usr/tmp/rootdtab on the host system.  There is one file per
+   socket connection, though the rootd daemon can support multiple
+   files open at once.
+
+   The messages are sent in the following order:
+
+   ROOTD_USER - user name, <optional info> is the user name, trailing
+   null is sent though it's not required it seems.  A ROOTD_AUTH
+   message is returned with any sort of error meaning that the user
+   name is wrong.
+
+   ROOTD_PASS - password, ones complemented, stored in <optional info>. Once
+   again the trailing null is sent.  Once again a ROOTD_AUTH message is 
+   returned
+
+   ROOTD_OPEN - <optional info> includes filename and one of
+     {create|update|read} as the file mode.  ~ seems to be dealt with
+     as the username's login directory.  A ROOTD_OPEN message is
+     returned.
+
+   Once the file is opened any of the following can be sent:
+
+   ROOTD_STAT - file status and size
+   returns a message where <optional info> is the file length in bytes
+
+   ROOTD_FLUSH - flushes the file, not sure this has any real effect
+   on the daemon since the daemon uses open/read/write/close rather
+   than the buffered fopen/fread/fwrite/fclose.
+
+   ROOTD_GET - on send <optional info> includes a text message of
+   offset and length to get.  Return is a status message first with a
+   status value, then, the raw bytes for the length that you
+   requested.  It's an error to seek or read past the end of the file,
+   and, the rootd daemon exits and won't respond anymore.  Ie, don't
+   do this.
+
+   ROOTD_PUT - on send <optional info> includes a text message of
+   offset and length to put.  Then send the raw bytes you want to
+   write.  Then recieve a status message
+
+
+   When you are finished then you send the message:
+
+   ROOTD_CLOSE - closes the file
+
+   Once the file is closed then the socket is closed.
+
+
+Revision 1.56  2000/01/04 11:58:31  oneel
+Updates so that compressed network files are dealt with regardless of
+their file names and/or mime types.
+
+Revision 1.55  2000/01/04 10:52:40  oneel
+cfitsio 2.034
+
+Revision 1.51  1999/08/10 12:13:40  oneel
+Make the http code a bit less picky about the types of files it
+uncompresses.  Now it also uncompresses files which end in .Z or .gz.
+
+Revision 1.50  1999/08/04 12:38:46  oneel
+Don's 2.0.32 patch with dal 1.3
+
+Revision 1.39  1998/12/02 15:31:33  oneel
+Updates to drvrnet.c so that less compiler warnings would be
+generated.  Fixes the signal handling.
+
+Revision 1.38  1998/11/23 10:03:24  oneel
+Added in a useragent string, as suggested by:
+Tim Kimball · Data Systems Division ¦ kimball stsci edu · 410-338-4417
+Space Telescope Science Institute   ¦ http://www.stsci.edu/~kimball/
+3700 San Martin Drive               ¦ http://archive.stsci.edu/
+Baltimore MD 21218 USA              ¦ http://faxafloi.stsci.edu:4547/
+
+   
+ */
+
+#ifdef HAVE_NET_SERVICES
+#include <string.h>
+
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#if defined(unix) || defined(__unix__)  || defined(__unix)
+#include <unistd.h>  
+#endif
+
+#include <signal.h>
+#include <setjmp.h>
+#include "fitsio2.h"
+
+static jmp_buf env; /* holds the jump buffer for setjmp/longjmp pairs */
+static void signal_handler(int sig);
+
+/* Network routine error codes */
+#define NET_OK 0
+#define NOT_INET_ADDRESS -1000
+#define UNKNOWN_INET_HOST -1001
+#define CONNECTION_ERROR -1002
+
+/* Network routine constants */
+#define NET_DEFAULT 0
+#define NET_OOB 1
+#define NET_PEEK 2
+
+#define NETTIMEOUT 180 /* in secs */
+
+/* local defines and variables */
+#define MAXLEN 1200
+#define SHORTLEN 100
+static char netoutfile[MAXLEN];
+
+
+#define ROOTD_USER  2000       /*user id follows */
+#define ROOTD_PASS  2001       /*passwd follows */
+#define ROOTD_AUTH  2002       /*authorization status (to client) */
+#define ROOTD_FSTAT 2003       /*filename follows */
+#define ROOTD_OPEN  2004       /*filename follows + mode */
+#define ROOTD_PUT   2005       /*offset, number of bytes and buffer */
+#define ROOTD_GET   2006       /*offset, number of bytes */
+#define ROOTD_FLUSH 2007       /*flush file */
+#define ROOTD_CLOSE 2008       /*close file */
+#define ROOTD_STAT  2009       /*return rootd statistics */
+#define ROOTD_ACK   2010       /*acknowledgement (all OK) */
+#define ROOTD_ERR   2011       /*error code and message follow */
+
+typedef struct    /* structure containing disk file structure */ 
+{
+  int sock;
+  LONGLONG currentpos;
+} rootdriver;
+
+static rootdriver handleTable[NMAXFILES];  /* allocate diskfile handle tables */
+
+/* static prototypes */
+
+static int NET_TcpConnect(char *hostname, int port);
+static int NET_SendRaw(int sock, const void *buf, int length, int opt);
+static int NET_RecvRaw(int sock, void *buffer, int length);
+static int NET_ParseUrl(const char *url, char *proto, char *host, int *port, 
+		 char *fn);
+static int CreateSocketAddress(struct sockaddr_in *sockaddrPtr,
+			       char *host,int port);
+static int ftp_status(FILE *ftp, char *statusstr);
+static int http_open_network(char *url, FILE **httpfile, char *contentencoding,
+			  int *contentlength);
+static int ftp_open_network(char *url, FILE **ftpfile, FILE **command, 
+			    int *sock);
+
+static int root_send_buffer(int sock, int op, char *buffer, int buflen);
+static int root_recv_buffer(int sock, int *op, char *buffer,int buflen);
+static int root_openfile(char *filename, char *rwmode, int *sock);
+static int encode64(unsigned s_len, char *src, unsigned d_len, char *dst);
+
+/***************************/
+/* Static variables */
+
+static int closehttpfile;
+static int closememfile;
+static int closefdiskfile;
+static int closediskfile;
+static int closefile;
+static int closeoutfile;
+static int closecommandfile;
+static int closeftpfile;
+static FILE *diskfile;
+static FILE *outfile;
+
+/*--------------------------------------------------------------------------*/
+/* This creates a memory file handle with a copy of the URL in filename. The 
+   file is uncompressed if necessary */
+
+int http_open(char *filename, int rwmode, int *handle)
+{
+
+  FILE *httpfile;
+  char contentencoding[SHORTLEN];
+  char newfilename[MAXLEN];
+  char errorstr[MAXLEN];
+  char recbuf[MAXLEN];
+  long len;
+  int contentlength;
+  int status;
+  char firstchar;
+
+  closehttpfile = 0;
+  closememfile = 0;
+
+  /* don't do r/w files */
+  if (rwmode != 0) {
+    ffpmsg("Can't open http:// type file with READWRITE access");
+    ffpmsg("  Specify an outfile for r/w access (http_open)");
+    goto error;
+  }
+
+  /* do the signal handler bits */
+  if (setjmp(env) != 0) {
+    /* feels like the second time */
+    /* this means something bad happened */
+    ffpmsg("Timeout (http_open)");
+    goto error;
+  }
+
+  (void) signal(SIGALRM, signal_handler);
+  
+  /* Open the network connection */
+
+  /* Does the file have a .Z or .gz in it */
+  /* Also, if file has a '?' in it (probably cgi script) */
+  if (strstr(filename,".Z") || strstr(filename,".gz") || 
+      strstr(filename,"?")) {
+    alarm(NETTIMEOUT);
+    if (http_open_network(filename,&httpfile,contentencoding,
+			       &contentlength)) {
+      alarm(0);
+      ffpmsg("Unable to open http file (http_open):");
+      ffpmsg(filename);
+      goto error;
+    } 
+  } else {
+  
+    if (strlen(filename) >= MAXLEN - 4) {
+	  ffpmsg("http file name is too long (http_open)");
+          ffpmsg(filename);
+	  goto error;
+    }
+  
+    alarm(NETTIMEOUT);
+    /* Try the .gz one */    
+    strcpy(newfilename,filename);
+    strcat(newfilename,".gz");
+    
+    if (http_open_network(newfilename,&httpfile,contentencoding,
+			  &contentlength)) {
+      alarm(0);
+      /* Now the .Z one */
+      strcpy(newfilename,filename);
+      strcat(newfilename,".Z");
+      alarm(NETTIMEOUT);
+      if (http_open_network(newfilename,&httpfile,contentencoding,
+			    &contentlength)) {
+	alarm(0);
+	alarm(NETTIMEOUT);
+	if (http_open_network(filename,&httpfile,contentencoding,
+			      &contentlength)) { 
+	  alarm(0);
+	  ffpmsg("Unable to open http file (http_open)");
+          ffpmsg(filename);
+	  goto error;
+	}
+      }
+    }
+  }
+
+  closehttpfile++;
+
+  /* Create the memory file */
+  if ((status =  mem_create(filename,handle))) {
+    ffpmsg("Unable to create memory file (http_open)");
+    goto error;
+  }
+
+  closememfile++;
+
+  /* Now, what do we do with the file */
+  /* Check to see what the first character is */
+  firstchar = fgetc(httpfile);
+  ungetc(firstchar,httpfile);
+  if (!strcmp(contentencoding,"x-gzip") || 
+      !strcmp(contentencoding,"x-compress") ||
+      strstr(filename,".gz") || 
+      strstr(filename,".Z") ||
+      ('\037' == firstchar)) {
+    /* do the compress dance, which is the same as the gzip dance */
+    /* Using the cfitsio routine */
+
+    status = 0;
+    /* Ok, this is a tough case, let's be arbritary and say 10*NETTIMEOUT,
+       Given the choices for nettimeout above they'll probaby ^C before, but
+       it's always worth a shot*/
+    
+    alarm(NETTIMEOUT*10);
+    status = mem_uncompress2mem(filename, httpfile, *handle);
+    alarm(0);
+    if (status) {
+      ffpmsg("Error writing compressed memory file (http_open)");
+      ffpmsg(filename);
+      goto error;
+    }
+    
+  } else {
+    /* It's not compressed, bad choice, but we'll copy it anyway */
+    if (contentlength % 2880) {
+      sprintf(errorstr,"Content-Length not a multiple of 2880 (http_open) %d",
+	      contentlength);
+      ffpmsg(errorstr);
+    }
+
+    /* write a memory file */
+    alarm(NETTIMEOUT);
+    while(0 != (len = fread(recbuf,1,MAXLEN,httpfile))) {
+      alarm(0); /* cancel alarm */
+      status = mem_write(*handle,recbuf,len);
+      if (status) {
+        ffpmsg("Error copying http file into memory (http_open)");
+        ffpmsg(filename);
+	goto error;
+      }
+      alarm(NETTIMEOUT); /* rearm the alarm */
+    }
+  }
+  
+  fclose(httpfile);
+
+  signal(SIGALRM, SIG_DFL);
+  alarm(0);
+  return mem_seek(*handle,0);
+
+ error:
+  alarm(0); /* clear it */
+  if (closehttpfile) {
+    fclose(httpfile);
+  }
+  if (closememfile) {
+    mem_close_free(*handle);
+  }
+  
+  signal(SIGALRM, SIG_DFL);
+  return (FILE_NOT_OPENED);
+}
+/*--------------------------------------------------------------------------*/
+/* This creates a memory file handle with a copy of the URL in filename.  The
+   file must be compressed and is copied (still compressed) to disk first. 
+   The compressed disk file is then uncompressed into memory (READONLY).
+*/
+
+int http_compress_open(char *url, int rwmode, int *handle)
+{
+  FILE *httpfile;
+  char contentencoding[SHORTLEN];
+  char recbuf[MAXLEN];
+  long len;
+  int contentlength;
+  int ii, flen, status;
+  char firstchar;
+
+  closehttpfile = 0;
+  closediskfile = 0;
+  closefdiskfile = 0;
+  closememfile = 0;
+
+  /* cfileio made a mistake, should set the netoufile first otherwise 
+     we don't know where to write the output file */
+
+  flen = strlen(netoutfile);
+  if (!flen)  {
+      ffpmsg
+	("Output file not set, shouldn't have happened (http_compress_open)");
+      goto error;
+  }
+
+  if (rwmode != 0) {
+    ffpmsg("Can't open compressed http:// type file with READWRITE access");
+    ffpmsg("  Specify an UNCOMPRESSED outfile (http_compress_open)");
+    goto error;
+  }
+  /* do the signal handler bits */
+  if (setjmp(env) != 0) {
+    /* feels like the second time */
+    /* this means something bad happened */
+    ffpmsg("Timeout (http_open)");
+    goto error;
+  }
+
+  signal(SIGALRM, signal_handler);
+  
+  /* Open the http connectin */
+  alarm(NETTIMEOUT);
+  if ((status = http_open_network(url,&httpfile,contentencoding,
+			       &contentlength))) {
+    alarm(0);
+    ffpmsg("Unable to open http file (http_compress_open)");
+    ffpmsg(url);
+    goto error;
+  }
+
+  closehttpfile++;
+
+  /* Better be compressed */
+
+  firstchar = fgetc(httpfile);
+  ungetc(firstchar,httpfile);
+  if (!strcmp(contentencoding,"x-gzip") || 
+      !strcmp(contentencoding,"x-compress") ||
+      ('\037' == firstchar)) {
+
+    if (*netoutfile == '!')
+    {
+       /* user wants to clobber file, if it already exists */
+       for (ii = 0; ii < flen; ii++)
+           netoutfile[ii] = netoutfile[ii + 1];  /* remove '!' */
+
+       status = file_remove(netoutfile);
+    }
+
+    /* Create the new file */
+    if ((status =  file_create(netoutfile,handle))) {
+      ffpmsg("Unable to create output disk file (http_compress_open):");
+      ffpmsg(netoutfile);
+      goto error;
+    }
+    
+    closediskfile++;
+
+    /* write a file */
+    alarm(NETTIMEOUT);
+    while(0 != (len = fread(recbuf,1,MAXLEN,httpfile))) {
+      alarm(0);
+      status = file_write(*handle,recbuf,len);
+      if (status) {
+	ffpmsg("Error writing disk file (http_compres_open)");
+        ffpmsg(netoutfile);
+	goto error;
+      }
+      alarm(NETTIMEOUT);
+    }
+    file_close(*handle);
+    fclose(httpfile);
+    closehttpfile--;
+    closediskfile--;
+
+    /* File is on disk, let's uncompress it into memory */
+
+    if (NULL == (diskfile = fopen(netoutfile,"r"))) {
+      ffpmsg("Unable to reopen disk file (http_compress_open)");
+      ffpmsg(netoutfile);
+      goto error;
+    }
+    closefdiskfile++;
+
+    /* Create the memory handle to hold it */
+    if ((status =  mem_create(url,handle))) {
+      ffpmsg("Unable to create memory file (http_compress_open)");
+      goto error;
+    }
+    closememfile++;
+
+    /* Uncompress it */
+    status = 0;
+    status = mem_uncompress2mem(url,diskfile,*handle);
+    fclose(diskfile);
+    closefdiskfile--;
+    if (status) {
+      ffpmsg("Error uncompressing disk file to memory (http_compress_open)");
+      ffpmsg(netoutfile);
+      goto error;
+    }
+      
+  } else {
+    /* Opps, this should not have happened */
+    ffpmsg("Can only have compressed files here (http_compress_open)");
+    goto error;
+  }    
+    
+  signal(SIGALRM, SIG_DFL);
+  alarm(0);
+  return mem_seek(*handle,0);
+
+ error:
+  alarm(0); /* clear it */
+  if (closehttpfile) {
+    fclose(httpfile);
+  }
+  if (closefdiskfile) {
+    fclose(diskfile);
+  }
+  if (closememfile) {
+    mem_close_free(*handle);
+  }
+  if (closediskfile) {
+    file_close(*handle);
+  } 
+  
+  signal(SIGALRM, SIG_DFL);
+  return (FILE_NOT_OPENED);
+}
+
+/*--------------------------------------------------------------------------*/
+/* This creates a file handle with a copy of the URL in filename.  The http
+   file is copied to disk first.  If it's compressed then it is
+   uncompressed when copying to the disk */
+
+int http_file_open(char *url, int rwmode, int *handle)
+{
+  FILE *httpfile;
+  char contentencoding[SHORTLEN];
+  char errorstr[MAXLEN];
+  char recbuf[MAXLEN];
+  long len;
+  int contentlength;
+  int ii, flen, status;
+  char firstchar;
+
+  /* Check if output file is actually a memory file */
+  if (!strncmp(netoutfile, "mem:", 4) )
+  {
+     /* allow the memory file to be opened with write access */
+     return( http_open(url, READONLY, handle) );
+  }     
+
+  closehttpfile = 0;
+  closefile = 0;
+  closeoutfile = 0;
+
+  /* cfileio made a mistake, we need to know where to write the file */
+  flen = strlen(netoutfile);
+  if (!flen) {
+      ffpmsg("Output file not set, shouldn't have happened (http_file_open)");
+      return (FILE_NOT_OPENED);
+  }
+
+  /* do the signal handler bits */
+  if (setjmp(env) != 0) {
+    /* feels like the second time */
+    /* this means something bad happened */
+    ffpmsg("Timeout (http_open)");
+    goto error;
+  }
+
+  signal(SIGALRM, signal_handler);
+  
+  /* Open the network connection */
+  alarm(NETTIMEOUT);
+  if ((status = http_open_network(url,&httpfile,contentencoding,
+			       &contentlength))) {
+    alarm(0);
+    ffpmsg("Unable to open http file (http_file_open)");
+    ffpmsg(url);
+    goto error;
+  }
+
+  closehttpfile++;
+
+  if (*netoutfile == '!')
+  {
+     /* user wants to clobber disk file, if it already exists */
+     for (ii = 0; ii < flen; ii++)
+         netoutfile[ii] = netoutfile[ii + 1];  /* remove '!' */
+
+     status = file_remove(netoutfile);
+  }
+
+  firstchar = fgetc(httpfile);
+  ungetc(firstchar,httpfile);
+  if (!strcmp(contentencoding,"x-gzip") || 
+      !strcmp(contentencoding,"x-compress") ||
+      ('\037' == firstchar)) {
+
+    /* to make this more cfitsioish we use the file driver calls to create
+       the disk file */
+
+    /* Create the output file */
+    if ((status =  file_create(netoutfile,handle))) {
+      ffpmsg("Unable to create output file (http_file_open)");
+      ffpmsg(netoutfile);
+      goto error;
+    }
+
+    file_close(*handle);
+    if (NULL == (outfile = fopen(netoutfile,"w"))) {
+      ffpmsg("Unable to reopen the output file (http_file_open)");
+      ffpmsg(netoutfile);
+      goto error;
+    }
+    closeoutfile++;
+    status = 0;
+
+    /* Ok, this is a tough case, let's be arbritary and say 10*NETTIMEOUT,
+       Given the choices for nettimeout above they'll probaby ^C before, but
+       it's always worth a shot*/
+
+    alarm(NETTIMEOUT*10);
+    status = uncompress2file(url,httpfile,outfile,&status);
+    alarm(0);
+    if (status) {
+      ffpmsg("Error uncompressing http file to disk file (http_file_open)");
+      ffpmsg(url);
+      ffpmsg(netoutfile);
+      goto error;
+    }
+    fclose(outfile);
+    closeoutfile--;
+  } else {
+    
+    /* Create the output file */
+    if ((status =  file_create(netoutfile,handle))) {
+      ffpmsg("Unable to create output file (http_file_open)");
+      ffpmsg(netoutfile);
+      goto error;
+    }
+    
+    /* Give a warning message.  This could just be bad padding at the end
+       so don't treat it like an error. */
+    closefile++;
+    
+    if (contentlength % 2880) {
+      sprintf(errorstr,
+	      "Content-Length not a multiple of 2880 (http_file_open) %d",
+	      contentlength);
+      ffpmsg(errorstr);
+    }
+    
+    /* write a file */
+    alarm(NETTIMEOUT);
+    while(0 != (len = fread(recbuf,1,MAXLEN,httpfile))) {
+      alarm(0);
+      status = file_write(*handle,recbuf,len);
+      if (status) {
+	ffpmsg("Error copying http file to disk file (http_file_open)");
+        ffpmsg(url);
+        ffpmsg(netoutfile);
+	goto error;
+      }
+    }
+    file_close(*handle);
+    closefile--;
+  }
+  
+  fclose(httpfile);
+  closehttpfile--;
+
+  signal(SIGALRM, SIG_DFL);
+  alarm(0);
+
+  return file_open(netoutfile,rwmode,handle); 
+
+ error:
+  alarm(0); /* clear it */
+  if (closehttpfile) {
+    fclose(httpfile);
+  }
+  if (closeoutfile) {
+    fclose(outfile);
+  }
+  if (closefile) {
+    file_close(*handle);
+  } 
+  
+  signal(SIGALRM, SIG_DFL);
+  return (FILE_NOT_OPENED);
+}
+
+/*--------------------------------------------------------------------------*/
+/* This is the guts of the code to get a file via http.  
+   url is the input url
+   httpfile is set to be the file connected to the socket which you can
+     read the file from
+   contentencoding is the mime type of the file, returned if the http server
+     returns it
+   contentlength is the lenght of the file, returned if the http server returns
+     it
+*/
+static int http_open_network(char *url, FILE **httpfile, char *contentencoding,
+			  int *contentlength)
+{
+
+  int status;
+  int sock;
+  int tmpint;
+  char recbuf[MAXLEN];
+  char tmpstr[MAXLEN];
+  char tmpstr1[SHORTLEN];
+  char tmpstr2[MAXLEN];
+  char errorstr[MAXLEN];
+  char proto[SHORTLEN];
+  char host[SHORTLEN];
+  char userpass[MAXLEN];
+  char fn[MAXLEN];
+  char turl[MAXLEN];
+  char *scratchstr;
+  int port;
+  float version;
+
+  char pproto[SHORTLEN];
+  char phost[SHORTLEN]; /* address of the proxy server */
+  int  pport;  /* port number of the proxy server */
+  char pfn[MAXLEN];
+  char *proxy; /* URL of the proxy server */
+
+  /* Parse the URL apart again */
+  strcpy(turl,"http://";);
+  strncat(turl,url,MAXLEN - 8);
+  if (NET_ParseUrl(turl,proto,host,&port,fn)) {
+    sprintf(errorstr,"URL Parse Error (http_open) %s",url);
+    ffpmsg(errorstr);
+    return (FILE_NOT_OPENED);
+  }
+
+  /* Do we have a user:password combo ? */
+    strcpy(userpass, url);
+  if ((scratchstr = strchr(userpass, '@')) != NULL) {
+    *scratchstr = '\0';
+  } else
+    strcpy(userpass, "");
+
+  /* Ph. Prugniel 2003/04/03
+     Are we using a proxy?
+     
+     We use a proxy if the environment variable "http_proxy" is set to an
+     address, eg. http://wwwcache.nottingham.ac.uk:3128
+     ("http_proxy" is also used by wget)
+  */
+  proxy = getenv("http_proxy");
+
+  /* Connect to the remote host */
+  if (proxy) {
+    if (NET_ParseUrl(proxy,pproto,phost,&pport,pfn)) {
+      sprintf(errorstr,"URL Parse Error (http_open) %s",proxy);
+      ffpmsg(errorstr);
+      return (FILE_NOT_OPENED);
+    }
+    sock = NET_TcpConnect(phost,pport);
+  }
+  else
+    sock = NET_TcpConnect(host,port); 
+
+  if (sock < 0) {
+    if (proxy) {
+      ffpmsg("Couldn't connect to host via proxy server (http_open_network)");
+      ffpmsg(proxy);
+    }
+    return (FILE_NOT_OPENED);
+  }
+
+  /* Make the socket a stdio file */
+  if (NULL == (*httpfile = fdopen(sock,"r"))) {
+    ffpmsg ("fdopen failed to convert socket to file (http_open_network)");
+    close(sock);
+    return (FILE_NOT_OPENED);
+  }
+
+  /* Send the GET request to the remote server */
+  /* Ph. Prugniel 2003/04/03 
+     One must add the Host: command because of HTTP 1.1 servers (ie. virtual
+     hosts) */
+
+  if (proxy)
+    sprintf(tmpstr,"GET http://%s:%-d%s HTTP/1.0\r\n",host,port,fn);
+  else
+    sprintf(tmpstr,"GET %s HTTP/1.0\r\n",fn);
+
+  if (strcmp(userpass, "")) {
+    encode64(strlen(userpass), userpass, MAXLEN, tmpstr2);
+    sprintf(tmpstr1, "Authorization: Basic %s\r\n", tmpstr2);
+
+    if (strlen(tmpstr) + strlen(tmpstr1) > MAXLEN - 1)
+        return (FILE_NOT_OPENED);
+
+    strcat(tmpstr,tmpstr1);
+  }
+
+  sprintf(tmpstr1,"User-Agent: HEASARC/CFITSIO/%-8.3f\r\n",ffvers(&version));
+
+  if (strlen(tmpstr) + strlen(tmpstr1) > MAXLEN - 1)
+        return (FILE_NOT_OPENED);
+
+  strcat(tmpstr,tmpstr1);
+
+  /* HTTP 1.1 servers require the following 'Host: ' string */
+  sprintf(tmpstr1,"Host: %s:%-d\r\n\r\n",host,port);
+
+  if (strlen(tmpstr) + strlen(tmpstr1) > MAXLEN - 1)
+        return (FILE_NOT_OPENED);
+
+  strcat(tmpstr,tmpstr1);
+
+  status = NET_SendRaw(sock,tmpstr,strlen(tmpstr),NET_DEFAULT);
+
+  /* read the header */
+  if (!(fgets(recbuf,MAXLEN,*httpfile))) {
+    sprintf (errorstr,"http header short (http_open_network) %s",recbuf);
+    ffpmsg(errorstr);
+    fclose(*httpfile);
+    return (FILE_NOT_OPENED);
+  }
+  *contentlength = 0;
+  contentencoding[0] = '\0';
+
+  /* Our choices are 200, ok, 301, temporary redirect, or 302 perm redirect */
+  sscanf(recbuf,"%s %d",tmpstr,&status);
+  if (status != 200){
+    if (status == 301 || status == 302) {
+      /* got a redirect */
+      if (status == 301) {
+	ffpmsg("Note: Web server replied with a temporary redirect from");
+      } else {
+	ffpmsg("Note: Web server replied with a redirect from");
+      }
+      ffpmsg(turl);
+      /* now, let's not write the most sophisticated parser here */
+
+      while (fgets(recbuf,MAXLEN,*httpfile)) {
+	scratchstr = strstr(recbuf,"<A HREF=\"");
+	if (scratchstr != NULL) {
+	  /* Ok, we found the beginning of the anchor */
+	  scratchstr += 9; /* skip the <A HREF=" bits */
+	  scratchstr += 7; /* skip http://, we die if it's really ftp:// */
+	  strcpy(turl,strtok(scratchstr,"\""));
+	  sprintf(errorstr,"to %s\n",turl);
+	  ffpmsg(errorstr);
+	  fclose (*httpfile);
+	  return 
+	    http_open_network(turl,httpfile,contentencoding,contentlength);
+	}
+      }
+      /* if we get here then we couldnt' decide the redirect */
+      ffpmsg("but we were unable to find the redirected url in the servers response");
+    }
+/*    sprintf(errorstr, 
+	    "(http_open_network) Status not 200, was %d\nLine was %s\n",
+	    status,recbuf); 
+    ffpmsg(errorstr);
+*/
+    fclose(*httpfile);
+    return (FILE_NOT_OPENED);
+  }
+
+  /* from here the first word holds the keyword we want */
+  /* so, read the rest of the header */
+  while (fgets(recbuf,MAXLEN,*httpfile)) {
+    /* Blank line ends the header */
+    if (*recbuf == '\r') break;
+    if (strlen(recbuf) > 3) {
+      recbuf[strlen(recbuf)-1] = '\0';
+      recbuf[strlen(recbuf)-1] = '\0';
+    }
+    sscanf(recbuf,"%s %d",tmpstr,&tmpint);
+    /* Did we get a content-length header ? */
+    if (!strcmp(tmpstr,"Content-Length:")) {
+      *contentlength = tmpint;
+    }
+    /* Did we get the content-encoding header ? */
+    if (!strcmp(tmpstr,"Content-Encoding:")) {
+      if (NULL != (scratchstr = strstr(recbuf,":"))) {
+	/* Found the : */
+	scratchstr++; /* skip the : */
+	scratchstr++; /* skip the extra space */
+	strcpy(contentencoding,scratchstr);
+      }
+    }
+  }
+  
+  /* we're done, so return */
+  return 0;
+}
+
+
+/*--------------------------------------------------------------------------*/
+/* This creates a memory file handle with a copy of the URL in filename. The 
+   file is uncompressed if necessary */
+
+int ftp_open(char *filename, int rwmode, int *handle)
+{
+
+  FILE *ftpfile;
+  FILE *command;
+  int sock;
+  char newfilename[MAXLEN];
+  char recbuf[MAXLEN];
+  long len;
+  int status;
+  char firstchar;
+
+  closememfile = 0;
+  closecommandfile = 0;
+  closeftpfile = 0;
+
+  /* don't do r/w files */
+  if (rwmode != 0) {
+    ffpmsg("Can't open ftp:// type file with READWRITE access");
+    ffpmsg("Specify an outfile for r/w access (ftp_open)");
+    return (FILE_NOT_OPENED);
+  }
+
+  /* do the signal handler bits */
+  if (setjmp(env) != 0) {
+    /* feels like the second time */
+    /* this means something bad happened */
+    ffpmsg("Timeout (http_open)");
+    goto error;
+  }
+
+  signal(SIGALRM, signal_handler);
+  
+  /* Open the ftp connetion.  ftpfile is connected to the file port, 
+     command is connected to port 21.  sock is the socket on port 21 */
+
+  if (strlen(filename) > MAXLEN - 4) {
+      ffpmsg("filename too long (ftp_open)");
+      ffpmsg(filename);
+      goto error;
+  } 
+
+  alarm(NETTIMEOUT);
+  strcpy(newfilename,filename);
+  /* Does the file have a .Z or .gz in it */
+  if (strstr(newfilename,".Z") || strstr(newfilename,".gz")) {
+    alarm(NETTIMEOUT);
+    if (ftp_open_network(filename,&ftpfile,&command,&sock)) {
+
+      alarm(0);
+      ffpmsg("Unable to open ftp file (ftp_open)");
+      ffpmsg(filename);
+      goto error;
+    } 
+  } else {
+    /* Try the .gz one */
+    strcpy(newfilename,filename);
+    strcat(newfilename,".gz");
+    alarm(NETTIMEOUT);
+    if (ftp_open_network(newfilename,&ftpfile,&command,&sock)) {
+      
+      alarm(0);
+      strcpy(newfilename,filename);
+      strcat(newfilename,".Z");
+      alarm(NETTIMEOUT);
+      if (ftp_open_network(newfilename,&ftpfile,&command,&sock)) {
+	
+	/* Now as given */
+	alarm(0);
+	strcpy(newfilename,filename);
+	alarm(NETTIMEOUT);
+	if (ftp_open_network(newfilename,&ftpfile,&command,&sock)) {
+	  alarm(0);
+	  ffpmsg("Unable to open ftp file (ftp_open)");
+          ffpmsg(newfilename);
+	  goto error;
+	}
+      }
+    }
+  }
+
+  closeftpfile++;
+  closecommandfile++;
+
+  /* create the memory file */
+  if ((status = mem_create(filename,handle))) {
+    ffpmsg ("Could not create memory file to passive port (ftp_open)");
+    ffpmsg(filename);
+    goto error;
+  }
+  closememfile++;
+  /* This isn't quite right, it'll fail if the file has .gzabc at the end
+     for instance */
+
+  /* Decide if the file is compressed */
+  firstchar = fgetc(ftpfile);
+  ungetc(firstchar,ftpfile);
+
+  if (strstr(newfilename,".gz") || 
+      strstr(newfilename,".Z") ||
+      ('\037' == firstchar)) {
+    
+    status = 0;
+    /* A bit arbritary really, the user will probably hit ^C */
+    alarm(NETTIMEOUT*10);
+    status = mem_uncompress2mem(filename, ftpfile, *handle);
+    alarm(0);
+    if (status) {
+      ffpmsg("Error writing compressed memory file (ftp_open)");
+      ffpmsg(filename);
+      goto error;
+    }
+  } else {
+    /* write a memory file */
+    alarm(NETTIMEOUT);
+    while(0 != (len = fread(recbuf,1,MAXLEN,ftpfile))) {
+      alarm(0);
+      status = mem_write(*handle,recbuf,len);
+      if (status) {
+	ffpmsg("Error writing memory file (http_open)");
+        ffpmsg(filename);
+	goto error;
+      }
+      alarm(NETTIMEOUT);
+    }
+  }
+
+  /* close and clean up */
+  fclose(ftpfile);
+  closeftpfile--;
+
+  NET_SendRaw(sock,"QUIT\n",5,NET_DEFAULT);
+  fclose(command);
+  closecommandfile--;
+
+  signal(SIGALRM, SIG_DFL);
+  alarm(0);
+
+  return mem_seek(*handle,0);
+
+ error:
+  alarm(0); /* clear it */
+  if (closecommandfile) {
+    fclose(command);
+  }
+  if (closeftpfile) {
+    fclose(ftpfile);
+  }
+  if (closememfile) {
+    mem_close_free(*handle);
+  }
+  
+  signal(SIGALRM, SIG_DFL);
+  return (FILE_NOT_OPENED);
+}
+/*--------------------------------------------------------------------------*/
+/* This creates a file handle with a copy of the URL in filename. The 
+   file must be  uncompressed and is copied to disk first */
+
+int ftp_file_open(char *url, int rwmode, int *handle)
+{
+  FILE *ftpfile;
+  FILE *command;
+  char recbuf[MAXLEN];
+  long len;
+  int sock;
+  int ii, flen, status;
+  char firstchar;
+
+  /* Check if output file is actually a memory file */
+  if (!strncmp(netoutfile, "mem:", 4) )
+  {
+     /* allow the memory file to be opened with write access */
+     return( ftp_open(url, READONLY, handle) );
+  }     
+
+  closeftpfile = 0;
+  closecommandfile = 0;
+  closefile = 0;
+  closeoutfile = 0;
+  
+  /* cfileio made a mistake, need to know where to write the output file */
+  flen = strlen(netoutfile);
+  if (!flen) 
+    {
+      ffpmsg("Output file not set, shouldn't have happened (ftp_file_open)");
+      return (FILE_NOT_OPENED);
+    }
+
+  /* do the signal handler bits */
+  if (setjmp(env) != 0) {
+    /* feels like the second time */
+    /* this means something bad happened */
+    ffpmsg("Timeout (http_open)");
+    goto error;
+  }
+
+  signal(SIGALRM, signal_handler);
+  
+  /* open the network connection to url. ftpfile holds the connection to
+     the input file, command holds the connection to port 21, and sock is 
+     the socket connected to port 21 */
+
+  alarm(NETTIMEOUT);
+  if ((status = ftp_open_network(url,&ftpfile,&command,&sock))) {
+    alarm(0);
+    ffpmsg("Unable to open http file (ftp_file_open)");
+    ffpmsg(url);
+    goto error;
+  }
+  closeftpfile++;
+  closecommandfile++;
+
+  if (*netoutfile == '!')
+  {
+     /* user wants to clobber file, if it already exists */
+     for (ii = 0; ii < flen; ii++)
+         netoutfile[ii] = netoutfile[ii + 1];  /* remove '!' */
+
+     status = file_remove(netoutfile);
+  }
+
+  /* Now, what do we do with the file */
+  firstchar = fgetc(ftpfile);
+  ungetc(firstchar,ftpfile);
+
+  if (strstr(url,".gz") || 
+      strstr(url,".Z") ||
+      ('\037' == firstchar)) {
+
+    /* to make this more cfitsioish we use the file driver calls to create
+       the file */
+    /* Create the output file */
+    if ((status =  file_create(netoutfile,handle))) {
+      ffpmsg("Unable to create output file (ftp_file_open)");
+      ffpmsg(netoutfile);
+      goto error;
+    }
+
+    file_close(*handle);
+    if (NULL == (outfile = fopen(netoutfile,"w"))) {
+      ffpmsg("Unable to reopen the output file (ftp_file_open)");
+      ffpmsg(netoutfile);
+      goto error;
+    }
+    closeoutfile++;
+    status = 0;
+
+    /* Ok, this is a tough case, let's be arbritary and say 10*NETTIMEOUT,
+       Given the choices for nettimeout above they'll probaby ^C before, but
+       it's always worth a shot*/
+
+    alarm(NETTIMEOUT*10);
+    status = uncompress2file(url,ftpfile,outfile,&status);
+    alarm(0);
+    if (status) {
+      ffpmsg("Unable to uncompress the output file (ftp_file_open)");
+      ffpmsg(url);
+      ffpmsg(netoutfile);
+      goto error;
+    }
+    fclose(outfile);
+    closeoutfile--;
+
+  } else {
+    
+    /* Create the output file */
+    if ((status =  file_create(netoutfile,handle))) {
+      ffpmsg("Unable to create output file (ftp_file_open)");
+      ffpmsg(netoutfile);
+      goto error;
+    }
+    closefile++;
+    
+    /* write a file */
+    alarm(NETTIMEOUT);
+    while(0 != (len = fread(recbuf,1,MAXLEN,ftpfile))) {
+      alarm(0);
+      status = file_write(*handle,recbuf,len);
+      if (status) {
+	ffpmsg("Error writing file (ftp_file_open)");
+        ffpmsg(url);
+        ffpmsg(netoutfile);
+	goto error;
+      }
+      alarm(NETTIMEOUT);
+    }
+    file_close(*handle);
+  }
+  fclose(ftpfile);
+  closeftpfile--;
+  
+  NET_SendRaw(sock,"QUIT\n",5,NET_DEFAULT);
+  fclose(command);
+  closecommandfile--;
+
+  signal(SIGALRM, SIG_DFL);
+  alarm(0);
+
+  return file_open(netoutfile,rwmode,handle);
+
+ error:
+  alarm(0); /* clear it */
+  if (closeftpfile) {
+    fclose(ftpfile);
+  }
+  if (closecommandfile) {
+    fclose(command);
+  }
+  if (closeoutfile) {
+    fclose(outfile);
+  }
+  if (closefile) {
+    file_close(*handle);
+  } 
+  
+  signal(SIGALRM, SIG_DFL);
+  return (FILE_NOT_OPENED);
+}
+
+/*--------------------------------------------------------------------------*/
+/* This creates a memory  handle with a copy of the URL in filename. The 
+   file must be compressed and is copied to disk first */
+
+int ftp_compress_open(char *url, int rwmode, int *handle)
+{
+  FILE *ftpfile;
+  FILE *command;
+  char recbuf[MAXLEN];
+  long len;
+  int ii, flen, status;
+  int sock;
+  char firstchar;
+
+  closeftpfile = 0;
+  closecommandfile = 0;
+  closememfile = 0;
+  closefdiskfile = 0;
+  closediskfile = 0;
+
+  /* don't do r/w files */
+  if (rwmode != 0) {
+    ffpmsg("Compressed files must be r/o");
+    return (FILE_NOT_OPENED);
+  }
+  
+  /* Need to know where to write the output file */
+  flen = strlen(netoutfile);
+  if (!flen) 
+    {
+      ffpmsg(
+	"Output file not set, shouldn't have happened (ftp_compress_open)");
+      return (FILE_NOT_OPENED);
+    }
+  
+  /* do the signal handler bits */
+  if (setjmp(env) != 0) {
+    /* feels like the second time */
+    /* this means something bad happened */
+    ffpmsg("Timeout (http_open)");
+    goto error;
+  }
+  
+  signal(SIGALRM, signal_handler);
+  
+  /* Open the network connection to url, ftpfile is connected to the file 
+     port, command is connected to port 21.  sock is for writing to port 21 */
+  alarm(NETTIMEOUT);
+
+  if ((status = ftp_open_network(url,&ftpfile,&command,&sock))) {
+    alarm(0);
+    ffpmsg("Unable to open ftp file (ftp_compress_open)");
+    ffpmsg(url);
+    goto error;
+  }
+  closeftpfile++;
+  closecommandfile++;
+
+  /* Now, what do we do with the file */
+  firstchar = fgetc(ftpfile);
+  ungetc(firstchar,ftpfile);
+
+  if (strstr(url,".gz") || 
+      strstr(url,".Z") ||
+      ('\037' == firstchar)) {
+  
+    if (*netoutfile == '!')
+    {
+       /* user wants to clobber file, if it already exists */
+       for (ii = 0; ii < flen; ii++)
+          netoutfile[ii] = netoutfile[ii + 1];  /* remove '!' */
+
+       status = file_remove(netoutfile);
+    }
+
+    /* Create the output file */
+    if ((status =  file_create(netoutfile,handle))) {
+      ffpmsg("Unable to create output file (ftp_compress_open)");
+      ffpmsg(netoutfile);
+      goto error;
+    }
+    closediskfile++;
+    
+    /* write a file */
+    alarm(NETTIMEOUT);
+    while(0 != (len = fread(recbuf,1,MAXLEN,ftpfile))) {
+      alarm(0);
+      status = file_write(*handle,recbuf,len);
+      if (status) {
+	ffpmsg("Error writing file (ftp_compres_open)");
+        ffpmsg(url);
+        ffpmsg(netoutfile);
+	goto error;
+      }
+      alarm(NETTIMEOUT);
+    }
+
+    file_close(*handle);
+    closediskfile--;
+    fclose(ftpfile);
+    closeftpfile--;
+    /* Close down the ftp connection */
+    NET_SendRaw(sock,"QUIT\n",5,NET_DEFAULT);
+    fclose(command);
+    closecommandfile--;
+
+    /* File is on disk, let's uncompress it into memory */
+
+    if (NULL == (diskfile = fopen(netoutfile,"r"))) {
+      ffpmsg("Unable to reopen disk file (ftp_compress_open)");
+      ffpmsg(netoutfile);
+      return (FILE_NOT_OPENED);
+    }
+    closefdiskfile++;
+  
+    if ((status =  mem_create(url,handle))) {
+      ffpmsg("Unable to create memory file (ftp_compress_open)");
+      ffpmsg(url);
+      goto error;
+    }
+    closememfile++;
+
+    status = 0;
+    status = mem_uncompress2mem(url,diskfile,*handle);
+    fclose(diskfile);
+    closefdiskfile--;
+
+    if (status) {
+      ffpmsg("Error writing compressed memory file (ftp_compress_open)");
+      goto error;
+    }
+      
+  } else {
+    /* Opps, this should not have happened */
+    ffpmsg("Can only compressed files here (ftp_compress_open)");
+    goto error;
+  }    
+    
+
+  signal(SIGALRM, SIG_DFL);
+  alarm(0);
+  return mem_seek(*handle,0);
+
+ error:
+  alarm(0); /* clear it */
+  if (closeftpfile) {
+    fclose(ftpfile);
+  }
+  if (closecommandfile) {
+    fclose(command);
+  }
+  if (closefdiskfile) {
+    fclose(diskfile);
+  }
+  if (closememfile) {
+    mem_close_free(*handle);
+  }
+  if (closediskfile) {
+    file_close(*handle);
+  } 
+  
+  signal(SIGALRM, SIG_DFL);
+  return (FILE_NOT_OPENED);
+}
+
+/*--------------------------------------------------------------------------*/
+/* Open a ftp connection to filename (really a URL), return ftpfile set to 
+   the file connection, and command set to the control connection, with sock
+   also set to the control connection */
+
+int ftp_open_network(char *filename, FILE **ftpfile, FILE **command, int *sock)
+{
+  int status;
+  int sock1;
+  int tmpint;
+  char recbuf[MAXLEN];
+  char errorstr[MAXLEN];
+  char tmpstr[MAXLEN];
+  char proto[SHORTLEN];
+  char host[SHORTLEN];
+  char *newhost;
+  char *username;
+  char *password;
+  char fn[MAXLEN];
+  char *newfn;
+  char *passive;
+  char *tstr;
+  char ip[SHORTLEN];
+  char turl[MAXLEN];
+  int port;
+
+  /* parse the URL */
+  if (strlen(filename) > MAXLEN - 7) {
+    ffpmsg("ftp filename is too long (ftp_open)");
+    return (FILE_NOT_OPENED);
+  }
+
+  strcpy(turl,"ftp://";);
+  strcat(turl,filename);
+  if (NET_ParseUrl(turl,proto,host,&port,fn)) {
+    sprintf(errorstr,"URL Parse Error (ftp_open) %s",filename);
+    ffpmsg(errorstr);
+    return (FILE_NOT_OPENED);
+  }
+#ifdef DEBUG
+  printf ("proto, %s, host, %s, port %d, fn %s\n",proto,host,port,fn);
+#endif
+  
+  port = 21;
+  /* we might have a user name */
+  username = "anonymous";
+  password = "user host com";
+  /* is there an @ sign */
+  if (NULL != (newhost = strrchr(host,'@'))) {
+    *newhost = '\0'; /* make it a null, */
+    newhost++; /* Now newhost points to the host name and host points to the 
+		  user name, password combo */
+    username = host;
+    /* is there a : for a password */
+    if (NULL != strchr(username,':')) {
+      password = strchr(username,':');
+      *password = '\0';
+      password++;
+    }
+  } else {
+    newhost = host;
+  }
+  
+#ifdef DEBUG
+  printf("User %s pass %s\n",username,password); 
+#endif
+  
+  /* Connect to the host on the required port */
+  *sock = NET_TcpConnect(newhost,port);
+  /* convert it to a stdio file */
+  if (NULL == (*command = fdopen(*sock,"r"))) {
+    ffpmsg ("fdopen failed to convert socket to stdio file (ftp_open)");
+    return (FILE_NOT_OPENED);
+    
+  }
+
+  /* Wait for the 220 response */
+  if (ftp_status(*command,"220 ")) {
+    ffpmsg ("error connecting to remote server, no 220 seen (ftp_open)");
+    fclose(*command);
+    return (FILE_NOT_OPENED);
+  }
+  
+  /* Send the user name and wait for the right response */
+  sprintf(tmpstr,"USER %s\n",username);
+  status = NET_SendRaw(*sock,tmpstr,strlen(tmpstr),NET_DEFAULT);
+  
+  if (ftp_status(*command,"331 ")) {
+    ffpmsg ("USER error no 331 seen (ftp_open)");
+    fclose(*command);
+    return (FILE_NOT_OPENED);
+    
+  }
+  
+  /* Send the password and wait for the right response */
+  sprintf(tmpstr,"PASS %s\n",password);
+  status = NET_SendRaw(*sock,tmpstr,strlen(tmpstr),NET_DEFAULT);
+  
+  if (ftp_status(*command,"230 ")) {
+    ffpmsg ("PASS error, no 230 seen (ftp_open)");
+    fclose(*command);
+    return (FILE_NOT_OPENED);
+  }
+  
+  
+  /* now do the cwd command */
+  newfn = strrchr(fn,'/');
+  if (newfn == NULL) {
+    strcpy(tmpstr,"CWD /\n");
+    newfn = fn;
+  } else {
+    *newfn = '\0';
+    newfn++;
+    if (strlen(fn) == 0) {
+      strcpy(tmpstr,"CWD /\n");
+    } else {
+      /* remove the leading slash */
+      if (fn[0] == '/') {
+	sprintf(tmpstr,"CWD %s\n",&fn[1]);
+      } else {
+	sprintf(tmpstr,"CWD %s\n",fn);
+      } 
+    }
+  }
+  
+#ifdef DEBUG
+  printf("CWD command is %s\n",tmpstr);
+#endif
+  status = NET_SendRaw(*sock,tmpstr,strlen(tmpstr),NET_DEFAULT);
+  
+  if (ftp_status(*command,"250 ")) {
+    ffpmsg ("CWD error, no 250 seen (ftp_open)");
+    fclose(*command);
+    return (FILE_NOT_OPENED);
+  }
+  
+  if (!strlen(newfn)) {
+    ffpmsg("Null file name (ftp_open)");
+    fclose(*command);
+    return (FILE_NOT_OPENED);
+  }
+  
+ 
+  /* Always use binary mode */
+  sprintf(tmpstr,"TYPE I\n");
+  status = NET_SendRaw(*sock,tmpstr,strlen(tmpstr),NET_DEFAULT);
+  
+  if (ftp_status(*command,"200 ")) {
+    ffpmsg ("TYPE I error, 200 not seen (ftp_open)");
+    fclose(*command);
+    return (FILE_NOT_OPENED);
+  }
+ 
+  status = NET_SendRaw(*sock,"PASV\n",5,NET_DEFAULT);
+  if (!(fgets(recbuf,MAXLEN,*command))) {
+    ffpmsg ("PASV error (ftp_open)");
+    fclose(*command);
+    return (FILE_NOT_OPENED);
+  }
+  
+  /*  Passive mode response looks like
+      227 Entering Passive Mode (129,194,67,8,210,80) */
+  if (recbuf[0] == '2' && recbuf[1] == '2' && recbuf[2] == '7') {
+    /* got a good passive mode response, find the opening ( */
+    
+    if (!(passive = strchr(recbuf,'('))) {
+      ffpmsg ("PASV error (ftp_open)");
+      fclose(*command);
+      return (FILE_NOT_OPENED);
+    }
+    
+    *passive = '\0';
+    passive++;
+    ip[0] = '\0';
+      
+    /* Messy parsing of response from PASV *command */
+    
+    if (!(tstr = strtok(passive,",)"))) {
+      ffpmsg ("PASV error (ftp_open)");
+      fclose(*command);
+      return (FILE_NOT_OPENED);
+    }
+    strcpy(ip,tstr);
+    strcat(ip,".");
+    
+    if (!(tstr = strtok(NULL,",)"))) {
+      ffpmsg ("PASV error (ftp_open)");
+      fclose(*command);
+      return (FILE_NOT_OPENED);
+    }
+    strcat(ip,tstr);
+    strcat(ip,".");
+    
+    if (!(tstr = strtok(NULL,",)"))) {
+      ffpmsg ("PASV error (ftp_open)");
+      fclose(*command);
+      return (FILE_NOT_OPENED);
+    }
+    strcat(ip,tstr);
+    strcat(ip,".");
+    
+    if (!(tstr = strtok(NULL,",)"))) {
+      ffpmsg ("PASV error (ftp_open)");
+      fclose(*command);
+      return (FILE_NOT_OPENED);
+    }
+    strcat(ip,tstr);
+    
+    /* Done the ip number, now do the port # */
+    if (!(tstr = strtok(NULL,",)"))) {
+      ffpmsg ("PASV error (ftp_open)");
+      fclose(*command);
+      return (FILE_NOT_OPENED);
+    }
+    sscanf(tstr,"%d",&port);
+    port *= 256;
+    
+    if (!(tstr = strtok(NULL,",)"))) {
+      ffpmsg ("PASV error (ftp_open)");
+      fclose(*command);
+      return (FILE_NOT_OPENED);
+    }
+    sscanf(tstr,"%d",&tmpint);
+    port += tmpint;
+    
+    
+    if (!strlen(newfn)) {
+      ffpmsg("Null file name (ftp_open)");
+      fclose(*command);
+      return (FILE_NOT_OPENED);
+    }
+    
+
+#ifdef DEBUG
+    puts("connection to passive port");
+#endif
+    /* COnnect to the data port */
+    sock1 = NET_TcpConnect(ip,port);
+    if (NULL == (*ftpfile = fdopen(sock1,"r"))) {
+      ffpmsg ("Could not connect to passive port (ftp_open)");
+      fclose(*command);
+      return (FILE_NOT_OPENED);
+    }
+
+    /* now we return */
+
+    /* Send the retrieve command */
+    sprintf(tmpstr,"RETR %s\n",newfn);
+    status = NET_SendRaw(*sock,tmpstr,strlen(tmpstr),NET_DEFAULT);
+
+#ifdef DEBUG
+    puts("Sent RETR command");
+#endif
+    if (ftp_status(*command,"150 ")) {
+    /*  ffpmsg ("RETR error, most likely file is not there (ftp_open)"); */
+      fclose(*command);
+#ifdef DEBUG
+      puts("File not there");
+#endif
+      return (FILE_NOT_OPENED);
+    }
+    return 0;
+  }
+  
+  /* no passive mode */
+
+  NET_SendRaw(*sock,"QUIT\n",5,NET_DEFAULT);
+  fclose(*command);
+  return (FILE_NOT_OPENED);
+}
+
+/*--------------------------------------------------------------------------*/
+/* return a socket which results from connection to hostname on port port */
+static int NET_TcpConnect(char *hostname, int port)
+{
+  /* Connect to hostname on port */
+ 
+   struct sockaddr_in sockaddr;
+   int sock;
+   int stat;
+   int val = 1;
+ 
+   CreateSocketAddress(&sockaddr,hostname,port);
+   /* Create socket */
+   if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
+     ffpmsg("Can't create socket");
+     return CONNECTION_ERROR;
+   }
+ 
+   if ((stat = connect(sock, (struct sockaddr*) &sockaddr, 
+		       sizeof(sockaddr))) 
+       < 0) {
+     close(sock);
+/*
+     perror("NET_Tcpconnect - Connection error");
+     ffpmsg("Can't connect to host, connection error");
+*/
+     return CONNECTION_ERROR;
+   }
+   setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *)&val, sizeof(val));
+   setsockopt(sock, SOL_SOCKET,  SO_KEEPALIVE, (char *)&val, sizeof(val));
+
+   val = 65536;
+   setsockopt(sock, SOL_SOCKET,  SO_SNDBUF,    (char *)&val, sizeof(val));
+   setsockopt(sock, SOL_SOCKET,  SO_RCVBUF,    (char *)&val, sizeof(val));
+   return sock;
+}
+
+/*--------------------------------------------------------------------------*/
+/* Write len bytes from buffer to socket sock */
+static int NET_SendRaw(int sock, const void *buffer, int length, int opt)
+{
+
+  char * buf = (char *) buffer;
+ 
+   int flag;
+   int n, nsent = 0;
+ 
+   switch (opt) {
+   case NET_DEFAULT:
+     flag = 0;
+     break;
+   case NET_OOB:
+     flag = MSG_OOB;
+     break;
+   case NET_PEEK:            
+   default:
+     flag = 0;
+     break;
+   }
+ 
+   if (sock < 0) return -1;
+   
+   for (n = 0; n < length; n += nsent) {
+     if ((nsent = send(sock, buf+n, length-n, flag)) <= 0) {
+       return nsent;
+     }
+#ifdef DEBUG
+     printf ("send raw, sent %d bytes\n",nsent);
+#endif
+   }
+#ifdef DEBUG
+   printf ("send raw end, sent %d bytes\n",n);
+#endif
+   return n;
+}
+
+/*--------------------------------------------------------------------------*/
+
+static int NET_RecvRaw(int sock, void *buffer, int length)
+{
+  /* Receive exactly length bytes into buffer. Returns number of bytes */
+  /* received. Returns -1 in case of error. */
+
+
+   int nrecv, n;
+   char *buf = (char *)buffer;
+
+   if (sock < 0) return -1;
+   for (n = 0; n < length; n += nrecv) {
+      while ((nrecv = recv(sock, buf+n, length-n, 0)) == -1 && errno == EINTR)
+	errno = 0;     /* probably a SIGCLD that was caught */
+      if (nrecv < 0)
+         return nrecv;
+      else if (nrecv == 0)
+	break;        /*/ EOF */
+   }
+
+   return n;
+}
+ 
+/*--------------------------------------------------------------------------*/
+/* Yet Another URL Parser 
+   url - input url
+   proto - input protocol
+   host - output host
+   port - output port
+   fn - output filename
+*/
+
+static int NET_ParseUrl(const char *url, char *proto, char *host, int *port, 
+		 char *fn)
+{
+  /* parses urls into their bits */
+  /* returns 1 if error, else 0 */
+
+  char *urlcopy, *urlcopyorig;
+  char *ptrstr;
+  char *thost;
+  int isftp = 0;
+
+  /* figure out if there is a http: or  ftp: */
+
+  urlcopyorig = urlcopy = (char *) malloc(strlen(url)+1);
+  strcpy(urlcopy,url);
+
+  /* set some defaults */
+  *port = 80;
+  strcpy(proto,"http:");
+  strcpy(host,"localhost");
+  strcpy(fn,"/");
+  
+  ptrstr = strstr(urlcopy,"http:");
+  if (ptrstr == NULL) {
+    /* Nope, not http: */
+    ptrstr = strstr(urlcopy,"root:");
+    if (ptrstr == NULL) {
+      /* Nope, not root either */
+      ptrstr = strstr(urlcopy,"ftp:");
+      if (ptrstr != NULL) {
+	if (ptrstr == urlcopy) {
+	  strcpy(proto,"ftp:");
+	  *port = 21;
+	  isftp++;
+	  urlcopy += 4; /* move past ftp: */
+	} else {
+	  /* not at the beginning, bad url */
+	  free(urlcopyorig);
+	  return 1;
+	}
+      }
+    } else {
+      if (ptrstr == urlcopy) {
+	urlcopy += 5; /* move past root: */
+      } else {
+	/* not at the beginning, bad url */
+	free(urlcopyorig);
+	return 1;
+      }
+    }
+  } else {
+    if (ptrstr == urlcopy) {
+      urlcopy += 5; /* move past http: */
+    } else {
+      free(urlcopyorig);
+      return 1;
+    }
+  }
+
+  /* got the protocol */
+  /* get the hostname */
+  if (urlcopy[0] == '/' && urlcopy[1] == '/') {
+    /* we have a hostname */
+    urlcopy += 2; /* move past the // */
+  }
+  /* do this only if http */
+  if (!strcmp(proto,"http:")) {
+
+    /* Move past any user:password */
+    if ((thost = strchr(urlcopy, '@')) != NULL)
+      urlcopy = thost+1;
+
+    strcpy(host,urlcopy);
+    thost = host;
+    while (*urlcopy != '/' && *urlcopy != ':' && *urlcopy) {
+      thost++;
+      urlcopy++;
+    }
+    /* we should either be at the end of the string, have a /, or have a : */
+    *thost = '\0';
+    if (*urlcopy == ':') {
+      /* follows a port number */
+      urlcopy++;
+      sscanf(urlcopy,"%d",port);
+      while (*urlcopy != '/' && *urlcopy) urlcopy++; /* step to the */
+    }
+  } else {
+    /* do this for ftp */
+    strcpy(host,urlcopy);
+    thost = host;
+    while (*urlcopy != '/' && *urlcopy) {
+      thost++;
+      urlcopy++; 
+    }
+    *thost = '\0';
+    /* Now, we should either be at the end of the string, or have a / */
+    
+  }
+  /* Now the rest is a fn */
+
+  if (*urlcopy) {
+    strcpy(fn,urlcopy);
+  }
+  free(urlcopyorig);
+  return 0;
+}
+
+/*--------------------------------------------------------------------------*/
+
+/* Small helper functions to set the netoutfile static string */
+/* Called by cfileio after parsing the output file off of the input file url */
+
+int http_checkfile (char *urltype, char *infile, char *outfile1)
+{
+  char newinfile[MAXLEN];
+  FILE *httpfile;
+  char contentencoding[MAXLEN];
+  int contentlength;
+  
+  /* default to http:// if there is no output file */
+    
+  strcpy(urltype,"http://";);
+
+  if (strlen(outfile1)) {
+    /* there is an output file */
+
+    /* don't copy the "file://" prefix, if present.  */
+    if (!strncmp(outfile1, "file://", 7) )
+       strcpy(netoutfile,outfile1+7);
+    else
+       strcpy(netoutfile,outfile1);
+
+    if (!strncmp(outfile1, "mem:", 4) )  {
+       /* copy the file to memory, with READ and WRITE access 
+          In this case, it makes no difference whether the http file
+          and or the output file are compressed or not.   */
+
+       strcpy(urltype, "httpmem://");  /* use special driver */
+       return 0;
+    }
+
+    if (strstr(infile, "?")) {
+      /* file name contains a '?' so probably a cgi string; don't open it */
+      strcpy(urltype,"httpfile://");
+      return 0;
+    }
+
+    if (!http_open_network(infile,&httpfile,contentencoding,&contentlength)) {
+      fclose(httpfile);
+      /* It's there, we're happy */
+      if (strstr(infile,".gz") || (strstr(infile,".Z"))) {
+	/* It's compressed */
+	if (strstr(outfile1,".gz") || (strstr(outfile1,".Z"))) {
+	  strcpy(urltype,"httpcompress://");
+	} else {
+	  strcpy(urltype,"httpfile://");
+	}
+      } else {
+	strcpy(urltype,"httpfile://");
+      }
+      return 0;
+    }
+
+    /* Ok, let's try the .gz one */
+    strcpy(newinfile,infile);
+    strcat(newinfile,".gz");
+    if (!http_open_network(newinfile,&httpfile,contentencoding,
+			   &contentlength)) {
+      fclose(httpfile);
+      strcpy(infile,newinfile);
+      /* It's there, we're happy, and, it's compressed  */
+      /* It's compressed */
+      if (strstr(outfile1,".gz") || (strstr(outfile1,".Z"))) {
+	strcpy(urltype,"httpcompress://");
+      } else {
+	strcpy(urltype,"httpfile://");
+      }
+      return 0;
+    }
+    
+    /* Ok, let's try the .Z one */
+    strcpy(newinfile,infile);
+    strcat(newinfile,".Z");
+    if (!http_open_network(newinfile,&httpfile,contentencoding,
+			   &contentlength)) {
+      fclose(httpfile);
+      strcpy(infile,newinfile);
+      /* It's there, we're happy, and, it's compressed  */
+      if (strstr(outfile1,".gz") || (strstr(outfile1,".Z"))) {
+	strcpy(urltype,"httpcompress://");
+      } else {
+	strcpy(urltype,"httpfile://");
+      }
+      return 0;
+    }
+    
+  } 
+  return 0;
+}
+/*--------------------------------------------------------------------------*/
+int ftp_checkfile (char *urltype, char *infile, char *outfile1)
+{
+  char newinfile[MAXLEN];
+  FILE *ftpfile;
+  FILE *command;
+  int sock;
+
+  
+  /* default to ftp://   */
+    
+  strcpy(urltype,"ftp://";);
+
+  if (strlen(outfile1)) {
+    /* there is an output file */
+
+    /* don't copy the "file://" prefix, if present.  */
+    if (!strncmp(outfile1, "file://", 7) )
+       strcpy(netoutfile,outfile1+7);
+    else
+       strcpy(netoutfile,outfile1);
+
+    if (!strncmp(outfile1, "mem:", 4) )  {
+       /* copy the file to memory, with READ and WRITE access 
+          In this case, it makes no difference whether the ftp file
+          and or the output file are compressed or not.   */
+
+       strcpy(urltype, "ftpmem://");  /* use special driver */
+       return 0;
+    }
+
+    if (!ftp_open_network(infile,&ftpfile,&command,&sock)) {
+      fclose(ftpfile);
+      fclose(command);
+      /* It's there, we're happy */
+      if (strstr(infile,".gz") || (strstr(infile,".Z"))) {
+	/* It's compressed */
+	if (strstr(outfile1,".gz") || (strstr(outfile1,".Z"))) {
+	  strcpy(urltype,"ftpcompress://");
+	} else {
+	  strcpy(urltype,"ftpfile://");
+	}
+      } else {
+	strcpy(urltype,"ftpfile://");
+      }
+      return 0;
+    }
+
+    /* Ok, let's try the .gz one */
+    strcpy(newinfile,infile);
+    strcat(newinfile,".gz");
+    if (!ftp_open_network(newinfile,&ftpfile,&command,&sock)) {
+      fclose(ftpfile);
+      fclose(command);
+      strcpy(infile,newinfile);
+      /* It's there, we're happy, and, it's compressed  */
+      if (strstr(outfile1,".gz") || (strstr(outfile1,".Z"))) {
+	strcpy(urltype,"ftpcompress://");
+      } else {
+	strcpy(urltype,"ftpfile://");
+      }
+      return 0;
+    }
+    
+    /* Ok, let's try the .Z one */
+    strcpy(newinfile,infile);
+    strcat(newinfile,".Z");
+    if (!ftp_open_network(newinfile,&ftpfile,&command,&sock)) {
+      fclose(ftpfile);
+      fclose(command);
+      strcpy(infile,newinfile);
+      if (strstr(outfile1,".gz") || (strstr(outfile1,".Z"))) {
+	strcpy(urltype,"ftpcompress://");
+      } else {
+	strcpy(urltype,"ftpfile://");
+      }
+      return 0;
+    }
+    
+  } 
+  return 0;
+}
+/*--------------------------------------------------------------------------*/
+/* A small helper function to wait for a particular status on the ftp 
+   connectino */
+static int ftp_status(FILE *ftp, char *statusstr)
+{
+  /* read through until we find a string beginning with statusstr */
+  /* This needs a timeout */
+
+  char recbuf[MAXLEN];
+  int len;
+
+  len = strlen(statusstr);
+  while (1) {
+    if (!(fgets(recbuf,MAXLEN,ftp))) {
+#ifdef DEBUG
+      puts("error reading response in ftp_status");
+#endif
+      return 1; /* error reading */
+    }
+    
+#ifdef DEBUG
+    printf("ftp_status, return string was %s\n",recbuf);
+#endif
+
+    recbuf[len] = '\0'; /* make it short */
+    if (!strcmp(recbuf,statusstr)) {
+      return 0; /* we're ok */
+    }
+    if (recbuf[0] > '3') {
+      /* oh well, some sort of error */
+      return 1; 
+    }
+  }
+}
+
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * CreateSocketAddress --
+ *
+ *	This function initializes a sockaddr structure for a host and port.
+ *
+ * Results:
+ *	1 if the host was valid, 0 if the host could not be converted to
+ *	an IP address.
+ *
+ * Side effects:
+ *	Fills in the *sockaddrPtr structure.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+CreateSocketAddress(
+    struct sockaddr_in *sockaddrPtr,	/* Socket address */
+    char *host,				/* Host.  NULL implies INADDR_ANY */
+    int port)				/* Port number */
+{
+    struct hostent *hostent;		/* Host database entry */
+    struct in_addr addr;		/* For 64/32 bit madness */
+    char localhost[MAXLEN];
+
+    strcpy(localhost,host);
+
+    memset((void *) sockaddrPtr, '\0', sizeof(struct sockaddr_in));
+    sockaddrPtr->sin_family = AF_INET;
+    sockaddrPtr->sin_port = htons((unsigned short) (port & 0xFFFF));
+    if (host == NULL) {
+	addr.s_addr = INADDR_ANY;
+    } else {
+        addr.s_addr = inet_addr(localhost);
+        if (addr.s_addr == 0xFFFFFFFF) {
+            hostent = gethostbyname(localhost);
+            if (hostent != NULL) {
+                memcpy((void *) &addr,
+                        (void *) hostent->h_addr_list[0],
+                        (size_t) hostent->h_length);
+            } else {
+#ifdef	EHOSTUNREACH
+                errno = EHOSTUNREACH;
+#else
+#ifdef ENXIO
+                errno = ENXIO;
+#endif
+#endif
+                return 0;	/* error */
+            }
+        }
+    }
+        
+    /*
+     * NOTE: On 64 bit machines the assignment below is rumored to not
+     * do the right thing. Please report errors related to this if you
+     * observe incorrect behavior on 64 bit machines such as DEC Alphas.
+     * Should we modify this code to do an explicit memcpy?
+     */
+
+    sockaddrPtr->sin_addr.s_addr = addr.s_addr;
+    return 1;	/* Success. */
+}
+
+/* Signal handler for timeouts */
+
+static void signal_handler(int sig) {
+
+  switch (sig) {
+  case SIGALRM:    /* process for alarm */
+    longjmp(env,sig);
+    
+  default: {
+      /* Hmm, shouldn't have happend */
+      exit(sig);
+    }
+  }
+}
+
+/**************************************************************/
+
+/* Root driver */
+
+/*--------------------------------------------------------------------------*/
+int root_init(void)
+{
+    int ii;
+
+    for (ii = 0; ii < NMAXFILES; ii++) /* initialize all empty slots in table */
+    {
+       handleTable[ii].sock = 0;
+       handleTable[ii].currentpos = 0;
+    }
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int root_setoptions(int options)
+{
+  /* do something with the options argument, to stop compiler warning */
+  options = 0;
+  return(options);
+}
+/*--------------------------------------------------------------------------*/
+int root_getoptions(int *options)
+{
+  *options = 0;
+  return(0);
+}
+/*--------------------------------------------------------------------------*/
+int root_getversion(int *version)
+{
+    *version = 10;
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int root_shutdown(void)
+{
+  return(0);
+}
+/*--------------------------------------------------------------------------*/
+int root_open(char *url, int rwmode, int *handle)
+{
+    int ii, status;
+    int sock;
+
+    *handle = -1;
+    for (ii = 0; ii < NMAXFILES; ii++)  /* find empty slot in table */
+    {
+        if (handleTable[ii].sock == 0)
+        {
+            *handle = ii;
+            break;
+        }
+    }
+
+    if (*handle == -1)
+       return(TOO_MANY_FILES);    /* too many files opened */
+
+    /*open the file */
+    if (rwmode) {
+      status = root_openfile(url, "update", &sock);
+    } else {
+      status = root_openfile(url, "read", &sock);
+    }
+    if (status)
+      return(status);
+    
+    handleTable[ii].sock = sock;
+    handleTable[ii].currentpos = 0;
+    
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int root_create(char *filename, int *handle)
+{
+    int ii, status;
+    int sock;
+
+    *handle = -1;
+    for (ii = 0; ii < NMAXFILES; ii++)  /* find empty slot in table */
+    {
+        if (handleTable[ii].sock == 0)
+        {
+            *handle = ii;
+            break;
+        }
+    }
+
+    if (*handle == -1)
+       return(TOO_MANY_FILES);    /* too many files opened */
+
+    /*open the file */
+    status = root_openfile(filename, "create", &sock);
+
+    if (status) {
+      ffpmsg("Unable to create file");
+      return(status);
+    }
+    
+    handleTable[ii].sock = sock;
+    handleTable[ii].currentpos = 0;
+    
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int root_size(int handle, LONGLONG *filesize)
+/*
+  return the size of the file in bytes
+*/
+{
+
+  int sock;
+  int offset;
+  int status;
+  int op;
+
+  sock = handleTable[handle].sock;
+
+  status = root_send_buffer(sock,ROOTD_STAT,NULL,0);
+  status = root_recv_buffer(sock,&op,(char *)&offset, 4);
+  *filesize = (LONGLONG) ntohl(offset);
+  
+  return(0);
+}
+/*--------------------------------------------------------------------------*/
+int root_close(int handle)
+/*
+  close the file
+*/
+{
+
+  int status;
+  int sock;
+
+  sock = handleTable[handle].sock;
+  status = root_send_buffer(sock,ROOTD_CLOSE,NULL,0);
+  close(sock);
+  handleTable[handle].sock = 0;
+  return(0);
+}
+/*--------------------------------------------------------------------------*/
+int root_flush(int handle)
+/*
+  flush the file
+*/
+{
+  int status;
+  int sock;
+
+  sock = handleTable[handle].sock;
+  status = root_send_buffer(sock,ROOTD_FLUSH,NULL,0);
+  return(0);
+}
+/*--------------------------------------------------------------------------*/
+int root_seek(int handle, LONGLONG offset)
+/*
+  seek to position relative to start of the file
+*/
+{
+  handleTable[handle].currentpos = offset;
+  return(0);
+}
+/*--------------------------------------------------------------------------*/
+int root_read(int hdl, void *buffer, long nbytes)
+/*
+  read bytes from the current position in the file
+*/
+{
+  char msg[SHORTLEN];
+  int op;
+  int status;
+  int astat;
+
+  /* we presume here that the file position will never be > 2**31 = 2.1GB */
+  sprintf(msg,"%ld %ld ",(long) handleTable[hdl].currentpos,nbytes);
+  status = root_send_buffer(handleTable[hdl].sock,ROOTD_GET,msg,strlen(msg));
+  if ((unsigned) status != strlen(msg)) {
+    return (READ_ERROR);
+  }
+  astat = 0;
+  status = root_recv_buffer(handleTable[hdl].sock,&op,(char *) &astat,4);
+  if (astat != 0) {
+    return (READ_ERROR);
+  }
+#ifdef DEBUG
+  printf("root_read, op %d astat %d\n",op,astat);
+#endif
+  status = NET_RecvRaw(handleTable[hdl].sock,buffer,nbytes);
+  if (status != nbytes) {
+    return (READ_ERROR);
+  }
+  handleTable[hdl].currentpos += nbytes;
+
+  return(0);
+}
+/*--------------------------------------------------------------------------*/
+int root_write(int hdl, void *buffer, long nbytes)
+/*
+  write bytes at the current position in the file
+*/
+{
+
+  char msg[SHORTLEN];
+  int len;
+  int sock;
+  int status;
+  int astat;
+  int op;
+
+  sock = handleTable[hdl].sock;
+  /* we presume here that the file position will never be > 2**31 = 2.1GB */
+  sprintf(msg,"%ld %ld ",(long) handleTable[hdl].currentpos,nbytes);
+
+  len = strlen(msg);
+  status = root_send_buffer(sock,ROOTD_PUT,msg,len+1);
+  if (status != len+1) {
+    return (WRITE_ERROR);
+  }
+  status = NET_SendRaw(sock,buffer,nbytes,NET_DEFAULT);
+  if (status != nbytes) {
+    return (WRITE_ERROR);
+  }
+  astat = 0;
+  status = root_recv_buffer(handleTable[hdl].sock,&op,(char *) &astat,4);
+#ifdef DEBUG
+  printf("root_read, op %d astat %d\n",op,astat);
+#endif
+  if (astat != 0) {
+    return (WRITE_ERROR);
+  }
+  handleTable[hdl].currentpos += nbytes;
+  return(0);
+}
+
+/*--------------------------------------------------------------------------*/
+int root_openfile(char *url, char *rwmode, int *sock)
+     /*
+       lowest level routine to physically open a root file
+     */
+{
+  
+  int status;
+  char recbuf[MAXLEN];
+  char errorstr[MAXLEN];
+  char proto[SHORTLEN];
+  char host[SHORTLEN];
+  char fn[MAXLEN];
+  char turl[MAXLEN];
+  int port;
+  int op;
+  int ii;
+  int authstat;
+  
+  
+  /* Parse the URL apart again */
+  strcpy(turl,"root://");
+  strcat(turl,url);
+  if (NET_ParseUrl(turl,proto,host,&port,fn)) {
+    sprintf(errorstr,"URL Parse Error (root_open) %s",url);
+    ffpmsg(errorstr);
+    return (FILE_NOT_OPENED);
+  }
+  
+#ifdef DEBUG
+  printf("Connecting to %s on port %d\n",host,port);
+#endif
+  /* Connect to the remote host */
+  *sock = NET_TcpConnect(host,port);
+  if (*sock < 0) {
+    ffpmsg("Couldn't connect to host (http_open_network)");
+    return (FILE_NOT_OPENED);
+  }
+  
+  /* get the username */
+  if (NULL != getenv("ROOTUSERNAME")) {
+    strcpy(recbuf,getenv("ROOTUSERNAME"));
+  } else {
+    printf("Username: ");
+    fgets(recbuf,MAXLEN,stdin);
+    recbuf[strlen(recbuf)-1] = '\0';
+  }
+  
+  status = root_send_buffer(*sock, ROOTD_USER, recbuf,strlen(recbuf));
+  if (status < 0) {
+    ffpmsg("error talking to remote system on username ");
+    return (FILE_NOT_OPENED);
+  }
+  
+  status = root_recv_buffer(*sock,&op,(char *)&authstat,4);
+  if (!status) {
+    ffpmsg("error talking to remote system on username");
+    return (FILE_NOT_OPENED);
+  }
+  
+#ifdef DEBUG
+  printf("op is %d and authstat is %d\n",op,authstat);
+#endif
+  
+  if (op != ROOTD_AUTH) {
+    ffpmsg("ERROR on ROOTD_USER");
+    ffpmsg(recbuf);
+    return (FILE_NOT_OPENED);
+  }
+  
+
+  /* now the password */
+  if (NULL != getenv("ROOTPASSWORD")) {
+    strcpy(recbuf,getenv("ROOTPASSWORD"));
+  } else {
+    printf("Password: ");
+    fgets(recbuf,MAXLEN,stdin);
+    recbuf[strlen(recbuf)-1] = '\0';
+  }
+  /* ones complement the password */
+  for (ii=0;(unsigned) ii<strlen(recbuf);ii++) {
+    recbuf[ii] = ~recbuf[ii];
+  }
+  
+  status = root_send_buffer(*sock, ROOTD_PASS, recbuf, strlen(recbuf));
+  if (status < 0) {
+    ffpmsg("error talking to remote system sending password");
+    return (FILE_NOT_OPENED);
+  }
+  
+  status = root_recv_buffer(*sock,&op,(char *)&authstat,4);
+  if (status < 0) {
+    ffpmsg("error talking to remote system acking password");
+    return (FILE_NOT_OPENED);
+  }
+  
+#ifdef DEBUG
+  printf("op is %d and authstat is %d\n",op,authstat);
+#endif
+  if (op != ROOTD_AUTH) {
+    ffpmsg("ERROR on ROOTD_PASS");
+    ffpmsg(recbuf);
+    return (FILE_NOT_OPENED);
+  }
+  
+  /* now the file open request */
+  strcpy(recbuf,fn);
+  strcat(recbuf," ");
+  strcat(recbuf,rwmode);
+
+  status = root_send_buffer(*sock, ROOTD_OPEN, recbuf, strlen(recbuf));
+  if (status < 0) {
+    ffpmsg("error talking to remote system on open ");
+    return (FILE_NOT_OPENED);
+  }
+
+  status = root_recv_buffer(*sock,&op,(char *)&authstat,4);
+  if (status < 0) {
+    ffpmsg("error talking to remote system on open");
+    return (FILE_NOT_OPENED);
+  }
+
+#ifdef DEBUG
+  printf("op is %d and recbuf is %d\n",op,authstat);
+#endif
+  
+  if ((op != ROOTD_OPEN) && (authstat != 0)) {
+    ffpmsg("ERROR on ROOTD_OPEN");
+    ffpmsg(recbuf);
+    return (FILE_NOT_OPENED);
+  }
+
+  return 0;
+
+}
+
+static int root_send_buffer(int sock, int op, char *buffer, int buflen)
+{
+  /* send a buffer, the form is
+     <len>
+     <op>
+     <buffer>
+
+     <len> includes the 4 bytes for the op, the length bytes (4) are implicit
+
+
+     if buffer is null don't send it, not everything needs something sent */
+
+  int len;
+  int status;
+
+  int hdr[2];
+
+  len = 4;
+
+  if (buffer != NULL) {
+    len += buflen;
+  }
+  
+  hdr[0] = htonl(len);
+
+#ifdef DEBUG
+  printf("len sent is %x\n",hdr[0]);
+#endif
+
+  hdr[1] = htonl(op);
+#ifdef DEBUG
+  printf("op sent is %x\n",hdr[1]);
+#endif
+  
+
+#ifdef DEBUG
+  printf("Sending op %d and length of %d\n",op,len);
+#endif
+
+  status = NET_SendRaw(sock,hdr,sizeof(hdr),NET_DEFAULT);
+  if (status < 0) {
+    return status;
+  }
+  if (buffer != NULL) {
+    status = NET_SendRaw(sock,buffer,buflen,NET_DEFAULT);
+  }
+  return status;
+}
+  
+static int root_recv_buffer(int sock, int *op, char *buffer, int buflen)
+{
+  /* recv a buffer, the form is
+     <len>
+     <op>
+     <buffer>
+
+  */
+
+  int recv1 = 0;
+  int len;
+  int status;
+  char recbuf[MAXLEN];
+
+  status = NET_RecvRaw(sock,&len,4);
+#ifdef DEBUG
+  printf("Recv: status from rec is %d\n",status);
+#endif
+  if (status < 0) {
+    return status;
+  }
+  recv1 += status;
+
+  len = ntohl(len);
+#ifdef DEBUG
+  printf ("Recv: length is %d\n",len);
+#endif
+
+  /* ok, have the length, recive the operation */
+  len -= 4;
+  status = NET_RecvRaw(sock,op,4);
+  if (status < 0) {
+    return status;
+  }
+
+  recv1 += status;
+
+  *op = ntohl(*op);
+#ifdef DEBUG
+  printf ("Recv: Operation is %d\n",*op);
+#endif
+  
+  if (len > MAXLEN) {
+    len = MAXLEN;
+  }
+
+  if (len > 0) { /* Get the rest of the message */
+    status = NET_RecvRaw(sock,recbuf,len);
+    if (len > buflen) {
+      len = buflen;
+    }
+    memcpy(buffer,recbuf,len);
+    if (status < 0) {
+      return status;
+    }
+  } 
+
+  recv1 += status;
+  return recv1;
+
+}
+
+/*****************************************************************************/
+/*
+  Encode a string into MIME Base64 format string
+*/
+
+
+static int encode64(unsigned s_len, char *src, unsigned d_len, char *dst) {
+
+  static char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+"abcdefghijklmnopqrstuvwxyz"
+"0123456789"
+"+/";
+
+  unsigned triad;
+
+
+  for (triad = 0; triad < s_len; triad += 3) {
+    unsigned long int sr;
+    unsigned byte;
+
+    for (byte = 0; (byte<3) && (triad+byte<s_len); ++byte) {
+      sr <<= 8;
+      sr |= (*(src+triad+byte) & 0xff);
+    }
+
+    /* shift left to next 6 bit alignment*/
+    sr <<= (6-((8*byte)%6))%6;
+
+    if (d_len < 4)
+      return 1;
+
+    *(dst+0) = *(dst+1) = *(dst+2) = *(dst+3) = '=';
+    switch(byte) {
+    case 3:
+      *(dst+3) = base64[sr&0x3f];
+      sr >>= 6;
+    case 2:
+      *(dst+2) = base64[sr&0x3f];
+      sr >>= 6;
+    case 1:
+      *(dst+1) = base64[sr&0x3f];
+      sr >>= 6;
+      *(dst+0) = base64[sr&0x3f];
+    }
+    dst += 4;
+    d_len -= 4;
+  }
+
+  *dst = '\0';
+  return 0;
+}
+
+
+#endif
diff --git a/src/plugins/cfitsio/drvrsmem.c b/src/plugins/cfitsio/drvrsmem.c
new file mode 100644
index 0000000..c5fda75
--- /dev/null
+++ b/src/plugins/cfitsio/drvrsmem.c
@@ -0,0 +1,973 @@
+/*              S H A R E D   M E M O R Y   D R I V E R
+                =======================================
+
+                  by Jerzy Borkowski obs unige ch
+
+09-Mar-98 : initial version 1.0 released
+23-Mar-98 : shared_malloc now accepts new handle as an argument
+23-Mar-98 : shmem://0, shmem://1, etc changed to shmem://h0, etc due to bug
+            in url parser.
+10-Apr-98 : code cleanup
+13-May-99 : delayed initialization added, global table deleted on exit when
+            no shmem segments remain, and last process terminates
+*/
+
+#ifdef HAVE_SHMEM_SERVICES
+#include "fitsio2.h"                         /* drvrsmem.h is included by it */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#if defined(unix) || defined(__unix__)  || defined(__unix)
+#include <unistd.h> 
+#endif
+
+
+static int shared_kbase = 0;                    /* base for shared memory handles */
+static int shared_maxseg = 0;                   /* max number of shared memory blocks */
+static int shared_range = 0;                    /* max number of tried entries */
+static int shared_fd = SHARED_INVALID;          /* handle of global access lock file */
+static int shared_gt_h = SHARED_INVALID;        /* handle of global table segment */
+static SHARED_LTAB *shared_lt = NULL;           /* local table pointer */
+static SHARED_GTAB *shared_gt = NULL;           /* global table pointer */
+static int shared_create_mode = 0666;           /* permission flags for created objects */
+static int shared_debug = 1;                    /* simple debugging tool, set to 0 to disable messages */
+static int shared_init_called = 0;              /* flag whether shared_init() has been called, used for delayed init */
+
+                /* static support routines prototypes */
+
+static  int shared_clear_entry(int idx);        /* unconditionally clear entry */
+static  int shared_destroy_entry(int idx);      /* unconditionally destroy sema & shseg and clear entry */
+static  int shared_mux(int idx, int mode);      /* obtain exclusive access to specified segment */
+static  int shared_demux(int idx, int mode);    /* free exclusive access to specified segment */
+
+static  int shared_process_count(int sem);      /* valid only for time of invocation */
+static  int shared_delta_process(int sem, int delta); /* change number of processes hanging on segment */
+static  int shared_attach_process(int sem);
+static  int shared_detach_process(int sem);
+static  int shared_get_free_entry(int newhandle);       /* get free entry in shared_key, or -1, entry is set rw locked */
+static  int shared_get_hash(long size, int idx);/* return hash value for malloc */
+static  long shared_adjust_size(long size);     /* size must be >= 0 !!! */
+static  int shared_check_locked_index(int idx); /* verify that given idx is valid */ 
+static  int shared_map(int idx);                /* map all tables for given idx, check for validity */
+static  int shared_validate(int idx, int mode); /* use intrnally inside crit.sect !!! */
+
+                /* support routines - initialization */
+
+
+static  int shared_clear_entry(int idx)         /* unconditionally clear entry */
+ { if ((idx < 0) || (idx >= shared_maxseg)) return(SHARED_BADARG);
+   shared_gt[idx].key = SHARED_INVALID;         /* clear entries in global table */
+   shared_gt[idx].handle = SHARED_INVALID;
+   shared_gt[idx].sem = SHARED_INVALID;
+   shared_gt[idx].semkey = SHARED_INVALID;
+   shared_gt[idx].nprocdebug = 0;
+   shared_gt[idx].size = 0;
+   shared_gt[idx].attr = 0;
+
+   return(SHARED_OK);
+ }
+
+static  int shared_destroy_entry(int idx)       /* unconditionally destroy sema & shseg and clear entry */
+ { int r, r2;
+   union semun filler;
+
+   if ((idx < 0) || (idx >= shared_maxseg)) return(SHARED_BADARG);
+   r2 = r = SHARED_OK;
+   filler.val = 0;                              /* this is to make cc happy (warning otherwise) */
+   if (SHARED_INVALID != shared_gt[idx].sem)  r = semctl(shared_gt[idx].sem, 0, IPC_RMID, filler); /* destroy semaphore */
+   if (SHARED_INVALID != shared_gt[idx].handle) r2 = shmctl(shared_gt[idx].handle, IPC_RMID, 0); /* destroy shared memory segment */
+   if (SHARED_OK == r) r = r2;                  /* accumulate error code in r, free r2 */
+   r2 = shared_clear_entry(idx);
+   return((SHARED_OK == r) ? r2 : r);
+ }
+
+void    shared_cleanup(void)                    /* this must (should) be called during exit/abort */
+ { int          i, j, r, oktodelete, filelocked, segmentspresent;
+   flock_t      flk;
+   struct shmid_ds  ds;
+
+   if (shared_debug) printf("shared_cleanup:");
+   if (NULL != shared_lt)
+     { if (shared_debug) printf(" deleting segments:");
+       for (i=0; i<shared_maxseg; i++)
+        { if (0 == shared_lt[i].tcnt) continue; /* we're not using this segment, skip this ... */
+          if (-1 != shared_lt[i].lkcnt) continue;  /* seg not R/W locked by us, skip this ... */
+
+          r = shared_destroy_entry(i);          /* destroy unconditionally sema & segment */
+          if (shared_debug) 
+            { if (SHARED_OK == r) printf(" [%d]", i);
+              else printf(" [error on %d !!!!]", i);
+
+            }
+        }
+       free((void *)shared_lt);                 /* free local table */
+       shared_lt = NULL;
+     }
+   if (NULL != shared_gt)                       /* detach global index table */
+     { oktodelete = 0;
+       filelocked = 0;
+       if (shared_debug) printf(" detaching globalsharedtable");
+       if (SHARED_INVALID != shared_fd)
+
+       flk.l_type = F_WRLCK;                    /* lock whole lock file */
+       flk.l_whence = 0;
+       flk.l_start = 0;
+       flk.l_len = shared_maxseg;
+       if (-1 != fcntl(shared_fd, F_SETLK, &flk))
+         { filelocked = 1;                      /* success, scan global table, to see if there are any segs */
+           segmentspresent = 0;                 /* assume, there are no segs in the system */
+           for (j=0; j<shared_maxseg; j++)
+            { if (SHARED_INVALID != shared_gt[j].key)
+                { segmentspresent = 1;          /* yes, there is at least one */
+                  break;
+                }
+            }
+           if (0 == segmentspresent)            /* if there are no segs ... */
+             if (0 == shmctl(shared_gt_h, IPC_STAT, &ds)) /* get number of processes attached to table */
+               { if (ds.shm_nattch <= 1) oktodelete = 1; /* if only one (we), then it is safe (but see text 4 lines later) to unlink */
+               }
+         }
+       shmdt((char *)shared_gt);                /* detach global table */
+       if (oktodelete)                          /* delete global table from system, if no shm seg present */
+         { shmctl(shared_gt_h, IPC_RMID, 0);    /* there is a race condition here - time window between shmdt and shmctl */
+           shared_gt_h = SHARED_INVALID;
+         }
+       shared_gt = NULL;
+       if (filelocked)                          /* if we locked, we need to unlock */
+         { flk.l_type = F_UNLCK;
+           flk.l_whence = 0;
+           flk.l_start = 0;
+           flk.l_len = shared_maxseg;
+           fcntl(shared_fd, F_SETLK, &flk);
+         }
+     }
+   shared_gt_h = SHARED_INVALID;
+
+   if (SHARED_INVALID != shared_fd)             /* close lock file */
+     { if (shared_debug) printf(" closing lockfile");
+       close(shared_fd);
+       shared_fd = SHARED_INVALID;
+     }
+
+   
+   shared_kbase = 0;
+   shared_maxseg = 0;
+   shared_range = 0;
+   shared_init_called = 0;
+
+   if (shared_debug) printf(" <<done>>\n");
+   return;
+ }
+
+
+int     shared_init(int debug_msgs)             /* initialize shared memory stuff, you have to call this routine once */
+ { int i;
+   char buf[1000], *p;
+   mode_t oldumask;
+
+   shared_init_called = 1;                      /* tell everybody no need to call us for the 2nd time */
+   shared_debug = debug_msgs;                   /* set required debug mode */
+   
+   if (shared_debug) printf("shared_init:");
+
+   shared_kbase = 0;                            /* adapt to current env. settings */
+   if (NULL != (p = getenv(SHARED_ENV_KEYBASE))) shared_kbase = atoi(p);
+   if (0 == shared_kbase) shared_kbase = SHARED_KEYBASE;
+   if (shared_debug) printf(" keybase=%d", shared_kbase);
+
+   shared_maxseg = 0;
+   if (NULL != (p = getenv(SHARED_ENV_MAXSEG))) shared_maxseg = atoi(p);
+   if (0 == shared_maxseg) shared_maxseg = SHARED_MAXSEG;
+   if (shared_debug) printf(" maxseg=%d", shared_maxseg);
+   
+   shared_range = 3 * shared_maxseg;
+
+   if (SHARED_INVALID == shared_fd)             /* create rw locking file (this file is never deleted) */
+     { if (shared_debug) printf(" lockfileinit=");
+       sprintf(buf, "%s.%d.%d", SHARED_FDNAME, shared_kbase, shared_maxseg);
+       oldumask = umask(0);
+
+       shared_fd = open(buf, O_TRUNC | O_EXCL | O_CREAT | O_RDWR, shared_create_mode);
+       umask(oldumask);
+       if (SHARED_INVALID == shared_fd)         /* or just open rw locking file, in case it already exists */
+         { shared_fd = open(buf, O_TRUNC | O_RDWR, shared_create_mode);
+           if (SHARED_INVALID == shared_fd) return(SHARED_NOFILE);
+           if (shared_debug) printf("slave");
+
+         }
+       else
+         { if (shared_debug) printf("master");
+         }
+     }
+
+   if (SHARED_INVALID == shared_gt_h)           /* global table not attached, try to create it in shared memory */
+     { if (shared_debug) printf(" globalsharedtableinit=");
+       shared_gt_h = shmget(shared_kbase, shared_maxseg * sizeof(SHARED_GTAB), IPC_CREAT | IPC_EXCL | shared_create_mode); /* try open as a master */
+       if (SHARED_INVALID == shared_gt_h)       /* if failed, try to open as a slave */
+         { shared_gt_h = shmget(shared_kbase, shared_maxseg * sizeof(SHARED_GTAB), shared_create_mode);
+           if (SHARED_INVALID == shared_gt_h) return(SHARED_IPCERR); /* means deleted ID residing in system, shared mem unusable ... */
+           shared_gt = (SHARED_GTAB *)shmat(shared_gt_h, 0, 0); /* attach segment */
+           if (((SHARED_GTAB *)SHARED_INVALID) == shared_gt) return(SHARED_IPCERR);
+           if (shared_debug) printf("slave");
+         }
+       else
+         { shared_gt = (SHARED_GTAB *)shmat(shared_gt_h, 0, 0); /* attach segment */
+           if (((SHARED_GTAB *)SHARED_INVALID) == shared_gt) return(SHARED_IPCERR);
+           for (i=0; i<shared_maxseg; i++) shared_clear_entry(i);       /* since we are master, init data */
+           if (shared_debug) printf("master");
+         }
+     }
+
+   if (NULL == shared_lt)                       /* initialize local table */
+     { if (shared_debug) printf(" localtableinit=");
+       if (NULL == (shared_lt = (SHARED_LTAB *)malloc(shared_maxseg * sizeof(SHARED_LTAB)))) return(SHARED_NOMEM);
+       for (i=0; i<shared_maxseg; i++)
+        { shared_lt[i].p = NULL;                /* not mapped */
+          shared_lt[i].tcnt = 0;                /* unused (or zero threads using this seg) */
+          shared_lt[i].lkcnt = 0;               /* segment is unlocked */
+          shared_lt[i].seekpos = 0L;            /* r/w pointer at the beginning of file */
+        }
+       if (shared_debug) printf("ok");
+     }
+
+   atexit(shared_cleanup);                      /* we want shared_cleanup to be called at exit or abort */
+
+   if (shared_debug) printf(" <<done>>\n");
+   return(SHARED_OK);
+ }
+
+
+int     shared_recover(int id)                  /* try to recover dormant segments after applic crash */
+ { int i, r, r2;
+
+   if (NULL == shared_gt) return(SHARED_NOTINIT);       /* not initialized */
+   if (NULL == shared_lt) return(SHARED_NOTINIT);       /* not initialized */
+   r = SHARED_OK;
+   for (i=0; i<shared_maxseg; i++)
+    { if (-1 != id) if (i != id) continue;
+      if (shared_lt[i].tcnt) continue;          /* somebody (we) is using it */
+      if (SHARED_INVALID == shared_gt[i].key) continue; /* unused slot */
+      if (shared_mux(i, SHARED_NOWAIT | SHARED_RDWRITE)) continue; /* acquire exclusive access to segment, but do not wait */
+      r2 = shared_process_count(shared_gt[i].sem);
+      if ((shared_gt[i].nprocdebug > r2) || (0 == r2))
+        { if (shared_debug) printf("Bogus handle=%d nproc=%d sema=%d:", i, shared_gt[i].nprocdebug, r2);
+          r = shared_destroy_entry(i);
+          if (shared_debug)
+            { printf("%s", r ? "error couldn't clear handle" : "handle cleared");
+            }
+        }
+      shared_demux(i, SHARED_RDWRITE);
+    }
+   return(r);                                           /* table full */
+ }
+
+                /* API routines - mutexes and locking */
+
+static  int shared_mux(int idx, int mode)       /* obtain exclusive access to specified segment */
+ { flock_t flk;
+
+   int r;
+
+   if (0 == shared_init_called)                 /* delayed initialization */
+     { if (SHARED_OK != (r = shared_init(0))) return(r);
+
+     }
+   if (SHARED_INVALID == shared_fd) return(SHARED_NOTINIT);
+   if ((idx < 0) || (idx >= shared_maxseg)) return(SHARED_BADARG);
+   flk.l_type = ((mode & SHARED_RDWRITE) ? F_WRLCK : F_RDLCK);
+   flk.l_whence = 0;
+   flk.l_start = idx;
+   flk.l_len = 1;
+   if (shared_debug) printf(" [mux (%d): ", idx);
+   if (-1 == fcntl(shared_fd, ((mode & SHARED_NOWAIT) ? F_SETLK : F_SETLKW), &flk))
+     { switch (errno)
+        { case EAGAIN: ;
+
+          case EACCES: if (shared_debug) printf("again]");
+                       return(SHARED_AGAIN);
+          default:     if (shared_debug) printf("err]");
+                       return(SHARED_IPCERR);
+        }
+     }
+   if (shared_debug) printf("ok]");
+   return(SHARED_OK);
+ }
+
+
+
+static  int shared_demux(int idx, int mode)     /* free exclusive access to specified segment */
+ { flock_t flk;
+
+   if (SHARED_INVALID == shared_fd) return(SHARED_NOTINIT);
+   if ((idx < 0) || (idx >= shared_maxseg)) return(SHARED_BADARG);
+   flk.l_type = F_UNLCK;
+   flk.l_whence = 0;
+   flk.l_start = idx;
+   flk.l_len = 1;
+   if (shared_debug) printf(" [demux (%d): ", idx);
+   if (-1 == fcntl(shared_fd, F_SETLKW, &flk))
+     { switch (errno)
+        { case EAGAIN: ;
+          case EACCES: if (shared_debug) printf("again]");
+                       return(SHARED_AGAIN);
+          default:     if (shared_debug) printf("err]");
+                       return(SHARED_IPCERR);
+        }
+
+     }
+   if (shared_debug) printf("mode=%d ok]", mode);
+   return(SHARED_OK);
+ }
+
+
+
+static int shared_process_count(int sem)                /* valid only for time of invocation */
+ { union semun su;
+
+   su.val = 0;                                          /* to force compiler not to give warning messages */
+   return(semctl(sem, 0, GETVAL, su));                  /* su is unused here */
+ }
+
+
+static int shared_delta_process(int sem, int delta)     /* change number of processes hanging on segment */
+ { struct sembuf sb;
+ 
+   if (SHARED_INVALID == sem) return(SHARED_BADARG);    /* semaphore not attached */
+   sb.sem_num = 0;
+   sb.sem_op = delta;
+   sb.sem_flg = SEM_UNDO;
+   return((-1 == semop(sem, &sb, 1)) ? SHARED_IPCERR : SHARED_OK);
+ }
+
+
+static int shared_attach_process(int sem)
+ { if (shared_debug) printf(" [attach process]");
+   return(shared_delta_process(sem, 1));
+ }
+
+
+static int shared_detach_process(int sem)
+ { if (shared_debug) printf(" [detach process]");
+   return(shared_delta_process(sem, -1));
+ }
+
+                /* API routines - hashing and searching */
+
+
+static int shared_get_free_entry(int newhandle)         /* get newhandle, or -1, entry is set rw locked */
+ {
+   if (NULL == shared_gt) return(-1);                   /* not initialized */
+   if (NULL == shared_lt) return(-1);                   /* not initialized */
+   if (newhandle < 0) return(-1);
+   if (newhandle >= shared_maxseg) return(-1);
+   if (shared_lt[newhandle].tcnt) return(-1);                   /* somebody (we) is using it */
+   if (shared_mux(newhandle, SHARED_NOWAIT | SHARED_RDWRITE)) return(-1); /* used by others */
+   if (SHARED_INVALID == shared_gt[newhandle].key) return(newhandle); /* we have found free slot, lock it and return index */
+   shared_demux(newhandle, SHARED_RDWRITE);
+   if (shared_debug) printf("[free_entry - ERROR - entry unusable]");
+   return(-1);                                          /* table full */
+ }
+
+
+static int shared_get_hash(long size, int idx)  /* return hash value for malloc */
+ { static int counter = 0;
+   int hash;
+
+   hash = (counter + size * idx) % shared_range;
+   counter = (counter + 1) % shared_range;
+   return(hash);
+ }
+
+
+static  long shared_adjust_size(long size)              /* size must be >= 0 !!! */
+ { return(((size + sizeof(BLKHEAD) + SHARED_GRANUL - 1) / SHARED_GRANUL) * SHARED_GRANUL); }
+
+
+                /* API routines - core : malloc/realloc/free/attach/detach/lock/unlock */
+
+int     shared_malloc(long size, int mode, int newhandle)               /* return idx or SHARED_INVALID */
+ { int h, i, r, idx, key;
+   union semun filler;
+   BLKHEAD *bp;
+   
+   if (0 == shared_init_called)                 /* delayed initialization */
+     { if (SHARED_OK != (r = shared_init(0))) return(r);
+     }
+   if (shared_debug) printf("malloc (size = %ld, mode = %d):", size, mode);
+   if (size < 0) return(SHARED_INVALID);
+   if (-1 == (idx = shared_get_free_entry(newhandle)))  return(SHARED_INVALID);
+   if (shared_debug) printf(" idx=%d", idx);
+   for (i = 0; ; i++)
+    { if (i >= shared_range)                            /* table full, signal error & exit */
+        { shared_demux(idx, SHARED_RDWRITE);
+          return(SHARED_INVALID);
+        }
+      key = shared_kbase + ((i + shared_get_hash(size, idx)) % shared_range);
+      if (shared_debug) printf(" key=%d", key);
+      h = shmget(key, shared_adjust_size(size), IPC_CREAT | IPC_EXCL | shared_create_mode);
+      if (shared_debug) printf(" handle=%d", h);
+      if (SHARED_INVALID == h) continue;                /* segment already accupied */
+      bp = (BLKHEAD *)shmat(h, 0, 0);                   /* try attach */
+      if (shared_debug) printf(" p=%p", bp);
+      if (((BLKHEAD *)SHARED_INVALID) == bp)            /* cannot attach, delete segment, try with another key */
+        { shmctl(h, IPC_RMID, 0);
+          continue;
+        }                                               /* now create semaphor counting number of processes attached */
+      if (SHARED_INVALID == (shared_gt[idx].sem = semget(key, 1, IPC_CREAT | IPC_EXCL | shared_create_mode)))
+        { shmdt((void *)bp);                            /* cannot create segment, delete everything */
+          shmctl(h, IPC_RMID, 0);
+          continue;                                     /* try with another key */
+        }
+      if (shared_debug) printf(" sem=%d", shared_gt[idx].sem);
+      if (shared_attach_process(shared_gt[idx].sem))    /* try attach process */
+        { semctl(shared_gt[idx].sem, 0, IPC_RMID, filler);      /* destroy semaphore */
+          shmdt((char *)bp);                            /* detach shared mem segment */
+          shmctl(h, IPC_RMID, 0);                       /* destroy shared mem segment */
+          continue;                                     /* try with another key */
+        }
+      bp->s.tflag = BLOCK_SHARED;                       /* fill in data in segment's header (this is really not necessary) */
+      bp->s.ID[0] = SHARED_ID_0;
+      bp->s.ID[1] = SHARED_ID_1;
+      bp->s.handle = idx;                               /* used in yorick */
+      if (mode & SHARED_RESIZE)
+        { if (shmdt((char *)bp)) r = SHARED_IPCERR;     /* if segment is resizable, then detach segment */
+          shared_lt[idx].p = NULL;
+        }
+      else  { shared_lt[idx].p = bp; }
+      shared_lt[idx].tcnt = 1;                          /* one thread using segment */
+      shared_lt[idx].lkcnt = 0;                         /* no locks at the moment */
+      shared_lt[idx].seekpos = 0L;                      /* r/w pointer positioned at beg of block */
+      shared_gt[idx].handle = h;                        /* fill in data in global table */
+      shared_gt[idx].size = size;
+      shared_gt[idx].attr = mode;
+      shared_gt[idx].semkey = key;
+      shared_gt[idx].key = key;
+      shared_gt[idx].nprocdebug = 0;
+
+      break;
+    }
+   shared_demux(idx, SHARED_RDWRITE);                   /* hope this will not fail */
+   return(idx);
+ }
+
+
+int     shared_attach(int idx)
+ { int r, r2;
+
+   if (SHARED_OK != (r = shared_mux(idx, SHARED_RDWRITE | SHARED_WAIT))) return(r);
+   if (SHARED_OK != (r = shared_map(idx)))
+     { shared_demux(idx, SHARED_RDWRITE);
+       return(r);
+     }
+   if (shared_attach_process(shared_gt[idx].sem))       /* try attach process */
+     { shmdt((char *)(shared_lt[idx].p));               /* cannot attach process, detach everything */
+       shared_lt[idx].p = NULL;
+       shared_demux(idx, SHARED_RDWRITE);
+       return(SHARED_BADARG);
+     }
+   shared_lt[idx].tcnt++;                               /* one more thread is using segment */
+   if (shared_gt[idx].attr & SHARED_RESIZE)             /* if resizeable, detach and return special pointer */
+     { if (shmdt((char *)(shared_lt[idx].p))) r = SHARED_IPCERR;  /* if segment is resizable, then detach segment */
+       shared_lt[idx].p = NULL;
+     }
+   shared_lt[idx].seekpos = 0L;                         /* r/w pointer positioned at beg of block */
+   r2 = shared_demux(idx, SHARED_RDWRITE);
+   return(r ? r : r2);
+ }
+
+
+
+static int      shared_check_locked_index(int idx)      /* verify that given idx is valid */ 
+ { int r;
+
+   if (0 == shared_init_called)                         /* delayed initialization */
+     { if (SHARED_OK != (r = shared_init(0))) return(r);
+
+     }
+   if ((idx < 0) || (idx >= shared_maxseg)) return(SHARED_BADARG);
+   if (NULL == shared_lt[idx].p) return(SHARED_BADARG); /* NULL pointer, not attached ?? */
+   if (0 == shared_lt[idx].lkcnt) return(SHARED_BADARG); /* not locked ?? */
+   if ((SHARED_ID_0 != (shared_lt[idx].p)->s.ID[0]) || (SHARED_ID_1 != (shared_lt[idx].p)->s.ID[1]) || 
+       (BLOCK_SHARED != (shared_lt[idx].p)->s.tflag))   /* invalid data in segment */
+     return(SHARED_BADARG);
+   return(SHARED_OK);
+ }
+
+
+
+static int      shared_map(int idx)                     /* map all tables for given idx, check for validity */
+ { int h;                                               /* have to obtain excl. access before calling shared_map */
+   BLKHEAD *bp;
+
+   if ((idx < 0) || (idx >= shared_maxseg)) return(SHARED_BADARG);
+   if (SHARED_INVALID == shared_gt[idx].key)  return(SHARED_BADARG);
+   if (SHARED_INVALID == (h = shmget(shared_gt[idx].key, 1, shared_create_mode)))  return(SHARED_BADARG);
+   if (((BLKHEAD *)SHARED_INVALID) == (bp = (BLKHEAD *)shmat(h, 0, 0)))  return(SHARED_BADARG);
+   if ((SHARED_ID_0 != bp->s.ID[0]) || (SHARED_ID_1 != bp->s.ID[1]) || (BLOCK_SHARED != bp->s.tflag) || (h != shared_gt[idx].handle))
+     { shmdt((char *)bp);                               /* invalid segment, detach everything */
+       return(SHARED_BADARG);
+
+     }
+   if (shared_gt[idx].sem != semget(shared_gt[idx].semkey, 1, shared_create_mode)) /* check if sema is still there */
+     { shmdt((char *)bp);                               /* cannot attach semaphore, detach everything */
+       return(SHARED_BADARG);
+     }
+   shared_lt[idx].p = bp;                               /* store pointer to shmem data */
+   return(SHARED_OK);
+ }
+
+
+static  int     shared_validate(int idx, int mode)      /* use intrnally inside crit.sect !!! */
+ { int r;
+
+   if (SHARED_OK != (r = shared_mux(idx, mode)))  return(r);            /* idx checked by shared_mux */
+   if (NULL == shared_lt[idx].p)
+     if (SHARED_OK != (r = shared_map(idx)))
+       { shared_demux(idx, mode); 
+         return(r);
+       }
+   if ((SHARED_ID_0 != (shared_lt[idx].p)->s.ID[0]) || (SHARED_ID_1 != (shared_lt[idx].p)->s.ID[1]) || (BLOCK_SHARED != (shared_lt[idx].p)->s.tflag))
+     { shared_demux(idx, mode);
+       return(r);
+     }
+   return(SHARED_OK);
+ }
+
+
+SHARED_P shared_realloc(int idx, long newsize)  /* realloc shared memory segment */
+ { int h, key, i, r;
+   BLKHEAD *bp;
+   long transfersize;
+
+   r = SHARED_OK;
+   if (newsize < 0) return(NULL);
+   if (shared_check_locked_index(idx)) return(NULL);
+   if (0 == (shared_gt[idx].attr & SHARED_RESIZE)) return(NULL);
+   if (-1 != shared_lt[idx].lkcnt) return(NULL); /* check for RW lock */
+   if (shared_adjust_size(shared_gt[idx].size) == shared_adjust_size(newsize))
+     { shared_gt[idx].size = newsize;
+
+       return((SHARED_P)((shared_lt[idx].p) + 1));
+     }
+   for (i = 0; ; i++)
+    { if (i >= shared_range)  return(NULL);     /* table full, signal error & exit */
+      key = shared_kbase + ((i + shared_get_hash(newsize, idx)) % shared_range);
+      h = shmget(key, shared_adjust_size(newsize), IPC_CREAT | IPC_EXCL | shared_create_mode);
+      if (SHARED_INVALID == h) continue;        /* segment already accupied */
+      bp = (BLKHEAD *)shmat(h, 0, 0);           /* try attach */
+      if (((BLKHEAD *)SHARED_INVALID) == bp)    /* cannot attach, delete segment, try with another key */
+        { shmctl(h, IPC_RMID, 0);
+          continue;
+        }
+      *bp = *(shared_lt[idx].p);                /* copy header, then data */
+      transfersize = ((newsize < shared_gt[idx].size) ? newsize : shared_gt[idx].size);
+      if (transfersize > 0)
+        memcpy((void *)(bp + 1), (void *)((shared_lt[idx].p) + 1), transfersize);
+      if (shmdt((char *)(shared_lt[idx].p))) r = SHARED_IPCERR; /* try to detach old segment */
+      if (shmctl(shared_gt[idx].handle, IPC_RMID, 0)) if (SHARED_OK == r) r = SHARED_IPCERR;  /* destroy old shared memory segment */
+      shared_gt[idx].size = newsize;            /* signal new size */
+      shared_gt[idx].handle = h;                /* signal new handle */
+      shared_gt[idx].key = key;                 /* signal new key */
+      shared_lt[idx].p = bp;
+      break;
+    }
+   return((SHARED_P)(bp + 1));
+ }
+
+
+int     shared_free(int idx)                    /* detach segment, if last process & !PERSIST, destroy segment */
+ { int cnt, r, r2;
+
+   if (SHARED_OK != (r = shared_validate(idx, SHARED_RDWRITE | SHARED_WAIT))) return(r);
+   if (SHARED_OK != (r = shared_detach_process(shared_gt[idx].sem)))    /* update number of processes using segment */
+     { shared_demux(idx, SHARED_RDWRITE);
+       return(r);
+     }
+   shared_lt[idx].tcnt--;                       /* update number of threads using segment */
+   if (shared_lt[idx].tcnt > 0)  return(shared_demux(idx, SHARED_RDWRITE));  /* if more threads are using segment we are done */
+   if (shmdt((char *)(shared_lt[idx].p)))       /* if, we are the last thread, try to detach segment */
+     { shared_demux(idx, SHARED_RDWRITE);
+       return(SHARED_IPCERR);
+     }
+   shared_lt[idx].p = NULL;                     /* clear entry in local table */
+   shared_lt[idx].seekpos = 0L;                 /* r/w pointer positioned at beg of block */
+   if (-1 == (cnt = shared_process_count(shared_gt[idx].sem))) /* get number of processes hanging on segment */
+     { shared_demux(idx, SHARED_RDWRITE);
+       return(SHARED_IPCERR);
+     }
+   if ((0 == cnt) && (0 == (shared_gt[idx].attr & SHARED_PERSIST)))  r = shared_destroy_entry(idx); /* no procs on seg, destroy it */
+   r2 = shared_demux(idx, SHARED_RDWRITE);
+   return(r ? r : r2);
+ }
+
+
+SHARED_P shared_lock(int idx, int mode)         /* lock given segment for exclusive access */
+ { int r;
+
+   if (shared_mux(idx, mode))  return(NULL);    /* idx checked by shared_mux */
+   if (0 != shared_lt[idx].lkcnt)               /* are we already locked ?? */
+     if (SHARED_OK != (r = shared_map(idx)))
+       { shared_demux(idx, mode); 
+         return(NULL);
+       }
+   if (NULL == shared_lt[idx].p)                /* stupid pointer ?? */
+     if (SHARED_OK != (r = shared_map(idx)))
+       { shared_demux(idx, mode); 
+         return(NULL);
+       }
+   if ((SHARED_ID_0 != (shared_lt[idx].p)->s.ID[0]) || (SHARED_ID_1 != (shared_lt[idx].p)->s.ID[1]) || (BLOCK_SHARED != (shared_lt[idx].p)->s.tflag))
+     { shared_demux(idx, mode);
+       return(NULL);
+     }
+   if (mode & SHARED_RDWRITE)
+     { shared_lt[idx].lkcnt = -1;
+
+       shared_gt[idx].nprocdebug++;
+     }
+
+   else shared_lt[idx].lkcnt++;
+   shared_lt[idx].seekpos = 0L;                 /* r/w pointer positioned at beg of block */
+   return((SHARED_P)((shared_lt[idx].p) + 1));
+ }
+
+
+int     shared_unlock(int idx)                  /* unlock given segment, assumes seg is locked !! */
+ { int r, r2, mode;
+
+   if (SHARED_OK != (r = shared_check_locked_index(idx))) return(r);
+   if (shared_lt[idx].lkcnt > 0)
+     { shared_lt[idx].lkcnt--;                  /* unlock read lock */
+       mode = SHARED_RDONLY;
+     }
+   else
+     { shared_lt[idx].lkcnt = 0;                /* unlock write lock */
+       shared_gt[idx].nprocdebug--;
+       mode = SHARED_RDWRITE;
+     }
+   if (0 == shared_lt[idx].lkcnt) if (shared_gt[idx].attr & SHARED_RESIZE)
+     { if (shmdt((char *)(shared_lt[idx].p))) r = SHARED_IPCERR; /* segment is resizable, then detach segment */
+       shared_lt[idx].p = NULL;                 /* signal detachment in local table */
+     }
+   r2 = shared_demux(idx, mode);                /* unlock segment, rest is only parameter checking */
+   return(r ? r : r2);
+ }
+
+                /* API routines - support and info routines */
+
+
+int     shared_attr(int idx)                    /* get the attributes of the shared memory segment */
+ { int r;
+
+   if (shared_check_locked_index(idx)) return(SHARED_INVALID);
+   r = shared_gt[idx].attr;
+   return(r);
+ }
+
+
+int     shared_set_attr(int idx, int newattr)   /* get the attributes of the shared memory segment */
+ { int r;
+
+   if (shared_check_locked_index(idx)) return(SHARED_INVALID);
+   if (-1 != shared_lt[idx].lkcnt) return(SHARED_INVALID); /* ADDED - check for RW lock */
+   r = shared_gt[idx].attr;
+   shared_gt[idx].attr = newattr;
+   return(r);
+
+ }
+
+
+int     shared_set_debug(int mode)              /* set/reset debug mode */
+ { int r = shared_debug;
+
+   shared_debug = mode;
+   return(r);
+ }
+
+
+int     shared_set_createmode(int mode)          /* set/reset debug mode */
+ { int r = shared_create_mode;
+
+   shared_create_mode = mode;
+   return(r);
+ }
+
+
+
+
+int     shared_list(int id)
+ { int i, r;
+
+   if (NULL == shared_gt) return(SHARED_NOTINIT);       /* not initialized */
+   if (NULL == shared_lt) return(SHARED_NOTINIT);       /* not initialized */
+   if (shared_debug) printf("shared_list:");
+   r = SHARED_OK;
+   printf(" Idx    Key   Nproc   Size   Flags\n");
+   printf("==============================================\n");
+   for (i=0; i<shared_maxseg; i++)
+    { if (-1 != id) if (i != id) continue;
+      if (SHARED_INVALID == shared_gt[i].key) continue; /* unused slot */
+      switch (shared_mux(i, SHARED_NOWAIT | SHARED_RDONLY)) /* acquire exclusive access to segment, but do not wait */
+
+       { case SHARED_AGAIN:
+                printf("!%3d %08lx %4d  %8d", i, (unsigned long int)shared_gt[i].key,
+                                shared_gt[i].nprocdebug, shared_gt[i].size);
+                if (SHARED_RESIZE & shared_gt[i].attr) printf(" RESIZABLE");
+                if (SHARED_PERSIST & shared_gt[i].attr) printf(" PERSIST");
+                printf("\n");
+                break;
+         case SHARED_OK:
+                printf(" %3d %08lx %4d  %8d", i, (unsigned long int)shared_gt[i].key,
+
+                                shared_gt[i].nprocdebug, shared_gt[i].size);
+                if (SHARED_RESIZE & shared_gt[i].attr) printf(" RESIZABLE");
+                if (SHARED_PERSIST & shared_gt[i].attr) printf(" PERSIST");
+                printf("\n");
+                shared_demux(i, SHARED_RDONLY);
+                break;
+         default:
+                continue;
+       }
+    }
+   if (shared_debug) printf(" done\n");
+   return(r);                                           /* table full */
+ }
+
+int     shared_getaddr(int id, char **address)
+ { int i;
+   char segname[10];
+
+   if (NULL == shared_gt) return(SHARED_NOTINIT);       /* not initialized */
+   if (NULL == shared_lt) return(SHARED_NOTINIT);       /* not initialized */
+ 
+   strcpy(segname,"h");
+   sprintf(segname+1,"%d", id);
+ 
+   if (smem_open(segname,0,&i)) return(SHARED_BADARG);
+ 
+   *address = ((char *)(((DAL_SHM_SEGHEAD *)(shared_lt[i].p + 1)) + 1));
+ /*  smem_close(i); */
+   return(SHARED_OK);
+ }
+
+
+int     shared_uncond_delete(int id)
+ { int i, r;
+
+   if (NULL == shared_gt) return(SHARED_NOTINIT);       /* not initialized */
+   if (NULL == shared_lt) return(SHARED_NOTINIT);       /* not initialized */
+   if (shared_debug) printf("shared_uncond_delete:");
+   r = SHARED_OK;
+   for (i=0; i<shared_maxseg; i++)
+    { if (-1 != id) if (i != id) continue;
+      if (shared_attach(i))
+        { if (-1 != id) printf("no such handle\n");
+          continue;
+        }
+      printf("handle %d:", i);
+      if (NULL == shared_lock(i, SHARED_RDWRITE | SHARED_NOWAIT)) 
+        { printf(" cannot lock in RW mode, not deleted\n");
+          continue;
+        }
+      if (shared_set_attr(i, SHARED_RESIZE) >= SHARED_ERRBASE)
+        { printf(" cannot clear PERSIST attribute");
+        }
+      if (shared_free(i))
+        { printf(" delete failed\n");
+        }
+      else
+        { printf(" deleted\n");
+        }
+    }
+   if (shared_debug) printf(" done\n");
+   return(r);                                           /* table full */
+ }
+
+
+/************************* CFITSIO DRIVER FUNCTIONS ***************************/
+
+int     smem_init(void)
+ { return(0);
+ }
+
+int     smem_shutdown(void)
+
+ { if (shared_init_called) shared_cleanup();
+   return(0);
+ }
+
+int     smem_setoptions(int option)
+ { option = 0;
+   return(0);
+ }
+
+
+int     smem_getoptions(int *options)
+ { if (NULL == options) return(SHARED_NULPTR);
+   *options = 0;
+   return(0);
+ }
+
+int     smem_getversion(int *version)
+ { if (NULL == version) return(SHARED_NULPTR);
+   *version = 10;
+   return(0);
+ }
+
+
+int     smem_open(char *filename, int rwmode, int *driverhandle)
+ { int h, nitems, r;
+   DAL_SHM_SEGHEAD *sp;
+
+
+   if (NULL == filename) return(SHARED_NULPTR);
+   if (NULL == driverhandle) return(SHARED_NULPTR);
+   nitems = sscanf(filename, "h%d", &h);
+   if (1 != nitems) return(SHARED_BADARG);
+
+   if (SHARED_OK != (r = shared_attach(h))) return(r);
+
+   if (NULL == (sp = (DAL_SHM_SEGHEAD *)shared_lock(h,
+                ((READWRITE == rwmode) ? SHARED_RDWRITE : SHARED_RDONLY))))
+     {  shared_free(h);
+        return(SHARED_BADARG);
+     }
+
+   if ((h != sp->h) || (DAL_SHM_SEGHEAD_ID != sp->ID))
+     { shared_unlock(h);
+       shared_free(h);
+
+       return(SHARED_BADARG);
+     }
+
+   *driverhandle = h;
+   return(0);
+ }
+
+
+int     smem_create(char *filename, int *driverhandle)
+ { DAL_SHM_SEGHEAD *sp;
+   int h, sz, nitems;
+
+   if (NULL == filename) return(SHARED_NULPTR);         /* currently ignored */
+   if (NULL == driverhandle) return(SHARED_NULPTR);
+   nitems = sscanf(filename, "h%d", &h);
+   if (1 != nitems) return(SHARED_BADARG);
+
+   if (SHARED_INVALID == (h = shared_malloc(sz = 2880 + sizeof(DAL_SHM_SEGHEAD), 
+                        SHARED_RESIZE | SHARED_PERSIST, h)))
+     return(SHARED_NOMEM);
+
+   if (NULL == (sp = (DAL_SHM_SEGHEAD *)shared_lock(h, SHARED_RDWRITE)))
+     { shared_free(h);
+       return(SHARED_BADARG);
+     }
+
+   sp->ID = DAL_SHM_SEGHEAD_ID;
+   sp->h = h;
+   sp->size = sz;
+   sp->nodeidx = -1;
+
+   *driverhandle = h;
+   
+   return(0);
+ }
+
+
+int     smem_close(int driverhandle)
+ { int r;
+
+   if (SHARED_OK != (r = shared_unlock(driverhandle))) return(r);
+   return(shared_free(driverhandle));
+ }
+
+int     smem_remove(char *filename)
+ { int nitems, h, r;
+
+   if (NULL == filename) return(SHARED_NULPTR);
+   nitems = sscanf(filename, "h%d", &h);
+   if (1 != nitems) return(SHARED_BADARG);
+
+   if (0 == shared_check_locked_index(h))       /* are we locked ? */
+
+     { if (-1 != shared_lt[h].lkcnt)            /* are we locked RO ? */
+         { if (SHARED_OK != (r = shared_unlock(h))) return(r);  /* yes, so relock in RW */
+           if (NULL == shared_lock(h, SHARED_RDWRITE)) return(SHARED_BADARG);
+         }
+
+     }
+   else                                         /* not locked */
+     { if (SHARED_OK != (r = smem_open(filename, READWRITE, &h)))
+         return(r);                             /* so open in RW mode */
+     }
+
+   shared_set_attr(h, SHARED_RESIZE);           /* delete PERSIST attribute */
+   return(smem_close(h));                       /* detach segment (this will delete it) */
+ }
+
+int     smem_size(int driverhandle, LONGLONG *size)
+ {
+   if (NULL == size) return(SHARED_NULPTR);
+   if (shared_check_locked_index(driverhandle)) return(SHARED_INVALID);
+   *size = (LONGLONG) (shared_gt[driverhandle].size - sizeof(DAL_SHM_SEGHEAD));
+   return(0);
+ }
+
+int     smem_flush(int driverhandle)
+ {
+   if (shared_check_locked_index(driverhandle)) return(SHARED_INVALID);
+   return(0);
+ }
+
+int     smem_seek(int driverhandle, LONGLONG offset)
+ {
+   if (offset < 0) return(SHARED_BADARG);
+   if (shared_check_locked_index(driverhandle)) return(SHARED_INVALID);
+   shared_lt[driverhandle].seekpos = offset;
+   return(0);
+ }
+
+int     smem_read(int driverhandle, void *buffer, long nbytes)
+ {
+   if (NULL == buffer) return(SHARED_NULPTR);
+   if (shared_check_locked_index(driverhandle)) return(SHARED_INVALID);
+   if (nbytes < 0) return(SHARED_BADARG);
+   if ((shared_lt[driverhandle].seekpos + nbytes) > shared_gt[driverhandle].size)
+     return(SHARED_BADARG);             /* read beyond EOF */
+
+   memcpy(buffer,
+          ((char *)(((DAL_SHM_SEGHEAD *)(shared_lt[driverhandle].p + 1)) + 1)) +
+                shared_lt[driverhandle].seekpos,
+          nbytes);
+
+   shared_lt[driverhandle].seekpos += nbytes;
+   return(0);
+ }
+
+int     smem_write(int driverhandle, void *buffer, long nbytes)
+ {
+   if (NULL == buffer) return(SHARED_NULPTR);
+   if (shared_check_locked_index(driverhandle)) return(SHARED_INVALID);
+   if (-1 != shared_lt[driverhandle].lkcnt) return(SHARED_INVALID); /* are we locked RW ? */
+
+   if (nbytes < 0) return(SHARED_BADARG);
+   if ((unsigned long)(shared_lt[driverhandle].seekpos + nbytes) > (unsigned long)(shared_gt[driverhandle].size - sizeof(DAL_SHM_SEGHEAD)))
+     {                  /* need to realloc shmem */
+       if (NULL == shared_realloc(driverhandle, shared_lt[driverhandle].seekpos + nbytes + sizeof(DAL_SHM_SEGHEAD)))
+         return(SHARED_NOMEM);
+     }
+
+   memcpy(((char *)(((DAL_SHM_SEGHEAD *)(shared_lt[driverhandle].p + 1)) + 1)) +
+                shared_lt[driverhandle].seekpos,
+          buffer,
+          nbytes);
+
+   shared_lt[driverhandle].seekpos += nbytes;
+   return(0);
+ }
+#endif
diff --git a/src/plugins/cfitsio/drvrsmem.h b/src/plugins/cfitsio/drvrsmem.h
new file mode 100644
index 0000000..52ac7d7
--- /dev/null
+++ b/src/plugins/cfitsio/drvrsmem.h
@@ -0,0 +1,179 @@
+/*		S H A R E D   M E M O R Y   D R I V E R
+		=======================================
+
+		  by Jerzy Borkowski obs unige ch
+
+09-Mar-98 : initial version 1.0 released
+23-Mar-98 : shared_malloc now accepts new handle as an argument
+*/
+
+
+#include <sys/ipc.h>		/* this is necessary for Solaris/Linux */
+#include <sys/shm.h>
+#include <sys/sem.h>
+
+#ifdef _AIX
+#include <fcntl.h>
+#else
+#include <sys/fcntl.h>
+#endif
+
+		/* configuration parameters */
+
+#define	SHARED_MAXSEG	(16)		/* maximum number of shared memory blocks */
+
+#define	SHARED_KEYBASE	(14011963)	/* base for shared memory keys, may be overriden by getenv */
+#define	SHARED_FDNAME	("/tmp/.shmem-lockfile") /* template for lock file name */
+
+#define	SHARED_ENV_KEYBASE ("SHMEM_LIB_KEYBASE") /* name of environment variable */
+#define	SHARED_ENV_MAXSEG ("SHMEM_LIB_MAXSEG")	/* name of environment variable */
+
+		/* useful constants */
+
+#define	SHARED_RDONLY	(0)		/* flag for shared_(un)lock, lock for read */
+#define	SHARED_RDWRITE	(1)		/* flag for shared_(un)lock, lock for write */
+#define	SHARED_WAIT	(0)		/* flag for shared_lock, block if cannot lock immediate */
+#define	SHARED_NOWAIT	(2)		/* flag for shared_lock, fail if cannot lock immediate */
+#define	SHARED_NOLOCK	(0x100)		/* flag for shared_validate function */
+
+#define	SHARED_RESIZE	(4)		/* flag for shared_malloc, object is resizeable */
+#define	SHARED_PERSIST	(8)		/* flag for shared_malloc, object is not deleted after last proc detaches */
+
+#define	SHARED_INVALID	(-1)		/* invalid handle for semaphore/shared memory */
+
+#define	SHARED_EMPTY	(0)		/* entries for shared_used table */
+#define	SHARED_USED	(1)
+
+#define	SHARED_GRANUL	(16384)		/* granularity of shared_malloc allocation = phys page size, system dependent */
+
+
+
+		/* checkpoints in shared memory segments - might be omitted */
+
+#define	SHARED_ID_0	('J')		/* first byte of identifier in BLKHEAD */
+#define	SHARED_ID_1	('B')		/* second byte of identifier in BLKHEAD */
+
+#define	BLOCK_REG	(0)		/* value for tflag member of BLKHEAD */
+#define	BLOCK_SHARED	(1)		/* value for tflag member of BLKHEAD */
+
+		/* generic error codes */
+
+#define	SHARED_OK	(0)
+
+#define	SHARED_ERR_MIN_IDX	SHARED_BADARG
+#define	SHARED_ERR_MAX_IDX	SHARED_NORESIZE
+
+
+#define	DAL_SHM_FREE	(0)
+#define	DAL_SHM_USED	(1)
+
+#define	DAL_SHM_ID0	('D')
+#define	DAL_SHM_ID1	('S')
+#define	DAL_SHM_ID2	('M')
+
+#define	DAL_SHM_SEGHEAD_ID	(0x19630114)
+
+
+
+		/* data types */
+
+/* BLKHEAD object is placed at the beginning of every memory segment (both
+  shared and regular) to allow automatic recognition of segments type */
+
+typedef union
+      { struct BLKHEADstruct
+	      {	char	ID[2];		/* ID = 'JB', just as a checkpoint */
+		char	tflag;		/* is it shared memory or regular one ? */
+		int	handle;		/* this is not necessary, used only for non-resizeable objects via ptr */
+	      } s;
+	double	d;			/* for proper alignment on every machine */
+      } BLKHEAD;
+
+typedef void *SHARED_P;			/* generic type of shared memory pointer */
+
+typedef	struct SHARED_GTABstruct	/* data type used in global table */
+      {	int	sem;			/* access semaphore (1 field): process count */
+	int	semkey;			/* key value used to generate semaphore handle */
+	int	key;			/* key value used to generate shared memory handle (realloc changes it) */
+	int	handle;			/* handle of shared memory segment */
+	int	size;			/* size of shared memory segment */
+	int	nprocdebug;		/* attached proc counter, helps remove zombie segments */
+	char	attr;			/* attributes of shared memory object */
+      } SHARED_GTAB;
+
+typedef	struct SHARED_LTABstruct	/* data type used in local table */
+      {	BLKHEAD	*p;			/* pointer to segment (may be null) */
+	int	tcnt;			/* number of threads in this process attached to segment */
+	int	lkcnt;			/* >=0 <- number of read locks, -1 - write lock */
+	long	seekpos;		/* current pointer position, read/write/seek operations change it */
+      } SHARED_LTAB;
+
+
+	/* system dependent definitions */
+
+#ifndef HAVE_FLOCK_T
+typedef struct flock flock_t;
+#define HAVE_FLOCK_T
+#endif
+
+#ifndef HAVE_UNION_SEMUN
+union semun
+      {	int val;
+	struct semid_ds *buf;
+	unsigned short *array;
+      };
+#define HAVE_UNION_SEMUN
+#endif
+
+
+typedef struct DAL_SHM_SEGHEAD_STRUCT	DAL_SHM_SEGHEAD;
+
+struct DAL_SHM_SEGHEAD_STRUCT
+      {	int	ID;			/* ID for debugging */
+	int	h;			/* handle of sh. mem */
+	int	size;			/* size of data area */
+	int	nodeidx;		/* offset of root object (node struct typically) */
+      };
+
+		/* API routines */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void	shared_cleanup(void);			/* must be called at exit/abort */
+int	shared_init(int debug_msgs);		/* must be called before any other shared memory routine */
+int	shared_recover(int id);			/* try to recover dormant segment(s) after applic crash */
+int	shared_malloc(long size, int mode, int newhandle);	/* allocate n-bytes of shared memory */
+int	shared_attach(int idx);			/* attach to segment given index to table */
+int	shared_free(int idx);			/* release shared memory */
+SHARED_P shared_lock(int idx, int mode);	/* lock segment for reading */
+SHARED_P shared_realloc(int idx, long newsize);	/* reallocate n-bytes of shared memory (ON LOCKED SEGMENT ONLY) */
+int	shared_size(int idx);			/* get size of attached shared memory segment (ON LOCKED SEGMENT ONLY) */
+int	shared_attr(int idx);			/* get attributes of attached shared memory segment (ON LOCKED SEGMENT ONLY) */
+int	shared_set_attr(int idx, int newattr);	/* set attributes of attached shared memory segment (ON LOCKED SEGMENT ONLY) */
+int	shared_unlock(int idx);			/* unlock segment (ON LOCKED SEGMENT ONLY) */
+int	shared_set_debug(int debug_msgs);	/* set/reset debug mode */
+int	shared_set_createmode(int mode);	/* set/reset debug mode */
+int	shared_list(int id);			/* list segment(s) */
+int	shared_uncond_delete(int id);		/* uncondintionally delete (NOWAIT operation) segment(s) */
+int	shared_getaddr(int id, char **address);	/* get starting address of FITS file in segment */
+
+int	smem_init(void);
+int	smem_shutdown(void);
+int	smem_setoptions(int options);
+int	smem_getoptions(int *options);
+int	smem_getversion(int *version);
+int	smem_open(char *filename, int rwmode, int *driverhandle);
+int	smem_create(char *filename, int *driverhandle);
+int	smem_close(int driverhandle);
+int	smem_remove(char *filename);
+int	smem_size(int driverhandle, LONGLONG *size);
+int	smem_flush(int driverhandle);
+int	smem_seek(int driverhandle, LONGLONG offset);
+int	smem_read(int driverhandle, void *buffer, long nbytes);
+int	smem_write(int driverhandle, void *buffer, long nbytes);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/src/plugins/cfitsio/editcol.c b/src/plugins/cfitsio/editcol.c
new file mode 100644
index 0000000..dc82f02
--- /dev/null
+++ b/src/plugins/cfitsio/editcol.c
@@ -0,0 +1,2474 @@
+/*  This file, editcol.c, contains the set of FITSIO routines that    */
+/*  insert or delete rows or columns in a table or resize an image    */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include "fitsio2.h"
+/*--------------------------------------------------------------------------*/
+int ffrsim(fitsfile *fptr,      /* I - FITS file pointer           */
+           int bitpix,          /* I - bits per pixel              */
+           int naxis,           /* I - number of axes in the array */
+           long *naxes,         /* I - size of each axis           */
+           int *status)         /* IO - error status               */
+/*
+   resize an existing primary array or IMAGE extension.
+*/
+{
+    LONGLONG tnaxes[99];
+    int ii;
+    
+    if (*status > 0)
+        return(*status);
+
+    for (ii = 0; (ii < naxis) && (ii < 99); ii++)
+        tnaxes[ii] = naxes[ii];
+
+    ffrsimll(fptr, bitpix, naxis, tnaxes, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffrsimll(fitsfile *fptr,    /* I - FITS file pointer           */
+           int bitpix,          /* I - bits per pixel              */
+           int naxis,           /* I - number of axes in the array */
+           LONGLONG *naxes,     /* I - size of each axis           */
+           int *status)         /* IO - error status               */
+/*
+   resize an existing primary array or IMAGE extension.
+*/
+{
+    int ii, simple, obitpix, onaxis, extend, nmodify;
+    long  nblocks, longval;
+    long pcount, gcount, longbitpix;
+    LONGLONG onaxes[99], newsize, oldsize;
+    char comment[FLEN_COMMENT], keyname[FLEN_KEYWORD], message[FLEN_ERRMSG];
+
+    if (*status > 0)
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+    {
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    }
+         /* rescan header if data structure is undefined */
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+        if ( ffrdef(fptr, status) > 0)               
+            return(*status);
+
+    /* get current image size parameters */
+    if (ffghprll(fptr, 99, &simple, &obitpix, &onaxis, onaxes, &pcount,
+               &gcount, &extend, status) > 0)
+        return(*status);
+
+    longbitpix = bitpix;
+
+    /* test for the 2 special cases that represent unsigned integers */
+    if (longbitpix == USHORT_IMG)
+        longbitpix = SHORT_IMG;
+    else if (longbitpix == ULONG_IMG)
+        longbitpix = LONG_IMG;
+
+    /* test that the new values are legal */
+
+    if (longbitpix != BYTE_IMG && longbitpix != SHORT_IMG && 
+        longbitpix != LONG_IMG && longbitpix != LONGLONG_IMG &&
+        longbitpix != FLOAT_IMG && longbitpix != DOUBLE_IMG)
+    {
+        sprintf(message,
+        "Illegal value for BITPIX keyword: %d", bitpix);
+        ffpmsg(message);
+        return(*status = BAD_BITPIX);
+    }
+
+    if (naxis < 0 || naxis > 999)
+    {
+        sprintf(message,
+        "Illegal value for NAXIS keyword: %d", naxis);
+        ffpmsg(message);
+        return(*status = BAD_NAXIS);
+    }
+
+    if (naxis == 0)
+        newsize = 0;
+    else
+        newsize = 1;
+
+    for (ii = 0; ii < naxis; ii++)
+    {
+        if (naxes[ii] < 0)
+        {
+            sprintf(message,
+            "Illegal value for NAXIS%d keyword: %.0f", ii + 1,  (double) (naxes[ii]));
+            ffpmsg(message);
+            return(*status = BAD_NAXES);
+        }
+
+        newsize *= naxes[ii];  /* compute new image size, in pixels */
+    }
+
+    /* compute size of old image, in bytes */
+
+    if (onaxis == 0)
+        oldsize = 0;
+    else
+    {
+        oldsize = 1;
+        for (ii = 0; ii < onaxis; ii++)
+            oldsize *= onaxes[ii];  
+        oldsize = (oldsize + pcount) * gcount * (abs(obitpix) / 8);
+    }
+
+    oldsize = (oldsize + 2879) / 2880; /* old size, in blocks */
+
+    newsize = (newsize + pcount) * gcount * (abs(longbitpix) / 8);
+    newsize = (newsize + 2879) / 2880; /* new size, in blocks */
+
+    if (newsize > oldsize)   /* have to insert new blocks for image */
+    {
+        nblocks = (long) (newsize - oldsize);
+        if (ffiblk(fptr, nblocks, 1, status) > 0)  
+            return(*status);
+    }
+    else if (oldsize > newsize)  /* have to delete blocks from image */
+    {
+        nblocks = (long) (oldsize - newsize);
+        if (ffdblk(fptr, nblocks, status) > 0)  
+            return(*status);
+    }
+
+    /* now update the header keywords */
+
+    strcpy(comment,"&");  /* special value to leave comments unchanged */
+
+    if (longbitpix != obitpix)
+    {                         /* update BITPIX value */
+        ffmkyj(fptr, "BITPIX", longbitpix, comment, status);
+    }
+
+    if (naxis != onaxis)
+    {                        /* update NAXIS value */
+        longval = naxis;
+        ffmkyj(fptr, "NAXIS", longval, comment, status);
+    }
+
+    /* modify the existing NAXISn keywords */
+    nmodify = minvalue(naxis, onaxis); 
+    for (ii = 0; ii < nmodify; ii++)
+    {
+        ffkeyn("NAXIS", ii+1, keyname, status);
+        ffmkyj(fptr, keyname, naxes[ii], comment, status);
+    }
+
+    if (naxis > onaxis)  /* insert additional NAXISn keywords */
+    {
+        strcpy(comment,"length of data axis");  
+        for (ii = onaxis; ii < naxis; ii++)
+        {
+            ffkeyn("NAXIS", ii+1, keyname, status);
+            ffikyj(fptr, keyname, naxes[ii], comment, status);
+        }
+    }
+    else if (onaxis > naxis) /* delete old NAXISn keywords */
+    {
+        for (ii = naxis; ii < onaxis; ii++)
+        {
+            ffkeyn("NAXIS", ii+1, keyname, status);
+            ffdkey(fptr, keyname, status);
+        }
+    }
+
+    /* Update the BSCALE and BZERO keywords, if an unsigned integer image */
+    if (bitpix == USHORT_IMG)
+    {
+        strcpy(comment, "offset data range to that of unsigned short");
+        ffukyg(fptr, "BZERO", 32768., 0, comment, status);
+        strcpy(comment, "default scaling factor");
+        ffukyg(fptr, "BSCALE", 1.0, 0, comment, status);
+    }
+    else if (bitpix == ULONG_IMG)
+    {
+        strcpy(comment, "offset data range to that of unsigned long");
+        ffukyg(fptr, "BZERO", 2147483648., 0, comment, status);
+        strcpy(comment, "default scaling factor");
+        ffukyg(fptr, "BSCALE", 1.0, 0, comment, status);
+    }
+
+    /* re-read the header, to make sure structures are updated */
+    ffrdef(fptr, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffirow(fitsfile *fptr,  /* I - FITS file pointer                        */
+           LONGLONG firstrow,   /* I - insert space AFTER this row              */
+                            /*     0 = insert space at beginning of table   */
+           LONGLONG nrows,      /* I - number of rows to insert                 */
+           int *status)     /* IO - error status                            */
+/*
+ insert NROWS blank rows immediated after row firstrow (1 = first row).
+ Set firstrow = 0 to insert space at the beginning of the table.
+*/
+{
+    int tstatus;
+    LONGLONG naxis1, naxis2;
+    LONGLONG datasize, firstbyte, nshift, nbytes;
+    LONGLONG freespace;
+    long nblock;
+
+    if (*status > 0)
+        return(*status);
+
+        /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+    {
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    }
+         /* rescan header if data structure is undefined */
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+        if ( ffrdef(fptr, status) > 0)               
+            return(*status);
+
+    if ((fptr->Fptr)->hdutype == IMAGE_HDU)
+    {
+        ffpmsg("Can only add rows to TABLE or BINTABLE extension (ffirow)");
+        return(*status = NOT_TABLE);
+    }
+
+    if (nrows < 0 )
+        return(*status = NEG_BYTES);
+    else if (nrows == 0)
+        return(*status);   /* no op, so just return */
+
+    /* get the current size of the table */
+    /* use internal structure since NAXIS2 keyword may not be up to date */
+    naxis1 = (fptr->Fptr)->rowlength;
+    naxis2 = (fptr->Fptr)->numrows;
+
+    if (firstrow > naxis2)
+    {
+        ffpmsg(
+   "Insert position greater than the number of rows in the table (ffirow)");
+        return(*status = BAD_ROW_NUM);
+    }
+    else if (firstrow < 0)
+    {
+        ffpmsg("Insert position is less than 0 (ffirow)");
+        return(*status = BAD_ROW_NUM);
+    }
+
+    /* current data size */
+    datasize = (fptr->Fptr)->heapstart + (fptr->Fptr)->heapsize;
+    freespace = ( ( (datasize + 2879) / 2880) * 2880) - datasize;
+    nshift = naxis1 * nrows;          /* no. of bytes to add to table */
+
+    if ( (freespace - nshift) < 0)   /* not enough existing space? */
+    {
+        nblock = (long) ((nshift - freespace + 2879) / 2880);   /* number of blocks */
+        ffiblk(fptr, nblock, 1, status);               /* insert the blocks */
+    }
+
+    firstbyte = naxis1 * firstrow;    /* relative insert position */
+    nbytes = datasize - firstbyte;           /* no. of bytes to shift down */
+    firstbyte += ((fptr->Fptr)->datastart);  /* absolute insert position */
+
+    ffshft(fptr, firstbyte, nbytes, nshift, status); /* shift rows and heap */
+
+    /* update the heap starting address */
+    (fptr->Fptr)->heapstart += nshift;
+
+    /* update the THEAP keyword if it exists */
+    tstatus = 0;
+    ffmkyj(fptr, "THEAP", (fptr->Fptr)->heapstart, "&", &tstatus);
+
+    /* update the NAXIS2 keyword */
+    ffmkyj(fptr, "NAXIS2", naxis2 + nrows, "&", status);
+    ((fptr->Fptr)->numrows) += nrows;
+    ((fptr->Fptr)->origrows) += nrows;
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffdrow(fitsfile *fptr,  /* I - FITS file pointer                        */
+           LONGLONG firstrow,   /* I - first row to delete (1 = first)          */
+           LONGLONG nrows,      /* I - number of rows to delete                 */
+           int *status)     /* IO - error status                            */
+/*
+ delete NROWS rows from table starting with firstrow (1 = first row of table).
+*/
+{
+    int tstatus;
+    LONGLONG naxis1, naxis2;
+    LONGLONG datasize, firstbyte, nbytes, nshift;
+    LONGLONG freespace;
+    long nblock;
+    char comm[FLEN_COMMENT];
+
+    if (*status > 0)
+        return(*status);
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+    {
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    }
+        /* rescan header if data structure is undefined */
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+        if ( ffrdef(fptr, status) > 0)               
+            return(*status);
+
+    if ((fptr->Fptr)->hdutype == IMAGE_HDU)
+    {
+        ffpmsg("Can only delete rows in TABLE or BINTABLE extension (ffdrow)");
+        return(*status = NOT_TABLE);
+    }
+
+    if (nrows < 0 )
+        return(*status = NEG_BYTES);
+    else if (nrows == 0)
+        return(*status);   /* no op, so just return */
+
+    ffgkyjj(fptr, "NAXIS1", &naxis1, comm, status); /* get the current   */
+
+   /* ffgkyj(fptr, "NAXIS2", &naxis2, comm, status);*/ /* size of the table */
+
+    /* the NAXIS2 keyword may not be up to date, so use the structure value */
+    naxis2 = (fptr->Fptr)->numrows;
+
+    if (firstrow > naxis2)
+    {
+        ffpmsg(
+   "Delete position greater than the number of rows in the table (ffdrow)");
+        return(*status = BAD_ROW_NUM);
+    }
+    else if (firstrow < 1)
+    {
+        ffpmsg("Delete position is less than 1 (ffdrow)");
+        return(*status = BAD_ROW_NUM);
+    }
+    else if (firstrow + nrows - 1 > naxis2)
+    {
+        ffpmsg("No. of rows to delete exceeds size of table (ffdrow)");
+        return(*status = BAD_ROW_NUM);
+    }
+
+    nshift = naxis1 * nrows;   /* no. of bytes to delete from table */
+    /* cur size of data */
+    datasize = (fptr->Fptr)->heapstart + (fptr->Fptr)->heapsize;
+
+    firstbyte = naxis1 * (firstrow + nrows - 1); /* relative del pos */
+    nbytes = datasize - firstbyte;    /* no. of bytes to shift up */
+    firstbyte += ((fptr->Fptr)->datastart);   /* absolute delete position */
+
+    ffshft(fptr, firstbyte, nbytes,  nshift * (-1), status); /* shift data */
+
+    freespace = ( ( (datasize + 2879) / 2880) * 2880) - datasize;
+    nblock = (long) ((nshift + freespace) / 2880);   /* number of blocks */
+
+    /* delete integral number blocks */
+    if (nblock > 0) 
+        ffdblk(fptr, nblock, status);
+
+    /* update the heap starting address */
+    (fptr->Fptr)->heapstart -= nshift;
+
+    /* update the THEAP keyword if it exists */
+    tstatus = 0;
+    ffmkyj(fptr, "THEAP", (long)(fptr->Fptr)->heapstart, "&", &tstatus);
+
+    /* update the NAXIS2 keyword */
+    ffmkyj(fptr, "NAXIS2", naxis2 - nrows, "&", status);
+    ((fptr->Fptr)->numrows) -= nrows;
+    ((fptr->Fptr)->origrows) -= nrows;
+
+    /* Update the heap data, if any.  This will remove any orphaned data */
+    /* that was only pointed to by the rows that have been deleted */
+    ffcmph(fptr, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffdrrg(fitsfile *fptr,  /* I - FITS file pointer to table               */
+           char *ranges,    /* I - ranges of rows to delete (1 = first)     */
+           int *status)     /* IO - error status                            */
+/*
+ delete the ranges of rows from the table (1 = first row of table).
+
+The 'ranges' parameter typically looks like:
+    '10-20, 30 - 40, 55' or '50-'
+and gives a list of rows or row ranges separated by commas.
+*/
+{
+    char *cptr;
+    int nranges, nranges2, ii;
+    long *minrow, *maxrow, nrows, *rowarray, jj, kk;
+    LONGLONG naxis2;
+    
+    if (*status > 0)
+        return(*status);
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+    {
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    }
+        /* rescan header if data structure is undefined */
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+        if ( ffrdef(fptr, status) > 0)               
+            return(*status);
+
+    if ((fptr->Fptr)->hdutype == IMAGE_HDU)
+    {
+        ffpmsg("Can only delete rows in TABLE or BINTABLE extension (ffdrrg)");
+        return(*status = NOT_TABLE);
+    }
+
+    /* the NAXIS2 keyword may not be up to date, so use the structure value */
+    naxis2 = (fptr->Fptr)->numrows;
+
+    /* find how many ranges were specified ( = no. of commas in string + 1) */
+    cptr = ranges;
+    for (nranges = 1; (cptr = strchr(cptr, ',')); nranges++)
+        cptr++;
+ 
+    minrow = calloc(nranges, sizeof(long));
+    maxrow = calloc(nranges, sizeof(long));
+
+    if (!minrow || !maxrow) {
+        *status = MEMORY_ALLOCATION;
+        ffpmsg("failed to allocate memory for row ranges (ffdrrg)");
+        if (maxrow) free(maxrow);
+        if (minrow) free(minrow);
+        return(*status);
+    }
+
+    /* parse range list into array of range min and max values */
+    ffrwrg(ranges, naxis2, nranges, &nranges2, minrow, maxrow, status);
+    if (*status > 0 || nranges2 == 0) {
+        free(maxrow);
+        free(minrow);
+        return(*status);
+    }
+
+    /* determine total number or rows to delete */
+    nrows = 0;
+    for (ii = 0; ii < nranges2; ii++) {
+       nrows = nrows + maxrow[ii] - minrow[ii] + 1;
+    }
+
+    rowarray = calloc(nrows, sizeof(long));
+    if (!rowarray) {
+        *status = MEMORY_ALLOCATION;
+        ffpmsg("failed to allocate memory for row array (ffdrrg)");
+        return(*status);
+    }
+
+    for (kk = 0, ii = 0; ii < nranges2; ii++) {
+       for (jj = minrow[ii]; jj <= maxrow[ii]; jj++) {
+           rowarray[kk] = jj;
+           kk++;
+       }
+    }
+
+    /* delete the rows */
+    ffdrws(fptr, rowarray, nrows, status);
+    
+    free(rowarray);
+    free(maxrow);
+    free(minrow);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffdrws(fitsfile *fptr,  /* I - FITS file pointer                        */
+           long *rownum,    /* I - list of rows to delete (1 = first)       */
+           long nrows,      /* I - number of rows to delete                 */
+           int *status)     /* IO - error status                            */
+/*
+ delete the list of rows from the table (1 = first row of table).
+*/
+{
+    LONGLONG naxis1, naxis2, insertpos, nextrowpos;
+    long ii, nextrow;
+    char comm[FLEN_COMMENT];
+    unsigned char *buffer;
+
+    if (*status > 0)
+        return(*status);
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    /* rescan header if data structure is undefined */
+    if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+        if ( ffrdef(fptr, status) > 0)               
+            return(*status);
+
+    if ((fptr->Fptr)->hdutype == IMAGE_HDU)
+    {
+        ffpmsg("Can only delete rows in TABLE or BINTABLE extension (ffdrws)");
+        return(*status = NOT_TABLE);
+    }
+
+    if (nrows < 0 )
+        return(*status = NEG_BYTES);
+    else if (nrows == 0)
+        return(*status);   /* no op, so just return */
+
+    ffgkyjj(fptr, "NAXIS1", &naxis1, comm, status); /* row width   */
+    ffgkyjj(fptr, "NAXIS2", &naxis2, comm, status); /* number of rows */
+
+    /* check that input row list is in ascending order */
+    for (ii = 1; ii < nrows; ii++)
+    {
+        if (rownum[ii - 1] >= rownum[ii])
+        {
+            ffpmsg("row numbers are not in increasing order (ffdrws)");
+            return(*status = BAD_ROW_NUM);
+        }
+    }
+
+    if (rownum[0] < 1)
+    {
+        ffpmsg("first row to delete is less than 1 (ffdrws)");
+        return(*status = BAD_ROW_NUM);
+    }
+    else if (rownum[nrows - 1] > naxis2)
+    {
+        ffpmsg("last row to delete exceeds size of table (ffdrws)");
+        return(*status = BAD_ROW_NUM);
+    }
+
+    buffer = (unsigned char *) malloc( (size_t) naxis1);  /* buffer for one row */
+
+    if (!buffer)
+    {
+        ffpmsg("malloc failed (ffdrws)");
+        return(*status = MEMORY_ALLOCATION);
+    }
+
+    /* byte location to start of first row to delete, and the next row */
+    insertpos = (fptr->Fptr)->datastart + ((rownum[0] - 1) * naxis1);
+    nextrowpos = insertpos + naxis1;
+    nextrow = rownum[0] + 1;
+
+    /* work through the list of rows to delete */
+    for (ii = 1; ii < nrows; nextrow++, nextrowpos += naxis1)
+    {
+        if (nextrow < rownum[ii])  
+        {   /* keep this row, so copy it to the new position */
+
+            ffmbyt(fptr, nextrowpos, REPORT_EOF, status);
+            ffgbyt(fptr, naxis1, buffer, status);  /* read the bytes */
+
+            ffmbyt(fptr, insertpos, IGNORE_EOF, status);
+            ffpbyt(fptr, naxis1, buffer, status);  /* write the bytes */
+
+            if (*status > 0)
+            {
+                ffpmsg("error while copying good rows in table (ffdrws)");
+                free(buffer);
+                return(*status);
+            }
+            insertpos += naxis1;
+        }
+        else
+        {   /* skip over this row since it is in the list */
+            ii++;
+        }
+    }
+
+    /* finished with all the rows to delete; copy remaining rows */
+    while(nextrow <= naxis2)
+    {
+        ffmbyt(fptr, nextrowpos, REPORT_EOF, status);
+        ffgbyt(fptr, naxis1, buffer, status);  /* read the bytes */
+
+        ffmbyt(fptr, insertpos, IGNORE_EOF, status);
+        ffpbyt(fptr, naxis1, buffer, status);  /* write the bytes */
+
+        if (*status > 0)
+        {
+            ffpmsg("failed to copy remaining rows in table (ffdrws)");
+            free(buffer);
+            return(*status);
+        }
+        insertpos  += naxis1;
+        nextrowpos += naxis1;
+        nextrow++; 
+    }
+    free(buffer);
+    
+    /* now delete the empty rows at the end of the table */
+    ffdrow(fptr, naxis2 - nrows + 1, nrows, status);
+
+    /* Update the heap data, if any.  This will remove any orphaned data */
+    /* that was only pointed to by the rows that have been deleted */
+    ffcmph(fptr, status);
+    
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffdrwsll(fitsfile *fptr, /* I - FITS file pointer                        */
+           LONGLONG *rownum, /* I - list of rows to delete (1 = first)       */
+           LONGLONG nrows,  /* I - number of rows to delete                 */
+           int *status)     /* IO - error status                            */
+/*
+ delete the list of rows from the table (1 = first row of table).
+*/
+{
+    LONGLONG insertpos, nextrowpos;
+    LONGLONG naxis1, naxis2, ii, nextrow;
+    char comm[FLEN_COMMENT];
+    unsigned char *buffer;
+
+    if (*status > 0)
+        return(*status);
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    /* rescan header if data structure is undefined */
+    if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+        if ( ffrdef(fptr, status) > 0)               
+            return(*status);
+
+    if ((fptr->Fptr)->hdutype == IMAGE_HDU)
+    {
+        ffpmsg("Can only delete rows in TABLE or BINTABLE extension (ffdrws)");
+        return(*status = NOT_TABLE);
+    }
+
+    if (nrows < 0 )
+        return(*status = NEG_BYTES);
+    else if (nrows == 0)
+        return(*status);   /* no op, so just return */
+
+    ffgkyjj(fptr, "NAXIS1", &naxis1, comm, status); /* row width   */
+    ffgkyjj(fptr, "NAXIS2", &naxis2, comm, status); /* number of rows */
+
+    /* check that input row list is in ascending order */
+    for (ii = 1; ii < nrows; ii++)
+    {
+        if (rownum[ii - 1] >= rownum[ii])
+        {
+            ffpmsg("row numbers are not in increasing order (ffdrws)");
+            return(*status = BAD_ROW_NUM);
+        }
+    }
+
+    if (rownum[0] < 1)
+    {
+        ffpmsg("first row to delete is less than 1 (ffdrws)");
+        return(*status = BAD_ROW_NUM);
+    }
+    else if (rownum[nrows - 1] > naxis2)
+    {
+        ffpmsg("last row to delete exceeds size of table (ffdrws)");
+        return(*status = BAD_ROW_NUM);
+    }
+
+    buffer = (unsigned char *) malloc( (size_t) naxis1);  /* buffer for one row */
+
+    if (!buffer)
+    {
+        ffpmsg("malloc failed (ffdrwsll)");
+        return(*status = MEMORY_ALLOCATION);
+    }
+
+    /* byte location to start of first row to delete, and the next row */
+    insertpos = (fptr->Fptr)->datastart + ((rownum[0] - 1) * naxis1);
+    nextrowpos = insertpos + naxis1;
+    nextrow = rownum[0] + 1;
+
+    /* work through the list of rows to delete */
+    for (ii = 1; ii < nrows; nextrow++, nextrowpos += naxis1)
+    {
+        if (nextrow < rownum[ii])  
+        {   /* keep this row, so copy it to the new position */
+
+            ffmbyt(fptr, nextrowpos, REPORT_EOF, status);
+            ffgbyt(fptr, naxis1, buffer, status);  /* read the bytes */
+
+            ffmbyt(fptr, insertpos, IGNORE_EOF, status);
+            ffpbyt(fptr, naxis1, buffer, status);  /* write the bytes */
+
+            if (*status > 0)
+            {
+                ffpmsg("error while copying good rows in table (ffdrws)");
+                free(buffer);
+                return(*status);
+            }
+            insertpos += naxis1;
+        }
+        else
+        {   /* skip over this row since it is in the list */
+            ii++;
+        }
+    }
+
+    /* finished with all the rows to delete; copy remaining rows */
+    while(nextrow <= naxis2)
+    {
+        ffmbyt(fptr, nextrowpos, REPORT_EOF, status);
+        ffgbyt(fptr, naxis1, buffer, status);  /* read the bytes */
+
+        ffmbyt(fptr, insertpos, IGNORE_EOF, status);
+        ffpbyt(fptr, naxis1, buffer, status);  /* write the bytes */
+
+        if (*status > 0)
+        {
+            ffpmsg("failed to copy remaining rows in table (ffdrws)");
+            free(buffer);
+            return(*status);
+        }
+        insertpos  += naxis1;
+        nextrowpos += naxis1;
+        nextrow++; 
+    }
+    free(buffer);
+    
+    /* now delete the empty rows at the end of the table */
+    ffdrow(fptr, naxis2 - nrows + 1, nrows, status);
+
+    /* Update the heap data, if any.  This will remove any orphaned data */
+    /* that was only pointed to by the rows that have been deleted */
+    ffcmph(fptr, status);
+    
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffrwrg(
+      char *rowlist,      /* I - list of rows and row ranges */
+      LONGLONG maxrows,       /* I - number of rows in the table */
+      int maxranges,     /* I - max number of ranges to be returned */
+      int *numranges,    /* O - number ranges returned */
+      long *minrow,       /* O - first row in each range */
+      long *maxrow,       /* O - last row in each range */
+      int *status)        /* IO - status value */
+{
+/*
+   parse the input list of row ranges, returning the number of ranges,
+   and the min and max row value in each range. 
+
+   The only characters allowed in the input rowlist are 
+       decimal digits, minus sign, and comma (and non-significant spaces) 
+
+   Example:  
+
+     list = "10-20, 30-35,50"
+
+   would return numranges = 3, minrow[] = {10, 30, 50}, maxrow[] = {20, 35, 50}
+
+   error is returned if min value of range is > max value of range or if the
+   ranges are not monotonically increasing.
+*/
+    char *next;
+    long minval, maxval;
+
+    if (*status > 0)
+        return(*status);
+
+    if (maxrows <= 0 ) {
+        *status = RANGE_PARSE_ERROR;
+        ffpmsg("Input maximum range value is <= 0 (fits_parse_ranges)");
+        return(*status);
+    }
+
+    next = rowlist;
+    *numranges = 0;
+
+    while (*next == ' ')next++;   /* skip spaces */
+   
+    while (*next != '\0') {
+
+      /* find min value of next range; *next must be '-' or a digit */
+      if (*next == '-') {
+          minval = 1;    /* implied minrow value = 1 */
+      } else if ( isdigit((int) *next) ) {
+          minval = strtol(next, &next, 10);
+      } else {
+          *status = RANGE_PARSE_ERROR;
+          ffpmsg("Syntax error in this row range list:");
+          ffpmsg(rowlist);
+          return(*status);
+      }
+
+      while (*next == ' ')next++;   /* skip spaces */
+
+      /* find max value of next range; *next must be '-', or ',' */
+      if (*next == '-') {
+          next++;
+          while (*next == ' ')next++;   /* skip spaces */
+
+          if ( isdigit((int) *next) ) {
+              maxval = strtol(next, &next, 10);
+          } else if (*next == ',' || *next == '\0') {
+              maxval = (long) maxrows;  /* implied max value */
+          } else {
+              *status = RANGE_PARSE_ERROR;
+              ffpmsg("Syntax error in this row range list:");
+              ffpmsg(rowlist);
+              return(*status);
+          }
+      } else if (*next == ',' || *next == '\0') {
+          maxval = minval;  /* only a single integer in this range */
+      } else {
+          *status = RANGE_PARSE_ERROR;
+          ffpmsg("Syntax error in this row range list:");
+          ffpmsg(rowlist);
+          return(*status);
+      }
+
+      if (*numranges + 1 > maxranges) {
+          *status = RANGE_PARSE_ERROR;
+          ffpmsg("Overflowed maximum number of ranges (fits_parse_ranges)");
+          return(*status);
+      }
+
+      if (minval < 1 ) {
+          *status = RANGE_PARSE_ERROR;
+          ffpmsg("Syntax error in this row range list: row number < 1");
+          ffpmsg(rowlist);
+          return(*status);
+      }
+
+      if (maxval < minval) {
+          *status = RANGE_PARSE_ERROR;
+          ffpmsg("Syntax error in this row range list: min > max");
+          ffpmsg(rowlist);
+          return(*status);
+      }
+
+      if (*numranges > 0) {
+          if (minval <= maxrow[(*numranges) - 1]) {
+             *status = RANGE_PARSE_ERROR;
+             ffpmsg("Syntax error in this row range list.  Range minimum is");
+             ffpmsg("  less than or equal to previous range maximum");
+             ffpmsg(rowlist);
+             return(*status);
+         }
+      }
+
+      if (minval <= maxrows) {   /* ignore range if greater than maxrows */
+          if (maxval > maxrows)
+              maxval = (long) maxrows;
+
+           minrow[*numranges] = minval;
+           maxrow[*numranges] = maxval;
+
+           (*numranges)++;
+      }
+
+      while (*next == ' ')next++;   /* skip spaces */
+      if (*next == ',') {
+           next++;
+           while (*next == ' ')next++;   /* skip more spaces */
+      }
+    }
+
+    if (*numranges == 0) {  /* a null string was entered */
+         minrow[0] = 1;
+         maxrow[0] = (long) maxrows;
+         *numranges = 1;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffrwrgll(
+      char *rowlist,      /* I - list of rows and row ranges */
+      LONGLONG maxrows,       /* I - number of rows in the list */
+      int maxranges,     /* I - max number of ranges to be returned */
+      int *numranges,    /* O - number ranges returned */
+      LONGLONG *minrow,       /* O - first row in each range */
+      LONGLONG *maxrow,       /* O - last row in each range */
+      int *status)        /* IO - status value */
+{
+/*
+   parse the input list of row ranges, returning the number of ranges,
+   and the min and max row value in each range. 
+
+   The only characters allowed in the input rowlist are 
+       decimal digits, minus sign, and comma (and non-significant spaces) 
+
+   Example:  
+
+     list = "10-20, 30-35,50"
+
+   would return numranges = 3, minrow[] = {10, 30, 50}, maxrow[] = {20, 35, 50}
+
+   error is returned if min value of range is > max value of range or if the
+   ranges are not monotonically increasing.
+*/
+    char *next;
+    LONGLONG minval, maxval;
+    double dvalue;
+
+    if (*status > 0)
+        return(*status);
+
+    if (maxrows <= 0 ) {
+        *status = RANGE_PARSE_ERROR;
+        ffpmsg("Input maximum range value is <= 0 (fits_parse_ranges)");
+        return(*status);
+    }
+
+    next = rowlist;
+    *numranges = 0;
+
+    while (*next == ' ')next++;   /* skip spaces */
+   
+    while (*next != '\0') {
+
+      /* find min value of next range; *next must be '-' or a digit */
+      if (*next == '-') {
+          minval = 1;    /* implied minrow value = 1 */
+      } else if ( isdigit((int) *next) ) {
+
+        /* read as a double, because the string to LONGLONG function */
+        /* is platform dependent (strtoll, strtol, _atoI64)          */
+
+          dvalue = strtod(next, &next);
+          minval = (LONGLONG) (dvalue + 0.1);
+
+      } else {
+          *status = RANGE_PARSE_ERROR;
+          ffpmsg("Syntax error in this row range list:");
+          ffpmsg(rowlist);
+          return(*status);
+      }
+
+      while (*next == ' ')next++;   /* skip spaces */
+
+      /* find max value of next range; *next must be '-', or ',' */
+      if (*next == '-') {
+          next++;
+          while (*next == ' ')next++;   /* skip spaces */
+
+          if ( isdigit((int) *next) ) {
+
+            /* read as a double, because the string to LONGLONG function */
+            /* is platform dependent (strtoll, strtol, _atoI64)          */
+
+              dvalue = strtod(next, &next);
+              maxval = (LONGLONG) (dvalue + 0.1);
+
+          } else if (*next == ',' || *next == '\0') {
+              maxval = maxrows;  /* implied max value */
+          } else {
+              *status = RANGE_PARSE_ERROR;
+              ffpmsg("Syntax error in this row range list:");
+              ffpmsg(rowlist);
+              return(*status);
+          }
+      } else if (*next == ',' || *next == '\0') {
+          maxval = minval;  /* only a single integer in this range */
+      } else {
+          *status = RANGE_PARSE_ERROR;
+          ffpmsg("Syntax error in this row range list:");
+          ffpmsg(rowlist);
+          return(*status);
+      }
+
+      if (*numranges + 1 > maxranges) {
+          *status = RANGE_PARSE_ERROR;
+          ffpmsg("Overflowed maximum number of ranges (fits_parse_ranges)");
+          return(*status);
+      }
+
+      if (minval < 1 ) {
+          *status = RANGE_PARSE_ERROR;
+          ffpmsg("Syntax error in this row range list: row number < 1");
+          ffpmsg(rowlist);
+          return(*status);
+      }
+
+      if (maxval < minval) {
+          *status = RANGE_PARSE_ERROR;
+          ffpmsg("Syntax error in this row range list: min > max");
+          ffpmsg(rowlist);
+          return(*status);
+      }
+
+      if (*numranges > 0) {
+          if (minval <= maxrow[(*numranges) - 1]) {
+             *status = RANGE_PARSE_ERROR;
+             ffpmsg("Syntax error in this row range list.  Range minimum is");
+             ffpmsg("  less than or equal to previous range maximum");
+             ffpmsg(rowlist);
+             return(*status);
+         }
+      }
+
+      if (minval <= maxrows) {   /* ignore range if greater than maxrows */
+          if (maxval > maxrows)
+              maxval = maxrows;
+
+           minrow[*numranges] = minval;
+           maxrow[*numranges] = maxval;
+
+           (*numranges)++;
+      }
+
+      while (*next == ' ')next++;   /* skip spaces */
+      if (*next == ',') {
+           next++;
+           while (*next == ' ')next++;   /* skip more spaces */
+      }
+    }
+
+    if (*numranges == 0) {  /* a null string was entered */
+         minrow[0] = 1;
+         maxrow[0] = maxrows;
+         *numranges = 1;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fficol(fitsfile *fptr,  /* I - FITS file pointer                        */
+           int numcol,      /* I - position for new col. (1 = 1st)          */
+           char *ttype,     /* I - name of column (TTYPE keyword)           */
+           char *tform,     /* I - format of column (TFORM keyword)         */
+           int *status)     /* IO - error status                            */
+/*
+ Insert a new column into an existing table at position numcol.  If
+ numcol is greater than the number of existing columns in the table
+ then the new column will be appended as the last column in the table.
+*/
+{
+    char *name, *format;
+
+    name = ttype;
+    format = tform;
+
+    fficls(fptr, numcol, 1, &name, &format, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fficls(fitsfile *fptr,  /* I - FITS file pointer                        */
+           int fstcol,      /* I - position for first new col. (1 = 1st)    */
+           int ncols,       /* I - number of columns to insert              */
+           char **ttype,    /* I - array of column names(TTYPE keywords)    */
+           char **tform,    /* I - array of formats of column (TFORM)       */
+           int *status)     /* IO - error status                            */
+/*
+ Insert 1 or more new columns into an existing table at position numcol.  If
+ fstcol is greater than the number of existing columns in the table
+ then the new column will be appended as the last column in the table.
+*/
+{
+    int colnum, datacode, decims, tfields, tstatus, ii;
+    LONGLONG datasize, firstbyte, nbytes, nadd, naxis1, naxis2, freespace;
+    LONGLONG tbcol, firstcol, delbyte;
+    long nblock, width, repeat;
+    char tfm[FLEN_VALUE], keyname[FLEN_KEYWORD], comm[FLEN_COMMENT], *cptr;
+    tcolumn *colptr;
+
+    if (*status > 0)
+        return(*status);
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+    {
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    }
+        /* rescan header if data structure is undefined */
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+        if ( ffrdef(fptr, status) > 0)               
+            return(*status);
+
+    if ((fptr->Fptr)->hdutype == IMAGE_HDU)
+    {
+       ffpmsg("Can only add columns to TABLE or BINTABLE extension (fficol)");
+       return(*status = NOT_TABLE);
+    }
+
+    /*  is the column number valid?  */
+    tfields = (fptr->Fptr)->tfield;
+    if (fstcol < 1 )
+        return(*status = BAD_COL_NUM);
+    else if (fstcol > tfields)
+        colnum = tfields + 1;   /* append as last column */
+    else
+        colnum = fstcol;
+
+    /* parse the tform value and calc number of bytes to add to each row */
+    delbyte = 0;
+    for (ii = 0; ii < ncols; ii++)
+    {
+        strcpy(tfm, tform[ii]);
+        ffupch(tfm);         /* make sure format is in upper case */
+
+        if ((fptr->Fptr)->hdutype == ASCII_TBL)
+        {
+            ffasfm(tfm, &datacode, &width, &decims, status);
+            delbyte += width + 1;  /*  add one space between the columns */
+        }
+        else
+        {
+            ffbnfm(tfm, &datacode, &repeat, &width, status);
+
+            if (datacode < 0)         /* variable length array column */
+                delbyte += 8;
+            else if (datacode == 1)          /* bit column; round up  */
+                delbyte += (repeat + 7) / 8; /* to multiple of 8 bits */
+            else if (datacode == 16)  /* ASCII string column */
+                delbyte += repeat;
+            else                      /* numerical data type */
+                delbyte += (datacode / 10) * repeat;
+        }
+    }
+
+    if (*status > 0) 
+        return(*status);
+
+    /* get the current size of the table */
+    /* use internal structure since NAXIS2 keyword may not be up to date */
+    naxis1 = (fptr->Fptr)->rowlength;
+    naxis2 = (fptr->Fptr)->numrows;
+
+    /* current size of data */
+    datasize = (fptr->Fptr)->heapstart + (fptr->Fptr)->heapsize;
+    freespace = ( ( (datasize + 2879) / 2880) * 2880) - datasize;
+    nadd = delbyte * naxis2;   /* no. of bytes to add to table */
+
+    if ( (freespace - nadd) < 0)   /* not enough existing space? */
+    {
+        nblock = (long) ((nadd - freespace + 2879) / 2880);     /* number of blocks  */
+        if (ffiblk(fptr, nblock, 1, status) > 0)       /* insert the blocks */
+            return(*status);
+    }
+
+    /* shift heap down (if it exists) */
+    if ((fptr->Fptr)->heapsize > 0)
+    {
+        nbytes = (fptr->Fptr)->heapsize;    /* no. of bytes to shift down */
+
+        /* absolute heap pos */
+        firstbyte = (fptr->Fptr)->datastart + (fptr->Fptr)->heapstart;
+
+        if (ffshft(fptr, firstbyte, nbytes, nadd, status) > 0) /* move heap */
+            return(*status);
+    }
+
+    /* update the heap starting address */
+    (fptr->Fptr)->heapstart += nadd;
+
+    /* update the THEAP keyword if it exists */
+    tstatus = 0;
+    ffmkyj(fptr, "THEAP", (fptr->Fptr)->heapstart, "&", &tstatus);
+
+    /* calculate byte position in the row where to insert the new column */
+    if (colnum > tfields)
+        firstcol = naxis1;
+    else
+    {
+        colptr = (fptr->Fptr)->tableptr;
+        colptr += (colnum - 1);
+        firstcol = colptr->tbcol;
+    }
+
+    /* insert delbyte bytes in every row, at byte position firstcol */
+    ffcins(fptr, naxis1, naxis2, delbyte, firstcol, status);
+
+    if ((fptr->Fptr)->hdutype == ASCII_TBL)
+    {
+        /* adjust the TBCOL values of the existing columns */
+        for(ii = 0; ii < tfields; ii++)
+        {
+            ffkeyn("TBCOL", ii + 1, keyname, status);
+            ffgkyjj(fptr, keyname, &tbcol, comm, status);
+            if (tbcol > firstcol)
+            {
+                tbcol += delbyte;
+                ffmkyj(fptr, keyname, tbcol, "&", status);
+            }
+        }
+    }
+
+    /* update the mandatory keywords */
+    ffmkyj(fptr, "TFIELDS", tfields + ncols, "&", status);
+    ffmkyj(fptr, "NAXIS1", naxis1 + delbyte, "&", status);
+
+    /* increment the index value on any existing column keywords */
+    if(colnum <= tfields)
+        ffkshf(fptr, colnum, tfields, ncols, status);
+
+    /* add the required keywords for the new columns */
+    for (ii = 0; ii < ncols; ii++, colnum++)
+    {
+        strcpy(comm, "label for field");
+        ffkeyn("TTYPE", colnum, keyname, status);
+        ffpkys(fptr, keyname, ttype[ii], comm, status);
+
+        strcpy(comm, "format of field");
+        strcpy(tfm, tform[ii]);
+        ffupch(tfm);         /* make sure format is in upper case */
+        ffkeyn("TFORM", colnum, keyname, status);
+
+        if (abs(datacode) == TSBYTE) 
+        {
+           /* Replace the 'S' with an 'B' in the TFORMn code */
+           cptr = tfm;
+           while (*cptr != 'S') 
+              cptr++;
+
+           *cptr = 'B';
+           ffpkys(fptr, keyname, tfm, comm, status);
+
+           /* write the TZEROn and TSCALn keywords */
+           ffkeyn("TZERO", colnum, keyname, status);
+           strcpy(comm, "offset for signed bytes");
+
+           ffpkyg(fptr, keyname, -128., 0, comm, status);
+
+           ffkeyn("TSCAL", colnum, keyname, status);
+           strcpy(comm, "data are not scaled");
+           ffpkyg(fptr, keyname, 1., 0, comm, status);
+        }
+        else if (abs(datacode) == TUSHORT) 
+        {
+           /* Replace the 'U' with an 'I' in the TFORMn code */
+           cptr = tfm;
+           while (*cptr != 'U') 
+              cptr++;
+
+           *cptr = 'I';
+           ffpkys(fptr, keyname, tfm, comm, status);
+
+           /* write the TZEROn and TSCALn keywords */
+           ffkeyn("TZERO", colnum, keyname, status);
+           strcpy(comm, "offset for unsigned integers");
+
+           ffpkyg(fptr, keyname, 32768., 0, comm, status);
+
+           ffkeyn("TSCAL", colnum, keyname, status);
+           strcpy(comm, "data are not scaled");
+           ffpkyg(fptr, keyname, 1., 0, comm, status);
+        }
+        else if (abs(datacode) == TULONG) 
+        {
+           /* Replace the 'V' with an 'J' in the TFORMn code */
+           cptr = tfm;
+           while (*cptr != 'V') 
+              cptr++;
+
+           *cptr = 'J';
+           ffpkys(fptr, keyname, tfm, comm, status);
+
+           /* write the TZEROn and TSCALn keywords */
+           ffkeyn("TZERO", colnum, keyname, status);
+           strcpy(comm, "offset for unsigned integers");
+
+           ffpkyg(fptr, keyname, 2147483648., 0, comm, status);
+
+           ffkeyn("TSCAL", colnum, keyname, status);
+           strcpy(comm, "data are not scaled");
+           ffpkyg(fptr, keyname, 1., 0, comm, status);
+        }
+        else
+        {
+           ffpkys(fptr, keyname, tfm, comm, status);
+        }
+
+        if ((fptr->Fptr)->hdutype == ASCII_TBL)   /* write the TBCOL keyword */
+        {
+            if (colnum == tfields + 1)
+                tbcol = firstcol + 2;  /* allow space between preceding col */
+            else
+                tbcol = firstcol + 1;
+
+            strcpy(comm, "beginning column of field");
+            ffkeyn("TBCOL", colnum, keyname, status);
+            ffpkyj(fptr, keyname, tbcol, comm, status);
+
+            /* increment the column starting position for the next column */
+            ffasfm(tfm, &datacode, &width, &decims, status);
+            firstcol += width + 1;  /*  add one space between the columns */
+        }
+    }
+    ffrdef(fptr, status); /* initialize the new table structure */
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffmvec(fitsfile *fptr,  /* I - FITS file pointer                        */
+           int colnum,      /* I - position of col to be modified           */
+           LONGLONG newveclen,  /* I - new vector length of column (TFORM)       */
+           int *status)     /* IO - error status                            */
+/*
+  Modify the vector length of a column in a binary table, larger or smaller.
+  E.g., change a column from TFORMn = '1E' to '20E'.
+*/
+{
+    int datacode, tfields, tstatus;
+    LONGLONG datasize, size, firstbyte, nbytes, nadd, ndelete;
+    LONGLONG naxis1, naxis2, firstcol, freespace;
+    LONGLONG width, delbyte, repeat;
+    long nblock;
+    char tfm[FLEN_VALUE], keyname[FLEN_KEYWORD], tcode[2];
+    tcolumn *colptr;
+
+    if (*status > 0)
+        return(*status);
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+    {
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    }
+        /* rescan header if data structure is undefined */
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+        if ( ffrdef(fptr, status) > 0)               
+            return(*status);
+
+    if ((fptr->Fptr)->hdutype != BINARY_TBL)
+    {
+       ffpmsg(
+  "Can only change vector length of a column in BINTABLE extension (ffmvec)");
+       return(*status = NOT_TABLE);
+    }
+
+    /*  is the column number valid?  */
+    tfields = (fptr->Fptr)->tfield;
+    if (colnum < 1 || colnum > tfields)
+        return(*status = BAD_COL_NUM);
+
+    /* look up the current vector length and element width */
+
+    colptr = (fptr->Fptr)->tableptr;
+    colptr += (colnum - 1);
+
+    datacode = colptr->tdatatype; /* datatype of the column */
+    repeat =  colptr->trepeat;  /* field repeat count  */
+    width =  colptr->twidth;   /*  width of a single element in chars */
+
+    if (datacode < 0)
+    {
+        ffpmsg(
+        "Can't modify vector length of variable length column (ffmvec)");
+        return(*status = BAD_TFORM);
+    }
+
+    if (repeat == newveclen)
+        return(*status);  /* column already has the desired vector length */
+
+    if (datacode == TSTRING)
+        width = 1;      /* width was equal to width of unit string */
+
+    naxis1 =  (fptr->Fptr)->rowlength;   /* current width of the table */
+    naxis2 = (fptr->Fptr)->numrows;
+
+    delbyte = (newveclen - repeat) * width;    /* no. of bytes to insert */
+    if (datacode == TBIT)  /* BIT column is a special case */
+       delbyte = ((newveclen + 7) / 8) - ((repeat + 7) / 8);
+
+    if (delbyte > 0)  /* insert space for more elements */
+    {
+      /* current size of data */
+      datasize = (fptr->Fptr)->heapstart + (fptr->Fptr)->heapsize;
+      freespace = ( ( (datasize + 2879) / 2880) * 2880) - datasize;
+
+      nadd = (LONGLONG)delbyte * naxis2;   /* no. of bytes to add to table */
+
+      if ( (freespace - nadd) < 0)   /* not enough existing space? */
+      {
+        nblock = (long) ((nadd - freespace + 2879) / 2880);    /* number of blocks  */
+        if (ffiblk(fptr, nblock, 1, status) > 0)      /* insert the blocks */
+          return(*status);
+      }
+
+      /* shift heap down (if it exists) */
+      if ((fptr->Fptr)->heapsize > 0)
+      {
+        nbytes = (fptr->Fptr)->heapsize;    /* no. of bytes to shift down */
+
+        /* absolute heap pos */
+        firstbyte = (fptr->Fptr)->datastart + (fptr->Fptr)->heapstart;
+
+        if (ffshft(fptr, firstbyte, nbytes, nadd, status) > 0) /* move heap */
+            return(*status);
+      }
+
+      /* update the heap starting address */
+      (fptr->Fptr)->heapstart += nadd;
+
+      /* update the THEAP keyword if it exists */
+      tstatus = 0;
+      ffmkyj(fptr, "THEAP", (fptr->Fptr)->heapstart, "&", &tstatus);
+
+      firstcol = colptr->tbcol + (repeat * width);  /* insert position */
+
+      /* insert delbyte bytes in every row, at byte position firstcol */
+      ffcins(fptr, naxis1, naxis2, delbyte, firstcol, status);
+    }
+    else if (delbyte < 0)
+    {
+      /* current size of table */
+      size = (fptr->Fptr)->heapstart + (fptr->Fptr)->heapsize;
+      freespace = ((size + 2879) / 2880) * 2880 - size - ((LONGLONG)delbyte * naxis2);
+      nblock = (long) (freespace / 2880);   /* number of empty blocks to delete */
+      firstcol = colptr->tbcol + (newveclen * width);  /* delete position */
+
+      /* delete elements from the vector */
+      ffcdel(fptr, naxis1, naxis2, -delbyte, firstcol, status);
+ 
+      /* abs heap pos */
+      firstbyte = (fptr->Fptr)->datastart + (fptr->Fptr)->heapstart;
+      ndelete = (LONGLONG)delbyte * naxis2; /* size of shift (negative) */
+
+      /* shift heap up (if it exists) */
+      if ((fptr->Fptr)->heapsize > 0)
+      {
+        nbytes = (fptr->Fptr)->heapsize;    /* no. of bytes to shift up */
+        if (ffshft(fptr, firstbyte, nbytes, ndelete, status) > 0)
+          return(*status);
+      }
+
+      /* delete the empty  blocks at the end of the HDU */
+      if (nblock > 0)
+        ffdblk(fptr, nblock, status);
+
+      /* update the heap starting address */
+      (fptr->Fptr)->heapstart += ndelete;  /* ndelete is negative */
+
+      /* update the THEAP keyword if it exists */
+      tstatus = 0;
+      ffmkyj(fptr, "THEAP", (fptr->Fptr)->heapstart, "&", &tstatus);
+    }
+
+    /* construct the new TFORM keyword for the column */
+    if (datacode == TBIT)
+      strcpy(tcode,"X");
+    else if (datacode == TBYTE)
+      strcpy(tcode,"B");
+    else if (datacode == TLOGICAL)
+      strcpy(tcode,"L");
+    else if (datacode == TSTRING)
+      strcpy(tcode,"A");
+    else if (datacode == TSHORT)
+      strcpy(tcode,"I");
+    else if (datacode == TLONG)
+      strcpy(tcode,"J");
+    else if (datacode == TLONGLONG)
+      strcpy(tcode,"K");
+    else if (datacode == TFLOAT)
+      strcpy(tcode,"E");
+    else if (datacode == TDOUBLE)
+      strcpy(tcode,"D");
+    else if (datacode == TCOMPLEX)
+      strcpy(tcode,"C");
+    else if (datacode == TDBLCOMPLEX)
+      strcpy(tcode,"M");
+
+    /* write as a double value because the LONGLONG conversion */
+    /* character in sprintf is platform dependent ( %lld, %ld, %I64d ) */
+
+    sprintf(tfm,"%.0f%s",(double) newveclen, tcode); 
+
+    ffkeyn("TFORM", colnum, keyname, status);  /* Keyword name */
+    ffmkys(fptr, keyname, tfm, "&", status);   /* modify TFORM keyword */
+
+    ffmkyj(fptr, "NAXIS1", naxis1 + delbyte, "&", status); /* modify NAXIS1 */
+
+    ffrdef(fptr, status); /* reinitialize the new table structure */
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffcpcl(fitsfile *infptr,    /* I - FITS file pointer to input file  */
+           fitsfile *outfptr,   /* I - FITS file pointer to output file */
+           int incol,           /* I - number of input column   */
+           int outcol,          /* I - number for output column  */
+           int create_col,      /* I - create new col if TRUE, else overwrite */
+           int *status)         /* IO - error status     */
+/*
+  copy a column from infptr and insert it in the outfptr table.
+*/
+{
+    int tstatus, colnum, typecode, anynull;
+    long tfields, repeat, width, nrows, outrows;
+    long inloop, outloop, maxloop, ndone, ntodo, npixels;
+    long firstrow, firstelem, ii;
+    char keyname[FLEN_KEYWORD], ttype[FLEN_VALUE], tform[FLEN_VALUE];
+    char ttype_comm[FLEN_COMMENT],tform_comm[FLEN_COMMENT];
+    char *lvalues = 0, nullflag, **strarray = 0;
+    char nulstr[] = {'\5', '\0'};  /* unique null string value */
+    double dnull = 0.l, *dvalues = 0;
+    float fnull = 0., *fvalues = 0;
+
+    if (*status > 0)
+        return(*status);
+
+    if (infptr->HDUposition != (infptr->Fptr)->curhdu)
+    {
+        ffmahd(infptr, (infptr->HDUposition) + 1, NULL, status);
+    }
+    else if ((infptr->Fptr)->datastart == DATA_UNDEFINED)
+        ffrdef(infptr, status);                /* rescan header */
+
+    if (outfptr->HDUposition != (outfptr->Fptr)->curhdu)
+    {
+        ffmahd(outfptr, (outfptr->HDUposition) + 1, NULL, status);
+    }
+    else if ((outfptr->Fptr)->datastart == DATA_UNDEFINED)
+        ffrdef(outfptr, status);               /* rescan header */
+
+    if (*status > 0)
+        return(*status);
+
+    if ((infptr->Fptr)->hdutype == IMAGE_HDU || (outfptr->Fptr)->hdutype == IMAGE_HDU)
+    {
+       ffpmsg
+       ("Can not copy columns to or from IMAGE HDUs (ffcpcl)");
+       return(*status = NOT_TABLE);
+    }
+
+    if ( (infptr->Fptr)->hdutype == BINARY_TBL &&  (outfptr->Fptr)->hdutype == ASCII_TBL)
+    {
+       ffpmsg
+       ("Copying from Binary table to ASCII table is not supported (ffcpcl)");
+       return(*status = NOT_BTABLE);
+    }
+
+    /* get the datatype and vector repeat length of the column */
+    ffgtcl(infptr, incol, &typecode, &repeat, &width, status);
+
+    if (typecode < 0)
+    {
+        ffpmsg("Variable-length columns are not supported (ffcpcl)");
+        return(*status = BAD_TFORM);
+    }
+
+    if (create_col)    /* insert new column in output table? */
+    {
+        tstatus = 0;
+        ffkeyn("TTYPE", incol, keyname, &tstatus);
+        ffgkys(infptr, keyname, ttype, ttype_comm, &tstatus);
+        ffkeyn("TFORM", incol, keyname, &tstatus);
+    
+        if (ffgkys(infptr, keyname, tform, tform_comm, &tstatus) )
+        {
+          ffpmsg
+          ("Could not find TTYPE and TFORM keywords in input table (ffcpcl)");
+          return(*status = NO_TFORM);
+        }
+
+        if ((infptr->Fptr)->hdutype == ASCII_TBL && (outfptr->Fptr)->hdutype == BINARY_TBL)
+        {
+            /* convert from ASCII table to BINARY table format string */
+            if (typecode == TSTRING)
+                ffnkey(width, "A", tform, status);
+
+            else if (typecode == TLONG)
+                strcpy(tform, "1J");
+
+            else if (typecode == TSHORT)
+                strcpy(tform, "1I");
+
+            else if (typecode == TFLOAT)
+                strcpy(tform,"1E");
+
+            else if (typecode == TDOUBLE)
+                strcpy(tform,"1D");
+        }
+
+        if (ffgkyj(outfptr, "TFIELDS", &tfields, 0, &tstatus))
+        {
+           ffpmsg
+           ("Could not read TFIELDS keyword in output table (ffcpcl)");
+           return(*status = NO_TFIELDS);
+        }
+
+        colnum = minvalue((int) tfields + 1, outcol); /* output col. number */
+
+        /* create the empty column */
+        if (fficol(outfptr, colnum, ttype, tform, status) > 0)
+        {
+           ffpmsg
+           ("Could not append new column to output file (ffcpcl)");
+           return(*status);
+        }
+
+        /* copy the comment strings from the input file for TTYPE and TFORM */
+        tstatus = 0;
+        ffkeyn("TTYPE", colnum, keyname, &tstatus);
+        ffmcom(outfptr, keyname, ttype_comm, &tstatus);
+        ffkeyn("TFORM", colnum, keyname, &tstatus);
+        ffmcom(outfptr, keyname, tform_comm, &tstatus);
+
+        /* copy other column-related keywords if they exist */
+
+        ffcpky(infptr, outfptr, incol, colnum, "TUNIT", status);
+        ffcpky(infptr, outfptr, incol, colnum, "TSCAL", status);
+        ffcpky(infptr, outfptr, incol, colnum, "TZERO", status);
+        ffcpky(infptr, outfptr, incol, colnum, "TDISP", status);
+        ffcpky(infptr, outfptr, incol, colnum, "TLMIN", status);
+        ffcpky(infptr, outfptr, incol, colnum, "TLMAX", status);
+        ffcpky(infptr, outfptr, incol, colnum, "TDIM", status);
+
+        /*  WCS keywords */
+        ffcpky(infptr, outfptr, incol, colnum, "TCTYP", status);
+        ffcpky(infptr, outfptr, incol, colnum, "TCUNI", status);
+        ffcpky(infptr, outfptr, incol, colnum, "TCRVL", status);
+        ffcpky(infptr, outfptr, incol, colnum, "TCRPX", status);
+        ffcpky(infptr, outfptr, incol, colnum, "TCDLT", status);
+        ffcpky(infptr, outfptr, incol, colnum, "TCROT", status);
+
+        if ((infptr->Fptr)->hdutype == ASCII_TBL && (outfptr->Fptr)->hdutype == BINARY_TBL)
+        {
+            /* binary tables only have TNULLn keyword for integer columns */
+            if (typecode == TLONG || typecode == TSHORT)
+            {
+                /* check if null string is defined; replace with integer */
+                ffkeyn("TNULL", incol, keyname, &tstatus);
+                if (ffgkys(infptr, keyname, ttype, 0, &tstatus) <= 0)
+                {
+                   ffkeyn("TNULL", colnum, keyname, &tstatus);
+                   if (typecode == TLONG)
+                      ffpkyj(outfptr, keyname, -9999999L, "Null value", status);
+                   else
+                      ffpkyj(outfptr, keyname, -32768L, "Null value", status);
+                }
+            }
+        }
+        else
+        {
+            ffcpky(infptr, outfptr, incol, colnum, "TNULL", status);
+        }
+
+        /* rescan header to recognize the new keywords */
+        if (ffrdef(outfptr, status) )
+            return(*status);
+    }
+    else
+    {
+        colnum = outcol;
+    }
+
+    ffgkyj(infptr,  "NAXIS2", &nrows,   0, status);  /* no. of input rows */
+    ffgkyj(outfptr, "NAXIS2", &outrows, 0, status);  /* no. of output rows */
+    nrows = minvalue(nrows, outrows);
+
+    if (typecode == TBIT)
+        repeat = (repeat - 1) / 8 + 1;  /* convert from bits to bytes */
+    else if (typecode == TSTRING && (infptr->Fptr)->hdutype == BINARY_TBL)
+        repeat = repeat / width;  /* convert from chars to unit strings */
+
+    /* get optimum number of rows to copy at one time */
+    ffgrsz(infptr,  &inloop,  status);
+    ffgrsz(outfptr, &outloop, status);
+
+    /* adjust optimum number, since 2 tables are open at once */
+    maxloop = minvalue(inloop, outloop); /* smallest of the 2 tables */
+    maxloop = maxvalue(1, maxloop / 2);  /* at least 1 row */
+    maxloop = minvalue(maxloop, nrows);  /* max = nrows to be copied */
+    maxloop *= repeat;                   /* mult by no of elements in a row */
+
+    /* allocate memory for arrays */
+    if (typecode == TLOGICAL)
+    {
+       lvalues   = (char *) calloc(maxloop, sizeof(char) );
+       if (!lvalues)
+       {
+         ffpmsg
+         ("malloc failed to get memory for logicals (ffcpcl)");
+         return(*status = ARRAY_TOO_BIG);
+       }
+    }
+    else if (typecode == TSTRING)
+    {
+       /* allocate array of pointers */
+       strarray = (char **) calloc(maxloop, sizeof(strarray));
+
+       /* allocate space for each string */
+       for (ii = 0; ii < maxloop; ii++)
+          strarray[ii] = (char *) calloc(width+1, sizeof(char));
+    }
+    else if (typecode == TCOMPLEX)
+    {
+       fvalues = (float *) calloc(maxloop * 2, sizeof(float) );
+       if (!fvalues)
+       {
+         ffpmsg
+         ("malloc failed to get memory for complex (ffcpcl)");
+         return(*status = ARRAY_TOO_BIG);
+       }
+       fnull = 0.;
+    }
+    else if (typecode == TDBLCOMPLEX)
+    {
+       dvalues = (double *) calloc(maxloop * 2, sizeof(double) );
+       if (!dvalues)
+       {
+         ffpmsg
+         ("malloc failed to get memory for dbl complex (ffcpcl)");
+         return(*status = ARRAY_TOO_BIG);
+       }
+       dnull = 0.;
+    }
+    else    /* numerical datatype; read them all as doubles */
+    {
+       dvalues = (double *) calloc(maxloop, sizeof(double) );
+       if (!dvalues)
+       {
+         ffpmsg
+         ("malloc failed to get memory for doubles (ffcpcl)");
+         return(*status = ARRAY_TOO_BIG);
+       }
+         dnull = -9.99991999E31;  /* use an unlikely value for nulls */
+    }
+
+    npixels = nrows * repeat;          /* total no. of pixels to copy */
+    ntodo = minvalue(npixels, maxloop);   /* no. to copy per iteration */
+    ndone = 0;             /* total no. of pixels that have been copied */
+
+    while (ntodo)      /* iterate through the table */
+    {
+        firstrow = ndone / repeat + 1;
+        firstelem = ndone - ((firstrow - 1) * repeat) + 1;
+
+        /* read from input table */
+        if (typecode == TLOGICAL)
+            ffgcl(infptr, incol, firstrow, firstelem, ntodo, 
+                       lvalues, status);
+        else if (typecode == TSTRING)
+            ffgcvs(infptr, incol, firstrow, firstelem, ntodo,
+                       nulstr, strarray, &anynull, status);
+
+        else if (typecode == TCOMPLEX)  
+            ffgcvc(infptr, incol, firstrow, firstelem, ntodo, fnull, 
+                   fvalues, &anynull, status);
+
+        else if (typecode == TDBLCOMPLEX)
+            ffgcvm(infptr, incol, firstrow, firstelem, ntodo, dnull, 
+                   dvalues, &anynull, status);
+
+        else       /* all numerical types */
+            ffgcvd(infptr, incol, firstrow, firstelem, ntodo, dnull, 
+                   dvalues, &anynull, status);
+
+        if (*status > 0)
+        {
+            ffpmsg("Error reading input copy of column (ffcpcl)");
+            break;
+        }
+
+        /* write to output table */
+        if (typecode == TLOGICAL)
+        {
+            nullflag = 2;
+
+            ffpcnl(outfptr, colnum, firstrow, firstelem, ntodo, 
+                       lvalues, nullflag, status);
+
+        }
+
+        else if (typecode == TSTRING)
+        {
+            if (anynull)
+                ffpcns(outfptr, colnum, firstrow, firstelem, ntodo,
+                       strarray, nulstr, status);
+            else
+                ffpcls(outfptr, colnum, firstrow, firstelem, ntodo,
+                       strarray, status);
+        }
+
+        else if (typecode == TCOMPLEX)  
+        {                      /* doesn't support writing nulls */
+            ffpclc(outfptr, colnum, firstrow, firstelem, ntodo, 
+                       fvalues, status);
+        }
+
+        else if (typecode == TDBLCOMPLEX)  
+        {                      /* doesn't support writing nulls */
+            ffpclm(outfptr, colnum, firstrow, firstelem, ntodo, 
+                       dvalues, status);
+        }
+
+        else  /* all other numerical types */
+        {
+            if (anynull)
+                ffpcnd(outfptr, colnum, firstrow, firstelem, ntodo, 
+                       dvalues, dnull, status);
+            else
+                ffpcld(outfptr, colnum, firstrow, firstelem, ntodo, 
+                       dvalues, status);
+        }
+
+        if (*status > 0)
+        {
+            ffpmsg("Error writing output copy of column (ffcpcl)");
+            break;
+        }
+
+        npixels -= ntodo;
+        ndone += ntodo;
+        ntodo = minvalue(npixels, maxloop);
+    }
+
+    /* free the previously allocated memory */
+    if (typecode == TLOGICAL)
+    {
+        free(lvalues);
+    }
+    else if (typecode == TSTRING)
+    {
+         for (ii = 0; ii < maxloop; ii++)
+             free(strarray[ii]);
+
+         free(strarray);
+    }
+    else
+    {
+        free(dvalues);
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffcprw(fitsfile *infptr,    /* I - FITS file pointer to input file  */
+           fitsfile *outfptr,   /* I - FITS file pointer to output file */
+           LONGLONG firstrow,   /* I - number of first row to copy (1 based)  */
+           LONGLONG nrows,      /* I - number of rows to copy  */
+           int *status)         /* IO - error status     */
+/*
+  copy consecutive set of rows from infptr and append it in the outfptr table.
+*/
+{
+    LONGLONG innaxis1, innaxis2, outnaxis1, outnaxis2, ii, jj;
+    unsigned char *buffer;
+
+    if (*status > 0)
+        return(*status);
+
+    if (infptr->HDUposition != (infptr->Fptr)->curhdu)
+    {
+        ffmahd(infptr, (infptr->HDUposition) + 1, NULL, status);
+    }
+    else if ((infptr->Fptr)->datastart == DATA_UNDEFINED)
+        ffrdef(infptr, status);                /* rescan header */
+
+    if (outfptr->HDUposition != (outfptr->Fptr)->curhdu)
+    {
+        ffmahd(outfptr, (outfptr->HDUposition) + 1, NULL, status);
+    }
+    else if ((outfptr->Fptr)->datastart == DATA_UNDEFINED)
+        ffrdef(outfptr, status);               /* rescan header */
+
+    if (*status > 0)
+        return(*status);
+
+    if ((infptr->Fptr)->hdutype == IMAGE_HDU || (outfptr->Fptr)->hdutype == IMAGE_HDU)
+    {
+       ffpmsg
+       ("Can not copy rows to or from IMAGE HDUs (ffcprw)");
+       return(*status = NOT_TABLE);
+    }
+
+    if ( ((infptr->Fptr)->hdutype == BINARY_TBL &&  (outfptr->Fptr)->hdutype == ASCII_TBL) ||
+         ((infptr->Fptr)->hdutype == ASCII_TBL &&  (outfptr->Fptr)->hdutype == BINARY_TBL) )
+    {
+       ffpmsg
+       ("Copying rows between Binary and ASCII tables is not supported (ffcprw)");
+       return(*status = NOT_BTABLE);
+    }
+
+    ffgkyjj(infptr,  "NAXIS1", &innaxis1,  0, status);  /* width of input rows */
+    ffgkyjj(infptr,  "NAXIS2", &innaxis2,  0, status);  /* no. of input rows */
+    ffgkyjj(outfptr, "NAXIS1", &outnaxis1, 0, status);  /* width of output rows */
+    ffgkyjj(outfptr, "NAXIS2", &outnaxis2, 0, status);  /* no. of output rows */
+
+    if (*status > 0)
+        return(*status);
+
+    if (outnaxis1 > innaxis1) {
+       ffpmsg
+       ("Input and output tables do not have same width (ffcprw)");
+       return(*status = BAD_ROW_WIDTH);
+    }    
+
+    if (firstrow + nrows - 1 > innaxis2) {
+       ffpmsg
+       ("Not enough rows in input table to copy (ffcprw)");
+       return(*status = BAD_ROW_NUM);
+    }
+
+    /* allocate buffer to hold 1 row of data */
+    buffer = malloc( (size_t) innaxis1);
+    if (!buffer) {
+       ffpmsg
+       ("Unable to allocate memory (ffcprw)");
+       return(*status = MEMORY_ALLOCATION);
+    }
+ 
+    /* copy the rows, 1 at a time */
+    jj = outnaxis2 + 1;
+    for (ii = firstrow; ii < firstrow + nrows; ii++) {
+        fits_read_tblbytes (infptr,  ii, 1, innaxis1, buffer, status);
+        fits_write_tblbytes(outfptr, jj, 1, innaxis1, buffer, status);
+        jj++;
+    }
+
+    outnaxis2 += nrows;
+    fits_update_key(outfptr, TLONGLONG, "NAXIS2", &outnaxis2, 0, status);
+
+    free(buffer);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffcpky(fitsfile *infptr,    /* I - FITS file pointer to input file  */
+           fitsfile *outfptr,   /* I - FITS file pointer to output file */
+           int incol,           /* I - input index number   */
+           int outcol,          /* I - output index number  */
+           char *rootname,      /* I - root name of the keyword to be copied */
+           int *status)         /* IO - error status     */
+/*
+  copy an indexed keyword from infptr to outfptr.
+*/
+{
+    int tstatus = 0;
+    char keyname[FLEN_KEYWORD];
+    char value[FLEN_VALUE], comment[FLEN_COMMENT], card[FLEN_CARD];
+
+    ffkeyn(rootname, incol, keyname, &tstatus);
+    if (ffgkey(infptr, keyname, value, comment, &tstatus) <= 0)
+    {
+        ffkeyn(rootname, outcol, keyname, &tstatus);
+        ffmkky(keyname, value, comment, card, status);
+        ffprec(outfptr, card, status);
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffdcol(fitsfile *fptr,  /* I - FITS file pointer                        */
+           int colnum,      /* I - column to delete (1 = 1st)               */
+           int *status)     /* IO - error status                            */
+/*
+  Delete a column from a table.
+*/
+{
+    int ii, tstatus;
+    LONGLONG firstbyte, size, ndelete, nbytes, naxis1, naxis2, firstcol, delbyte, freespace;
+    LONGLONG tbcol;
+    long nblock, nspace;
+    char keyname[FLEN_KEYWORD], comm[FLEN_COMMENT];
+    tcolumn *colptr, *nextcol;
+
+    if (*status > 0)
+        return(*status);
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+    {
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    }
+    /* rescan header if data structure is undefined */
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+        if ( ffrdef(fptr, status) > 0)               
+            return(*status);
+
+    if ((fptr->Fptr)->hdutype == IMAGE_HDU)
+    {
+       ffpmsg
+       ("Can only delete column from TABLE or BINTABLE extension (ffdcol)");
+       return(*status = NOT_TABLE);
+    }
+
+    if (colnum < 1 || colnum > (fptr->Fptr)->tfield )
+        return(*status = BAD_COL_NUM);
+
+    colptr = (fptr->Fptr)->tableptr;
+    colptr += (colnum - 1);
+    firstcol = colptr->tbcol;  /* starting byte position of the column */
+
+    /* use column width to determine how many bytes to delete in each row */
+    if ((fptr->Fptr)->hdutype == ASCII_TBL)
+    {
+      delbyte = colptr->twidth;  /* width of ASCII column */
+
+      if (colnum < (fptr->Fptr)->tfield) /* check for space between next column */
+      {
+        nextcol = colptr + 1;
+        nspace = (long) ((nextcol->tbcol) - (colptr->tbcol) - delbyte);
+        if (nspace > 0)
+            delbyte++;
+      }
+      else if (colnum > 1)   /* check for space between last 2 columns */
+      {
+        nextcol = colptr - 1;
+        nspace = (long) ((colptr->tbcol) - (nextcol->tbcol) - (nextcol->twidth));
+        if (nspace > 0)
+        {
+           delbyte++;
+           firstcol--;  /* delete the leading space */
+        }
+      }
+    }
+    else   /* a binary table */
+    {
+      if (colnum < (fptr->Fptr)->tfield)
+      {
+         nextcol = colptr + 1;
+         delbyte = (nextcol->tbcol) - (colptr->tbcol);
+      }
+      else
+      {
+         delbyte = ((fptr->Fptr)->rowlength) - (colptr->tbcol);
+      }
+    }
+
+    naxis1 = (fptr->Fptr)->rowlength;   /* current width of the table */
+    naxis2 = (fptr->Fptr)->numrows;
+
+    /* current size of table */
+    size = (fptr->Fptr)->heapstart + (fptr->Fptr)->heapsize;
+    freespace = ((LONGLONG)delbyte * naxis2) + ((size + 2879) / 2880) * 2880 - size;
+    nblock = (long) (freespace / 2880);   /* number of empty blocks to delete */
+
+    ffcdel(fptr, naxis1, naxis2, delbyte, firstcol, status); /* delete col */
+
+    /* absolute heap position */
+    firstbyte = (fptr->Fptr)->datastart + (fptr->Fptr)->heapstart;
+    ndelete = (LONGLONG)delbyte * naxis2; /* size of shift */
+
+    /* shift heap up (if it exists) */
+    if ((fptr->Fptr)->heapsize > 0)
+    {
+      nbytes = (fptr->Fptr)->heapsize;    /* no. of bytes to shift up */
+
+      if (ffshft(fptr, firstbyte, nbytes, -ndelete, status) > 0) /* mv heap */
+          return(*status);
+    }
+
+    /* delete the empty  blocks at the end of the HDU */
+    if (nblock > 0)
+        ffdblk(fptr, nblock, status);
+
+    /* update the heap starting address */
+    (fptr->Fptr)->heapstart -= ndelete;
+
+    /* update the THEAP keyword if it exists */
+    tstatus = 0;
+    ffmkyj(fptr, "THEAP", (long)(fptr->Fptr)->heapstart, "&", &tstatus);
+
+    if ((fptr->Fptr)->hdutype == ASCII_TBL)
+    {
+      /* adjust the TBCOL values of the remaining columns */
+      for (ii = 1; ii <= (fptr->Fptr)->tfield; ii++)
+      {
+        ffkeyn("TBCOL", ii, keyname, status);
+        ffgkyjj(fptr, keyname, &tbcol, comm, status);
+        if (tbcol > firstcol)
+        {
+          tbcol = tbcol - delbyte;
+          ffmkyj(fptr, keyname, tbcol, "&", status);
+        }
+      }
+    }
+
+    /* update the mandatory keywords */
+    ffmkyj(fptr, "TFIELDS", ((fptr->Fptr)->tfield) - 1, "&", status);        
+    ffmkyj(fptr,  "NAXIS1",   naxis1 - delbyte, "&", status);
+    /*
+      delete the index keywords starting with 'T' associated with the 
+      deleted column and subtract 1 from index of all higher keywords
+    */
+    ffkshf(fptr, colnum, (fptr->Fptr)->tfield, -1, status);
+
+    ffrdef(fptr, status);  /* initialize the new table structure */
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffcins(fitsfile *fptr,  /* I - FITS file pointer                        */
+           LONGLONG naxis1,     /* I - width of the table, in bytes             */
+           LONGLONG naxis2,     /* I - number of rows in the table              */
+           LONGLONG ninsert,    /* I - number of bytes to insert in each row    */
+           LONGLONG bytepos,    /* I - rel. position in row to insert bytes     */
+           int *status)     /* IO - error status                            */
+/*
+ Insert 'ninsert' bytes into each row of the table at position 'bytepos'.
+*/
+{
+    unsigned char buffer[10000], cfill;
+    LONGLONG newlen, fbyte, nbytes, irow, nseg, ii;
+
+    if (*status > 0)
+        return(*status);
+
+    if (naxis2 == 0)
+        return(*status);  /* just return if there are 0 rows in the table */
+
+    /* select appropriate fill value */
+    if ((fptr->Fptr)->hdutype == ASCII_TBL)
+        cfill = 32;                     /* ASCII tables use blank fill */
+    else
+        cfill = 0;    /* primary array and binary tables use zero fill */
+
+    newlen = naxis1 + ninsert;
+
+    if (newlen <= 10000)
+    {
+       /*******************************************************************
+       CASE #1: optimal case where whole new row fits in the work buffer
+       *******************************************************************/
+
+        for (ii = 0; ii < ninsert; ii++)
+            buffer[ii] = cfill;      /* initialize buffer with fill value */
+
+        /* first move the trailing bytes (if any) in the last row */
+        fbyte = bytepos + 1;
+        nbytes = naxis1 - bytepos;
+        ffgtbb(fptr, naxis2, fbyte, nbytes, &buffer[ninsert], status);
+        (fptr->Fptr)->rowlength = newlen; /*  new row length */
+
+        /* write the row (with leading fill bytes) in the new place */
+        nbytes += ninsert;
+        ffptbb(fptr, naxis2, fbyte, nbytes, buffer, status);
+        (fptr->Fptr)->rowlength = naxis1;  /* reset to orig. value */
+
+        /*  now move the rest of the rows */
+        for (irow = naxis2 - 1; irow > 0; irow--)
+        {
+            /* read the row to be shifted (work backwards thru the table) */
+            ffgtbb(fptr, irow, fbyte, naxis1, &buffer[ninsert], status);
+            (fptr->Fptr)->rowlength = newlen; /* new row length */
+
+            /* write the row (with the leading fill bytes) in the new place */
+            ffptbb(fptr, irow, fbyte, newlen, buffer, status);
+            (fptr->Fptr)->rowlength = naxis1; /* reset to orig value */
+        }
+    }
+    else
+    {
+        /*****************************************************************
+        CASE #2:  whole row doesn't fit in work buffer; move row in pieces
+        ******************************************************************
+        first copy the data, then go back and write fill into the new column
+        start by copying the trailing bytes (if any) in the last row.     */
+
+        nbytes = naxis1 - bytepos;
+        nseg = (nbytes + 9999) / 10000;
+        fbyte = (nseg - 1) * 10000 + bytepos + 1;
+        nbytes = naxis1 - fbyte + 1;
+
+        for (ii = 0; ii < nseg; ii++)
+        {
+            ffgtbb(fptr, naxis2, fbyte, nbytes, buffer, status);
+            (fptr->Fptr)->rowlength =   newlen;  /* new row length */
+
+            ffptbb(fptr, naxis2, fbyte + ninsert, nbytes, buffer, status);
+            (fptr->Fptr)->rowlength =   naxis1; /* reset to orig value */
+
+            fbyte -= 10000;
+            nbytes = 10000;
+        }
+
+        /* now move the rest of the rows */
+        nseg = (naxis1 + 9999) / 10000;
+        for (irow = naxis2 - 1; irow > 0; irow--)
+        {
+          fbyte = (nseg - 1) * 10000 + bytepos + 1;
+          nbytes = naxis1 - (nseg - 1) * 10000;
+          for (ii = 0; ii < nseg; ii++)
+          { 
+            /* read the row to be shifted (work backwards thru the table) */
+            ffgtbb(fptr, irow, fbyte, nbytes, buffer, status);
+            (fptr->Fptr)->rowlength =   newlen;  /* new row length */
+
+            /* write the row in the new place */
+            ffptbb(fptr, irow, fbyte + ninsert, nbytes, buffer, status);
+            (fptr->Fptr)->rowlength =   naxis1; /* reset to orig value */
+
+            fbyte -= 10000;
+            nbytes = 10000;
+          }
+        }
+
+        /* now write the fill values into the new column */
+        nbytes = minvalue(ninsert, 10000);
+        memset(buffer, cfill, (size_t) nbytes); /* initialize with fill value */
+
+        nseg = (ninsert + 9999) / 10000;
+        (fptr->Fptr)->rowlength =  newlen;  /* new row length */
+
+        for (irow = 1; irow <= naxis2; irow++)
+        {
+          fbyte = bytepos + 1;
+          nbytes = ninsert - ((nseg - 1) * 10000);
+          for (ii = 0; ii < nseg; ii++)
+          {
+            ffptbb(fptr, irow, fbyte, nbytes, buffer, status);
+            fbyte += nbytes;
+            nbytes = 10000;
+          }
+        }
+        (fptr->Fptr)->rowlength = naxis1;  /* reset to orig value */
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffcdel(fitsfile *fptr,  /* I - FITS file pointer                        */
+           LONGLONG naxis1,     /* I - width of the table, in bytes             */
+           LONGLONG naxis2,     /* I - number of rows in the table              */
+           LONGLONG ndelete,    /* I - number of bytes to delete in each row    */
+           LONGLONG bytepos,    /* I - rel. position in row to delete bytes     */
+           int *status)     /* IO - error status                            */
+/*
+ delete 'ndelete' bytes from each row of the table at position 'bytepos'.  */
+{
+    unsigned char buffer[10000];
+    LONGLONG i1, i2, ii, irow, nseg;
+    LONGLONG newlen, remain, nbytes;
+
+    if (*status > 0)
+        return(*status);
+
+    if (naxis2 == 0)
+        return(*status);  /* just return if there are 0 rows in the table */
+
+    newlen = naxis1 - ndelete;
+
+    if (newlen <= 10000)
+    {
+      /*******************************************************************
+      CASE #1: optimal case where whole new row fits in the work buffer
+      *******************************************************************/
+      i1 = bytepos + 1;
+      i2 = i1 + ndelete;
+      for (irow = 1; irow < naxis2; irow++)
+      {
+        ffgtbb(fptr, irow, i2, newlen, buffer, status); /* read row */
+        (fptr->Fptr)->rowlength = newlen;  /* new row length */
+
+        ffptbb(fptr, irow, i1, newlen, buffer, status); /* write row */
+        (fptr->Fptr)->rowlength = naxis1;  /* reset to orig value */
+      }
+
+      /* now do the last row */
+      remain = naxis1 - (bytepos + ndelete);
+
+      if (remain > 0)
+      {
+        ffgtbb(fptr, naxis2, i2, remain, buffer, status); /* read row */
+        (fptr->Fptr)->rowlength = newlen;  /* new row length */
+
+        ffptbb(fptr, naxis2, i1, remain, buffer, status); /* write row */
+        (fptr->Fptr)->rowlength = naxis1;  /* reset to orig value */
+      }
+    }
+    else
+    {
+        /*****************************************************************
+        CASE #2:  whole row doesn't fit in work buffer; move row in pieces
+        ******************************************************************/
+
+        nseg = (newlen + 9999) / 10000;
+        for (irow = 1; irow < naxis2; irow++)
+        {
+          i1 = bytepos + 1;
+          i2 = i1 + ndelete;
+
+          nbytes = newlen - (nseg - 1) * 10000;
+          for (ii = 0; ii < nseg; ii++)
+          { 
+            ffgtbb(fptr, irow, i2, nbytes, buffer, status); /* read bytes */
+            (fptr->Fptr)->rowlength = newlen;  /* new row length */
+
+            ffptbb(fptr, irow, i1, nbytes, buffer, status); /* rewrite bytes */
+            (fptr->Fptr)->rowlength = naxis1; /* reset to orig value */
+
+            i1 += nbytes;
+            i2 += nbytes;
+            nbytes = 10000;
+          }
+        }
+
+        /* now do the last row */
+        remain = naxis1 - (bytepos + ndelete);
+
+        if (remain > 0)
+        {
+          nseg = (remain + 9999) / 10000;
+          i1 = bytepos + 1;
+          i2 = i1 + ndelete;
+          nbytes = remain - (nseg - 1) * 10000;
+          for (ii = 0; ii < nseg; ii++)
+          { 
+            ffgtbb(fptr, naxis2, i2, nbytes, buffer, status);
+            (fptr->Fptr)->rowlength = newlen;  /* new row length */
+
+            ffptbb(fptr, naxis2, i1, nbytes, buffer, status); /* write row */
+            (fptr->Fptr)->rowlength = naxis1;  /* reset to orig value */
+
+            i1 += nbytes;
+            i2 += nbytes;
+            nbytes = 10000;
+          }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffkshf(fitsfile *fptr,  /* I - FITS file pointer                        */
+           int colmin,      /* I - starting col. to be incremented; 1 = 1st */
+           int colmax,      /* I - last column to be incremented            */
+           int incre,       /* I - shift index number by this amount        */
+           int *status)     /* IO - error status                            */
+/*
+  shift the index value on any existing column keywords
+  This routine will modify the name of any keyword that begins with 'T'
+  and has an index number in the range COLMIN - COLMAX, inclusive.
+
+  if incre is positive, then the index values will be incremented.
+  if incre is negative, then the kewords with index = COLMIN
+  will be deleted and the index of higher numbered keywords will
+  be decremented.
+*/
+{
+    int nkeys, nmore, nrec, tstatus, i1;
+    long ivalue;
+    char rec[FLEN_CARD], q[FLEN_KEYWORD], newkey[FLEN_KEYWORD];
+
+    ffghsp(fptr, &nkeys, &nmore, status);  /* get number of keywords */
+
+    /* go thru header starting with the 9th keyword looking for 'TxxxxNNN' */
+
+    for (nrec = 9; nrec <= nkeys; nrec++)
+    {     
+        ffgrec(fptr, nrec, rec, status);
+
+        if (rec[0] == 'T')
+        {
+            i1 = 0;
+            strncpy(q, &rec[1], 4);
+            if (!strncmp(q, "BCOL", 4) || !strncmp(q, "FORM", 4) ||
+                !strncmp(q, "TYPE", 4) || !strncmp(q, "SCAL", 4) ||
+                !strncmp(q, "UNIT", 4) || !strncmp(q, "NULL", 4) ||
+                !strncmp(q, "ZERO", 4) || !strncmp(q, "DISP", 4) ||
+                !strncmp(q, "LMIN", 4) || !strncmp(q, "LMAX", 4) ||
+                !strncmp(q, "DMIN", 4) || !strncmp(q, "DMAX", 4) ||
+                !strncmp(q, "CTYP", 4) || !strncmp(q, "CRPX", 4) ||
+                !strncmp(q, "CRVL", 4) || !strncmp(q, "CDLT", 4) ||
+                !strncmp(q, "CROT", 4) || !strncmp(q, "CUNI", 4) )
+              i1 = 5;
+            else if (!strncmp(rec, "TDIM", 4) )
+              i1 = 4;
+
+            if (i1)
+            {
+              /* try reading the index number suffix */
+              q[0] = '\0';
+              strncat(q, &rec[i1], 8 - i1);
+
+              tstatus = 0;
+              ffc2ii(q, &ivalue, &tstatus);
+
+              if (tstatus == 0 && ivalue >= colmin && ivalue <= colmax)
+              {
+                if (incre <= 0 && ivalue == colmin)       
+                {
+                  ffdrec(fptr, nrec, status); /* delete keyword */
+                  nkeys = nkeys - 1;
+                  nrec = nrec - 1;
+                }
+                else
+                {
+                  ivalue = ivalue + incre;
+                  q[0] = '\0';
+                  strncat(q, rec, i1);
+     
+                  ffkeyn(q, ivalue, newkey, status);
+                  strncpy(rec, "        ", 8);    /* erase old keyword name */
+                  i1 = strlen(newkey);
+                  strncpy(rec, newkey, i1);   /* overwrite new keyword name */
+                  ffmrec(fptr, nrec, rec, status);  /* modify the record */
+                }
+              }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffshft(fitsfile *fptr,  /* I - FITS file pointer                        */
+           LONGLONG firstbyte, /* I - position of first byte in block to shift */
+           LONGLONG nbytes,    /* I - size of block of bytes to shift          */
+           LONGLONG nshift,    /* I - size of shift in bytes (+ or -)          */
+           int *status)     /* IO - error status                            */
+/*
+    Shift block of bytes by nshift bytes (positive or negative).
+    A positive nshift value moves the block down further in the file, while a
+    negative value shifts the block towards the beginning of the file.
+*/
+{
+#define shftbuffsize 100000
+    long ntomov;
+    LONGLONG ptr, ntodo;
+    char buffer[shftbuffsize];
+
+    if (*status > 0)
+        return(*status);
+
+    ntodo = nbytes;   /* total number of bytes to shift */
+
+    if (nshift > 0)
+            /* start at the end of the block and work backwards */
+            ptr = firstbyte + nbytes;
+    else
+            /* start at the beginning of the block working forwards */
+            ptr = firstbyte;
+
+    while (ntodo)
+    {
+        /* number of bytes to move at one time */
+        ntomov = (long) (minvalue(ntodo, shftbuffsize));
+
+        if (nshift > 0)     /* if moving block down ... */
+            ptr -= ntomov;
+
+        /* move to position and read the bytes to be moved */
+
+        ffmbyt(fptr, ptr, REPORT_EOF, status);
+        ffgbyt(fptr, ntomov, buffer, status);
+
+        /* move by shift amount and write the bytes */
+        ffmbyt(fptr, ptr + nshift, IGNORE_EOF, status);
+        if (ffpbyt(fptr, ntomov, buffer, status) > 0)
+        {
+           ffpmsg("Error while shifting block (ffshft)");
+           return(*status);
+        }
+
+        ntodo -= ntomov;
+        if (nshift < 0)     /* if moving block up ... */
+            ptr += ntomov;
+    }
+
+    /* now overwrite the old data with fill */
+    if ((fptr->Fptr)->hdutype == ASCII_TBL)
+       memset(buffer, 32, shftbuffsize); /* fill ASCII tables with spaces */
+    else
+       memset(buffer,  0, shftbuffsize); /* fill other HDUs with zeros */
+
+
+    if (nshift < 0)
+    {
+        ntodo = -nshift;
+        /* point to the end of the shifted block */
+        ptr = firstbyte + nbytes + nshift;
+    }
+    else
+    {
+        ntodo = nshift;
+        /* point to original beginning of the block */
+        ptr = firstbyte;
+    }
+
+    ffmbyt(fptr, ptr, REPORT_EOF, status);
+
+    while (ntodo)
+    {
+        ntomov = (long) (minvalue(ntodo, shftbuffsize));
+        ffpbyt(fptr, ntomov, buffer, status);
+        ntodo -= ntomov;
+    }
+    return(*status);
+}
diff --git a/src/plugins/cfitsio/edithdu.c b/src/plugins/cfitsio/edithdu.c
new file mode 100644
index 0000000..385bbe9
--- /dev/null
+++ b/src/plugins/cfitsio/edithdu.c
@@ -0,0 +1,883 @@
+/*  This file, edithdu.c, contains the FITSIO routines related to       */
+/*  copying, inserting, or deleting HDUs in a FITS file                 */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+
+#include <string.h>
+#include <stdlib.h>
+#include "fitsio2.h"
+/*--------------------------------------------------------------------------*/
+int ffcopy(fitsfile *infptr,    /* I - FITS file pointer to input file  */
+           fitsfile *outfptr,   /* I - FITS file pointer to output file */
+           int morekeys,        /* I - reserve space in output header   */
+           int *status)         /* IO - error status     */
+/*
+  copy the CHDU from infptr to the CHDU of outfptr.
+  This will also allocate space in the output header for MOREKY keywords
+*/
+{
+    int nspace;
+    
+    if (*status > 0)
+        return(*status);
+
+    if (infptr == outfptr)
+        return(*status = SAME_FILE);
+
+    if (ffcphd(infptr, outfptr, status) )  /* copy the header keywords */
+       return(*status);
+
+    if (morekeys > 0) {
+      ffhdef(outfptr, morekeys, status); /* reserve space for more keywords */
+
+    } else {
+        if (ffghsp(infptr, NULL, &nspace, status) > 0) /* get existing space */
+            return(*status);
+
+        if (nspace > 0) {
+            ffhdef(outfptr, nspace, status); /* preserve same amount of space */
+            if (nspace >= 35) {  
+
+	        /* There is at least 1 full empty FITS block in the header. */
+	        /* Physically write the END keyword at the beginning of the */
+	        /* last block to preserve this extra space now rather than */
+		/* later.  This is needed by the stream: driver which cannot */
+		/* seek back to the header to write the END keyword later. */
+
+	        ffwend(outfptr, status);
+            }
+        }
+    }
+
+    ffcpdt(infptr, outfptr, status);  /* now copy the data unit */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffcpfl(fitsfile *infptr,    /* I - FITS file pointer to input file  */
+           fitsfile *outfptr,   /* I - FITS file pointer to output file */
+           int previous,        /* I - copy any previous HDUs?   */
+           int current,         /* I - copy the current HDU?     */
+           int following,       /* I - copy any following HDUs?   */
+           int *status)         /* IO - error status     */
+/*
+  copy all or part of the input file to the output file.
+*/
+{
+    int hdunum, ii;
+
+    if (*status > 0)
+        return(*status);
+
+    if (infptr == outfptr)
+        return(*status = SAME_FILE);
+
+    ffghdn(infptr, &hdunum);
+
+    if (previous) {   /* copy any previous HDUs */
+        for (ii=1; ii < hdunum; ii++) {
+            ffmahd(infptr, ii, NULL, status);
+            ffcopy(infptr, outfptr, 0, status);
+        }
+    }
+
+    if (current && (*status <= 0) ) {  /* copy current HDU */
+        ffmahd(infptr, hdunum, NULL, status);
+        ffcopy(infptr, outfptr, 0, status);
+    }
+
+    if (following && (*status <= 0) ) { /* copy any remaining HDUs */
+        ii = hdunum + 1;
+        while (1)
+        { 
+            if (ffmahd(infptr, ii, NULL, status) ) {
+                 /* reset expected end of file status */
+                 if (*status == END_OF_FILE)
+                    *status = 0;
+                 break;
+            }
+
+            if (ffcopy(infptr, outfptr, 0, status))
+                 break;  /* quit on unexpected error */
+
+            ii++;
+        }
+    }
+
+    ffmahd(infptr, hdunum, NULL, status);  /* restore initial position */
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffcphd(fitsfile *infptr,    /* I - FITS file pointer to input file  */
+           fitsfile *outfptr,   /* I - FITS file pointer to output file */
+           int *status)         /* IO - error status     */
+/*
+  copy the header keywords from infptr to outfptr.
+*/
+{
+    int nkeys, ii, inPrim = 0, outPrim = 0;
+    long naxis, naxes[1];
+    char *card, comm[FLEN_COMMENT];
+    char *tmpbuff;
+
+    if (*status > 0)
+        return(*status);
+
+    if (infptr == outfptr)
+        return(*status = SAME_FILE);
+
+    /* set the input pointer to the correct HDU */
+    if (infptr->HDUposition != (infptr->Fptr)->curhdu)
+        ffmahd(infptr, (infptr->HDUposition) + 1, NULL, status);
+
+    if (ffghsp(infptr, &nkeys, NULL, status) > 0) /* get no. of keywords */
+        return(*status);
+
+    /* create a memory buffer to hold the header records */
+    tmpbuff = (char*) malloc(nkeys*FLEN_CARD*sizeof(char));
+    if (!tmpbuff)
+        return(*status = MEMORY_ALLOCATION);
+
+    /* read all of the header records in the input HDU */
+    for (ii = 0; ii < nkeys; ii++)
+      ffgrec(infptr, ii+1, tmpbuff + (ii * FLEN_CARD), status);
+
+    if (infptr->HDUposition == 0)  /* set flag if this is the Primary HDU */
+       inPrim = 1;
+
+    /* if input is an image hdu, get the number of axes */
+    naxis = -1;   /* negative if HDU is a table */
+    if ((infptr->Fptr)->hdutype == IMAGE_HDU)
+        ffgkyj(infptr, "NAXIS", &naxis, NULL, status);
+
+    /* set the output pointer to the correct HDU */
+    if (outfptr->HDUposition != (outfptr->Fptr)->curhdu)
+        ffmahd(outfptr, (outfptr->HDUposition) + 1, NULL, status);
+
+    /* check if output header is empty; if not create new empty HDU */
+    if ((outfptr->Fptr)->headend !=
+        (outfptr->Fptr)->headstart[(outfptr->Fptr)->curhdu] )
+           ffcrhd(outfptr, status);   
+
+    if (outfptr->HDUposition == 0)
+    {
+        if (naxis < 0)
+        {
+            /* the input HDU is a table, so we have to create */
+            /* a dummy Primary array before copying it to the output */
+            ffcrim(outfptr, 8, 0, naxes, status);
+            ffcrhd(outfptr, status); /* create new empty HDU */
+        }
+        else
+        {
+            /* set flag that this is the Primary HDU */
+            outPrim = 1;
+        }
+    }
+
+    if (*status > 0)  /* check for errors before proceeding */
+    {
+        free(tmpbuff);
+        return(*status);
+    }
+    if ( inPrim == 1 && outPrim == 0 )
+    {
+        /* copying from primary array to image extension */
+        strcpy(comm, "IMAGE extension");
+        ffpkys(outfptr, "XTENSION", "IMAGE", comm, status);
+
+        /* copy BITPIX through NAXISn keywords */
+        for (ii = 1; ii < 3 + naxis; ii++)
+        {
+            card = tmpbuff + (ii * FLEN_CARD);
+            ffprec(outfptr, card, status);
+        }
+
+        strcpy(comm, "number of random group parameters");
+        ffpkyj(outfptr, "PCOUNT", 0, comm, status);
+  
+        strcpy(comm, "number of random groups");
+        ffpkyj(outfptr, "GCOUNT", 1, comm, status);
+
+
+        /* copy remaining keywords, excluding EXTEND, and reference COMMENT keywords */
+        for (ii = 3 + naxis ; ii < nkeys; ii++)
+        {
+            card = tmpbuff+(ii * FLEN_CARD);
+            if (FSTRNCMP(card, "EXTEND  ", 8) &&
+                FSTRNCMP(card, "COMMENT   FITS (Flexible Image Transport System) format is", 58) && 
+                FSTRNCMP(card, "COMMENT   and Astrophysics', volume 376, page 3", 47) )
+            {
+                 ffprec(outfptr, card, status);
+            }
+        }
+    }
+    else if ( inPrim == 0 && outPrim == 1 )
+    {
+        /* copying between image extension and primary array */
+        strcpy(comm, "file does conform to FITS standard");
+        ffpkyl(outfptr, "SIMPLE", TRUE, comm, status);
+
+        /* copy BITPIX through NAXISn keywords */
+        for (ii = 1; ii < 3 + naxis; ii++)
+        {
+            card = tmpbuff + (ii * FLEN_CARD);
+            ffprec(outfptr, card, status);
+        }
+
+        /* add the EXTEND keyword */
+        strcpy(comm, "FITS dataset may contain extensions");
+        ffpkyl(outfptr, "EXTEND", TRUE, comm, status);
+
+      /* write standard block of self-documentating comments */
+      ffprec(outfptr,
+      "COMMENT   FITS (Flexible Image Transport System) format is defined in 'Astronomy",
+      status);
+      ffprec(outfptr,
+      "COMMENT   and Astrophysics', volume 376, page 359; bibcode: 2001A&A...376..359H",
+      status);
+
+        /* copy remaining keywords, excluding pcount, gcount */
+        for (ii = 3 + naxis; ii < nkeys; ii++)
+        {
+            card = tmpbuff+(ii * FLEN_CARD);
+            if (FSTRNCMP(card, "PCOUNT  ", 8) && FSTRNCMP(card, "GCOUNT  ", 8))
+            {
+                 ffprec(outfptr, card, status);
+            }
+        }
+    }
+    else
+    {
+        /* input and output HDUs are same type; simply copy all keywords */
+        for (ii = 0; ii < nkeys; ii++)
+        {
+            card = tmpbuff+(ii * FLEN_CARD);
+            ffprec(outfptr, card, status);
+        }
+    }
+
+    free(tmpbuff);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffcpdt(fitsfile *infptr,    /* I - FITS file pointer to input file  */
+           fitsfile *outfptr,   /* I - FITS file pointer to output file */
+           int *status)         /* IO - error status     */
+{
+/*
+  copy the data unit from the CHDU of infptr to the CHDU of outfptr. 
+  This will overwrite any data already in the outfptr CHDU.
+*/
+    long nb, ii;
+    LONGLONG indatastart, indataend, outdatastart;
+    char buffer[2880];
+
+    if (*status > 0)
+        return(*status);
+
+    if (infptr == outfptr)
+        return(*status = SAME_FILE);
+
+    ffghadll(infptr,  NULL, &indatastart, &indataend, status);
+    ffghadll(outfptr, NULL, &outdatastart, NULL, status);
+
+    /* Calculate the number of blocks to be copied  */
+    nb = (long) ((indataend - indatastart) / 2880);
+
+    if (nb > 0)
+    {
+      if (infptr->Fptr == outfptr->Fptr)
+      {
+        /* copying between 2 HDUs in the SAME file */
+        for (ii = 0; ii < nb; ii++)
+        {
+            ffmbyt(infptr,  indatastart,  REPORT_EOF, status);
+            ffgbyt(infptr,  2880L, buffer, status); /* read input block */
+
+            ffmbyt(outfptr, outdatastart, IGNORE_EOF, status);
+            ffpbyt(outfptr, 2880L, buffer, status); /* write output block */
+
+            indatastart  += 2880; /* move address */
+            outdatastart += 2880; /* move address */
+        }
+      }
+      else
+      {
+        /* copying between HDUs in separate files */
+        /* move to the initial copy position in each of the files */
+        ffmbyt(infptr,  indatastart,  REPORT_EOF, status);
+        ffmbyt(outfptr, outdatastart, IGNORE_EOF, status);
+
+        for (ii = 0; ii < nb; ii++)
+        {
+            ffgbyt(infptr,  2880L, buffer, status); /* read input block */
+            ffpbyt(outfptr, 2880L, buffer, status); /* write output block */
+        }
+      }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffwrhdu(fitsfile *infptr,    /* I - FITS file pointer to input file  */
+            FILE *outstream,     /* I - stream to write HDU to */
+            int *status)         /* IO - error status     */
+{
+/*
+  write the data unit from the CHDU of infptr to the output file stream
+*/
+    long nb, ii;
+    LONGLONG hdustart, hduend;
+    char buffer[2880];
+
+    if (*status > 0)
+        return(*status);
+
+    ffghadll(infptr, &hdustart,  NULL, &hduend, status);
+
+    nb = (long) ((hduend - hdustart) / 2880);  /* number of blocks to copy */
+
+    if (nb > 0)
+    {
+
+        /* move to the start of the HDU */
+        ffmbyt(infptr,  hdustart,  REPORT_EOF, status);
+
+        for (ii = 0; ii < nb; ii++)
+        {
+            ffgbyt(infptr,  2880L, buffer, status); /* read input block */
+            fwrite(buffer, 1, 2880, outstream ); /* write to output stream */
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffiimg(fitsfile *fptr,      /* I - FITS file pointer           */
+           int bitpix,          /* I - bits per pixel              */
+           int naxis,           /* I - number of axes in the array */
+           long *naxes,         /* I - size of each axis           */
+           int *status)         /* IO - error status               */
+/*
+  insert an IMAGE extension following the current HDU 
+*/
+{
+    LONGLONG tnaxes[99];
+    int ii;
+    
+    if (*status > 0)
+        return(*status);
+
+    if (naxis > 99) {
+        ffpmsg("NAXIS value is too large (>99)  (ffiimg)");
+	return(*status = 212);
+    }
+
+    for (ii = 0; (ii < naxis); ii++)
+       tnaxes[ii] = naxes[ii];
+       
+    ffiimgll(fptr, bitpix, naxis, tnaxes, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffiimgll(fitsfile *fptr,    /* I - FITS file pointer           */
+           int bitpix,          /* I - bits per pixel              */
+           int naxis,           /* I - number of axes in the array */
+           LONGLONG *naxes,     /* I - size of each axis           */
+           int *status)         /* IO - error status               */
+/*
+  insert an IMAGE extension following the current HDU 
+*/
+{
+    int bytlen, nexthdu, maxhdu, ii, onaxis;
+    long nblocks;
+    LONGLONG npixels, newstart, datasize;
+    char errmsg[FLEN_ERRMSG], card[FLEN_CARD], naxiskey[FLEN_KEYWORD];
+
+    if (*status > 0)
+        return(*status);
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    maxhdu = (fptr->Fptr)->maxhdu;
+
+    if (*status != PREPEND_PRIMARY)
+    {
+      /* if the current header is completely empty ...  */
+      if (( (fptr->Fptr)->headend == (fptr->Fptr)->headstart[(fptr->Fptr)->curhdu])
+        /* or, if we are at the end of the file, ... */
+      ||  ( (((fptr->Fptr)->curhdu) == maxhdu ) &&
+       ((fptr->Fptr)->headstart[maxhdu + 1] >= (fptr->Fptr)->logfilesize ) ) )
+      {
+        /* then simply append new image extension */
+        ffcrimll(fptr, bitpix, naxis, naxes, status);
+        return(*status);
+      }
+    }
+
+    if (bitpix == 8)
+        bytlen = 1;
+    else if (bitpix == 16)
+        bytlen = 2;
+    else if (bitpix == 32 || bitpix == -32)
+        bytlen = 4;
+    else if (bitpix == 64 || bitpix == -64)
+        bytlen = 8;
+    else
+    {
+        sprintf(errmsg,
+        "Illegal value for BITPIX keyword: %d", bitpix);
+        ffpmsg(errmsg);
+        return(*status = BAD_BITPIX);  /* illegal bitpix value */
+    }
+    if (naxis < 0 || naxis > 999)
+    {
+        sprintf(errmsg,
+        "Illegal value for NAXIS keyword: %d", naxis);
+        ffpmsg(errmsg);
+        return(*status = BAD_NAXIS);
+    }
+
+    for (ii = 0; ii < naxis; ii++)
+    {
+        if (naxes[ii] < 0)
+        {
+            sprintf(errmsg,
+            "Illegal value for NAXIS%d keyword: %ld", ii + 1,  (long) naxes[ii]);
+            ffpmsg(errmsg);
+            return(*status = BAD_NAXES);
+        }
+    }
+
+    /* calculate number of pixels in the image */
+    if (naxis == 0)
+        npixels = 0;
+    else 
+        npixels = naxes[0];
+
+    for (ii = 1; ii < naxis; ii++)
+        npixels = npixels * naxes[ii];
+
+    datasize = npixels * bytlen;          /* size of image in bytes */
+    nblocks = (long) (((datasize + 2879) / 2880) + 1);  /* +1 for the header */
+
+    if ((fptr->Fptr)->writemode == READWRITE) /* must have write access */
+    {   /* close the CHDU */
+        ffrdef(fptr, status);  /* scan header to redefine structure */
+        ffpdfl(fptr, status);  /* insure correct data file values */
+    }
+    else
+        return(*status = READONLY_FILE);
+
+    if (*status == PREPEND_PRIMARY)
+    {
+        /* inserting a new primary array; the current primary */
+        /* array must be transformed into an image extension. */
+
+        *status = 0;   
+        ffmahd(fptr, 1, NULL, status);  /* move to the primary array */
+
+        ffgidm(fptr, &onaxis, status);
+        if (onaxis > 0)
+            ffkeyn("NAXIS",onaxis, naxiskey, status);
+        else
+            strcpy(naxiskey, "NAXIS");
+
+        ffgcrd(fptr, naxiskey, card, status);  /* read last NAXIS keyword */
+        
+        ffikyj(fptr, "PCOUNT", 0, "required keyword", status); /* add PCOUNT and */
+        ffikyj(fptr, "GCOUNT", 1, "required keyword", status); /* GCOUNT keywords */
+
+        if (*status > 0)
+            return(*status);
+
+        if (ffdkey(fptr, "EXTEND", status) ) /* delete the EXTEND keyword */
+            *status = 0;
+
+        /* redefine internal structure for this HDU */
+        ffrdef(fptr, status);
+
+
+        /* insert space for the primary array */
+        if (ffiblk(fptr, nblocks, -1, status) > 0)  /* insert the blocks */
+            return(*status);
+
+        nexthdu = 0;  /* number of the new hdu */
+        newstart = 0; /* starting addr of HDU */
+    }
+    else
+    {
+        nexthdu = ((fptr->Fptr)->curhdu) + 1; /* number of the next (new) hdu */
+        newstart = (fptr->Fptr)->headstart[nexthdu]; /* save starting addr of HDU */
+
+        (fptr->Fptr)->hdutype = IMAGE_HDU;  /* so that correct fill value is used */
+        /* ffiblk also increments headstart for all following HDUs */
+        if (ffiblk(fptr, nblocks, 1, status) > 0)  /* insert the blocks */
+            return(*status);
+    }
+
+    ((fptr->Fptr)->maxhdu)++;      /* increment known number of HDUs in the file */
+    for (ii = (fptr->Fptr)->maxhdu; ii > (fptr->Fptr)->curhdu; ii--)
+        (fptr->Fptr)->headstart[ii + 1] = (fptr->Fptr)->headstart[ii]; /* incre start addr */
+
+    if (nexthdu == 0)
+       (fptr->Fptr)->headstart[1] = nblocks * 2880; /* start of the old Primary array */
+
+    (fptr->Fptr)->headstart[nexthdu] = newstart; /* set starting addr of HDU */
+
+    /* set default parameters for this new empty HDU */
+    (fptr->Fptr)->curhdu = nexthdu;   /* we are now located at the next HDU */
+    fptr->HDUposition = nexthdu;      /* we are now located at the next HDU */
+    (fptr->Fptr)->nextkey = (fptr->Fptr)->headstart[nexthdu];  
+    (fptr->Fptr)->headend = (fptr->Fptr)->headstart[nexthdu];
+    (fptr->Fptr)->datastart = ((fptr->Fptr)->headstart[nexthdu]) + 2880;
+    (fptr->Fptr)->hdutype = IMAGE_HDU;  /* might need to be reset... */
+
+    /* write the required header keywords */
+    ffphprll(fptr, TRUE, bitpix, naxis, naxes, 0, 1, TRUE, status);
+
+    /* redefine internal structure for this HDU */
+    ffrdef(fptr, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffitab(fitsfile *fptr,  /* I - FITS file pointer                        */
+           LONGLONG naxis1,     /* I - width of row in the table                */
+           LONGLONG naxis2,     /* I - number of rows in the table              */
+           int tfields,     /* I - number of columns in the table           */
+           char **ttype,    /* I - name of each column                      */
+           long *tbcol,     /* I - byte offset in row to each column        */
+           char **tform,    /* I - value of TFORMn keyword for each column  */
+           char **tunit,    /* I - value of TUNITn keyword for each column  */
+           const char *extnmx,   /* I - value of EXTNAME keyword, if any         */
+           int *status)     /* IO - error status                            */
+/*
+  insert an ASCII table extension following the current HDU 
+*/
+{
+    int nexthdu, maxhdu, ii, nunit, nhead, ncols, gotmem = 0;
+    long nblocks, rowlen;
+    LONGLONG datasize, newstart;
+    char errmsg[81], extnm[FLEN_VALUE];
+
+    if (*status > 0)
+        return(*status);
+
+    extnm[0] = '\0';
+    if (extnmx)
+      strncat(extnm, extnmx, FLEN_VALUE-1);
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    maxhdu = (fptr->Fptr)->maxhdu;
+    /* if the current header is completely empty ...  */
+    if (( (fptr->Fptr)->headend == (fptr->Fptr)->headstart[(fptr->Fptr)->curhdu] )
+        /* or, if we are at the end of the file, ... */
+    ||  ( (((fptr->Fptr)->curhdu) == maxhdu ) &&
+       ((fptr->Fptr)->headstart[maxhdu + 1] >= (fptr->Fptr)->logfilesize ) ) )
+    {
+        /* then simply append new image extension */
+        ffcrtb(fptr, ASCII_TBL, naxis2, tfields, ttype, tform, tunit,
+               extnm, status);
+        return(*status);
+    }
+
+    if (naxis1 < 0)
+        return(*status = NEG_WIDTH);
+    else if (naxis2 < 0)
+        return(*status = NEG_ROWS);
+    else if (tfields < 0 || tfields > 999)
+    {
+        sprintf(errmsg,
+        "Illegal value for TFIELDS keyword: %d", tfields);
+        ffpmsg(errmsg);
+        return(*status = BAD_TFIELDS);
+    }
+
+    /* count number of optional TUNIT keywords to be written */
+    nunit = 0;
+    for (ii = 0; ii < tfields; ii++)
+    {
+        if (tunit && *tunit && *tunit[ii])
+            nunit++;
+    }
+
+    if (extnm && *extnm)
+         nunit++;     /* add one for the EXTNAME keyword */
+
+    rowlen = (long) naxis1;
+
+    if (!tbcol || !tbcol[0] || (!naxis1 && tfields)) /* spacing not defined? */
+    {
+      /* allocate mem for tbcol; malloc may have problems allocating small */
+      /* arrays, so allocate at least 20 bytes */
+
+      ncols = maxvalue(5, tfields);
+      tbcol = (long *) calloc(ncols, sizeof(long));
+
+      if (tbcol)
+      {
+        gotmem = 1;
+
+        /* calculate width of a row and starting position of each column. */
+        /* Each column will be separated by 1 blank space */
+        ffgabc(tfields, tform, 1, &rowlen, tbcol, status);
+      }
+    }
+
+    nhead = (9 + (3 * tfields) + nunit + 35) / 36;  /* no. of header blocks */
+    datasize = (LONGLONG)rowlen * naxis2;          /* size of table in bytes */
+    nblocks = (long) (((datasize + 2879) / 2880) + nhead);  /* size of HDU */
+
+    if ((fptr->Fptr)->writemode == READWRITE) /* must have write access */
+    {   /* close the CHDU */
+        ffrdef(fptr, status);  /* scan header to redefine structure */
+        ffpdfl(fptr, status);  /* insure correct data file values */
+    }
+    else
+        return(*status = READONLY_FILE);
+
+    nexthdu = ((fptr->Fptr)->curhdu) + 1; /* number of the next (new) hdu */
+    newstart = (fptr->Fptr)->headstart[nexthdu]; /* save starting addr of HDU */
+
+    (fptr->Fptr)->hdutype = ASCII_TBL;  /* so that correct fill value is used */
+    /* ffiblk also increments headstart for all following HDUs */
+    if (ffiblk(fptr, nblocks, 1, status) > 0)  /* insert the blocks */
+    {
+        if (gotmem)
+            free(tbcol); 
+        return(*status);
+    }
+
+    ((fptr->Fptr)->maxhdu)++;      /* increment known number of HDUs in the file */
+    for (ii = (fptr->Fptr)->maxhdu; ii > (fptr->Fptr)->curhdu; ii--)
+        (fptr->Fptr)->headstart[ii + 1] = (fptr->Fptr)->headstart[ii]; /* incre start addr */
+
+    (fptr->Fptr)->headstart[nexthdu] = newstart; /* set starting addr of HDU */
+
+    /* set default parameters for this new empty HDU */
+    (fptr->Fptr)->curhdu = nexthdu;   /* we are now located at the next HDU */
+    fptr->HDUposition = nexthdu;      /* we are now located at the next HDU */
+    (fptr->Fptr)->nextkey = (fptr->Fptr)->headstart[nexthdu];  
+    (fptr->Fptr)->headend = (fptr->Fptr)->headstart[nexthdu];
+    (fptr->Fptr)->datastart = ((fptr->Fptr)->headstart[nexthdu]) + (nhead * 2880);
+    (fptr->Fptr)->hdutype = ASCII_TBL;  /* might need to be reset... */
+
+    /* write the required header keywords */
+
+    ffphtb(fptr, rowlen, naxis2, tfields, ttype, tbcol, tform, tunit,
+           extnm, status);
+
+    if (gotmem)
+        free(tbcol); 
+
+    /* redefine internal structure for this HDU */
+
+    ffrdef(fptr, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffibin(fitsfile *fptr,  /* I - FITS file pointer                        */
+           LONGLONG naxis2,     /* I - number of rows in the table              */
+           int tfields,     /* I - number of columns in the table           */
+           char **ttype,    /* I - name of each column                      */
+           char **tform,    /* I - value of TFORMn keyword for each column  */
+           char **tunit,    /* I - value of TUNITn keyword for each column  */
+           const char *extnmx,     /* I - value of EXTNAME keyword, if any         */
+           LONGLONG pcount, /* I - size of special data area (heap)         */
+           int *status)     /* IO - error status                            */
+/*
+  insert a Binary table extension following the current HDU 
+*/
+{
+    int nexthdu, maxhdu, ii, nunit, nhead, datacode;
+    LONGLONG naxis1;
+    long nblocks, repeat, width;
+    LONGLONG datasize, newstart;
+    char errmsg[81], extnm[FLEN_VALUE];
+
+    if (*status > 0)
+        return(*status);
+
+    extnm[0] = '\0';
+    if (extnmx)
+      strncat(extnm, extnmx, FLEN_VALUE-1);
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    maxhdu = (fptr->Fptr)->maxhdu;
+    /* if the current header is completely empty ...  */
+    if (( (fptr->Fptr)->headend == (fptr->Fptr)->headstart[(fptr->Fptr)->curhdu] )
+        /* or, if we are at the end of the file, ... */
+    ||  ( (((fptr->Fptr)->curhdu) == maxhdu ) &&
+       ((fptr->Fptr)->headstart[maxhdu + 1] >= (fptr->Fptr)->logfilesize ) ) )
+    {
+        /* then simply append new image extension */
+        ffcrtb(fptr, BINARY_TBL, naxis2, tfields, ttype, tform, tunit,
+               extnm, status);
+        return(*status);
+    }
+
+    if (naxis2 < 0)
+        return(*status = NEG_ROWS);
+    else if (tfields < 0 || tfields > 999)
+    {
+        sprintf(errmsg,
+        "Illegal value for TFIELDS keyword: %d", tfields);
+        ffpmsg(errmsg);
+        return(*status = BAD_TFIELDS);
+    }
+
+    /* count number of optional TUNIT keywords to be written */
+    nunit = 0;
+    for (ii = 0; ii < tfields; ii++)
+    {
+        if (tunit && *tunit && *tunit[ii])
+            nunit++;
+    }
+
+    if (extnm && *extnm)
+         nunit++;     /* add one for the EXTNAME keyword */
+
+    nhead = (9 + (2 * tfields) + nunit + 35) / 36;  /* no. of header blocks */
+
+    /* calculate total width of the table */
+    naxis1 = 0;
+    for (ii = 0; ii < tfields; ii++)
+    {
+        ffbnfm(tform[ii], &datacode, &repeat, &width, status);
+
+        if (datacode == TBIT)
+            naxis1 = naxis1 + ((repeat + 7) / 8);
+        else if (datacode == TSTRING)
+            naxis1 += repeat;
+        else
+            naxis1 = naxis1 + (repeat * width);
+    }
+
+    datasize = ((LONGLONG)naxis1 * naxis2) + pcount;         /* size of table in bytes */
+    nblocks = (long) ((datasize + 2879) / 2880) + nhead;  /* size of HDU */
+
+    if ((fptr->Fptr)->writemode == READWRITE) /* must have write access */
+    {   /* close the CHDU */
+        ffrdef(fptr, status);  /* scan header to redefine structure */
+        ffpdfl(fptr, status);  /* insure correct data file values */
+    }
+    else
+        return(*status = READONLY_FILE);
+
+    nexthdu = ((fptr->Fptr)->curhdu) + 1; /* number of the next (new) hdu */
+    newstart = (fptr->Fptr)->headstart[nexthdu]; /* save starting addr of HDU */
+
+    (fptr->Fptr)->hdutype = BINARY_TBL;  /* so that correct fill value is used */
+
+    /* ffiblk also increments headstart for all following HDUs */
+    if (ffiblk(fptr, nblocks, 1, status) > 0)  /* insert the blocks */
+        return(*status);
+
+    ((fptr->Fptr)->maxhdu)++;      /* increment known number of HDUs in the file */
+    for (ii = (fptr->Fptr)->maxhdu; ii > (fptr->Fptr)->curhdu; ii--)
+        (fptr->Fptr)->headstart[ii + 1] = (fptr->Fptr)->headstart[ii]; /* incre start addr */
+
+    (fptr->Fptr)->headstart[nexthdu] = newstart; /* set starting addr of HDU */
+
+    /* set default parameters for this new empty HDU */
+    (fptr->Fptr)->curhdu = nexthdu;   /* we are now located at the next HDU */
+    fptr->HDUposition = nexthdu;      /* we are now located at the next HDU */
+    (fptr->Fptr)->nextkey = (fptr->Fptr)->headstart[nexthdu];  
+    (fptr->Fptr)->headend = (fptr->Fptr)->headstart[nexthdu];
+    (fptr->Fptr)->datastart = ((fptr->Fptr)->headstart[nexthdu]) + (nhead * 2880);
+    (fptr->Fptr)->hdutype = BINARY_TBL;  /* might need to be reset... */
+
+    /* write the required header keywords. This will write PCOUNT = 0 */
+    /* so that the variable length data will be written at the right place */
+    ffphbn(fptr, naxis2, tfields, ttype, tform, tunit, extnm, pcount,
+           status);
+
+    /* redefine internal structure for this HDU (with PCOUNT = 0) */
+    ffrdef(fptr, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffdhdu(fitsfile *fptr,      /* I - FITS file pointer                   */
+           int *hdutype,        /* O - type of the new CHDU after deletion */
+           int *status)         /* IO - error status                       */
+/*
+  Delete the CHDU.  If the CHDU is the primary array, then replace the HDU
+  with an empty primary array with no data.   Return the
+  type of the new CHDU after the old CHDU is deleted.
+*/
+{
+    int tmptype = 0;
+    long nblocks, ii, naxes[1];
+
+    if (*status > 0)
+        return(*status);
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    if ((fptr->Fptr)->curhdu == 0) /* replace primary array with null image */
+    {
+        /* ignore any existing keywords */
+        (fptr->Fptr)->headend = 0;
+        (fptr->Fptr)->nextkey = 0;
+
+        /* write default primary array header */
+        ffphpr(fptr,1,8,0,naxes,0,1,1,status);
+
+        /* calc number of blocks to delete (leave just 1 block) */
+        nblocks = (long) (( (fptr->Fptr)->headstart[(fptr->Fptr)->curhdu + 1] - 
+                2880 ) / 2880);
+
+        /* ffdblk also updates the starting address of all following HDUs */
+        if (nblocks > 0)
+        {
+            if (ffdblk(fptr, nblocks, status) > 0) /* delete the HDU */
+                return(*status);
+        }
+
+        /* this might not be necessary, but is doesn't hurt */
+        (fptr->Fptr)->datastart = DATA_UNDEFINED;
+
+        ffrdef(fptr, status);  /* reinitialize the primary array */
+    }
+    else
+    {
+
+        /* calc number of blocks to delete */
+        nblocks = (long) (( (fptr->Fptr)->headstart[(fptr->Fptr)->curhdu + 1] - 
+                (fptr->Fptr)->headstart[(fptr->Fptr)->curhdu] ) / 2880);
+
+        /* ffdblk also updates the starting address of all following HDUs */
+        if (ffdblk(fptr, nblocks, status) > 0) /* delete the HDU */
+            return(*status);
+
+        /* delete the CHDU from the list of HDUs */
+        for (ii = (fptr->Fptr)->curhdu + 1; ii <= (fptr->Fptr)->maxhdu; ii++)
+            (fptr->Fptr)->headstart[ii] = (fptr->Fptr)->headstart[ii + 1];
+
+        (fptr->Fptr)->headstart[(fptr->Fptr)->maxhdu + 1] = 0;
+        ((fptr->Fptr)->maxhdu)--; /* decrement the known number of HDUs */
+
+        if (ffrhdu(fptr, &tmptype, status) > 0)  /* initialize next HDU */
+        {
+            /* failed (end of file?), so move back one HDU */
+            *status = 0;
+            ffcmsg();       /* clear extraneous error messages */
+            ffgext(fptr, ((fptr->Fptr)->curhdu) - 1, &tmptype, status);
+        }
+    }
+
+    if (hdutype)
+       *hdutype = tmptype;
+
+    return(*status);
+}
+
diff --git a/src/plugins/cfitsio/eval_defs.h b/src/plugins/cfitsio/eval_defs.h
new file mode 100644
index 0000000..09c066b
--- /dev/null
+++ b/src/plugins/cfitsio/eval_defs.h
@@ -0,0 +1,163 @@
+#include <stdio.h>
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+#if defined(__sgi) || defined(__hpux)
+#include <alloca.h>
+#endif
+#ifdef sparc
+#include <malloc.h>
+#endif
+#include "fitsio2.h"
+
+#define MAXDIMS       5
+#define MAXSUBS      10
+#define MAXVARNAME   80
+#define CONST_OP  -1000
+#define pERROR       -1
+#define MAX_STRLEN  256
+#define MAX_STRLEN_S "255"
+
+#ifndef FFBISON
+#include "eval_tab.h"
+#endif
+
+
+typedef struct {
+                  char   name[MAXVARNAME+1];
+                  int    type;
+                  long   nelem;
+                  int    naxis;
+                  long   naxes[MAXDIMS];
+                  char   *undef;
+                  void   *data;
+                                } DataInfo;
+
+typedef struct {
+                  long   nelem;
+                  int    naxis;
+                  long   naxes[MAXDIMS];
+                  char   *undef;
+                  union {
+                         double dbl;
+                         long   lng;
+                         char   log;
+                         char   str[MAX_STRLEN];
+                         double *dblptr;
+                         long   *lngptr;
+                         char   *logptr;
+                         char   **strptr;
+                         void   *ptr;
+		  } data;
+                                } lval;
+
+typedef struct Node {
+                  int    operation;
+                  void   (*DoOp)(struct Node *this);
+                  int    nSubNodes;
+                  int    SubNodes[MAXSUBS];
+                  int    type;
+                  lval   value;
+                                } Node;
+
+typedef struct {
+                  fitsfile    *def_fptr;
+                  int         (*getData)( char *dataName, void *dataValue );
+                  int         (*loadData)( int varNum, long fRow, long nRows,
+					   void *data, char *undef );
+
+                  int         compressed;
+                  int         timeCol;
+                  int         parCol;
+                  int         valCol;
+
+                  char        *expr;
+                  int         index;
+                  int         is_eobuf;
+
+                  Node        *Nodes;
+                  int         nNodes;
+                  int         nNodesAlloc;
+                  int         resultNode;
+                  
+                  long        firstRow;
+                  long        nRows;
+
+                  int         nCols;
+                  iteratorCol *colData;
+                  DataInfo    *varData;
+                  PixelFilter *pixFilter;
+
+                  long        firstDataRow;
+                  long        nDataRows;
+                  long        totalRows;
+
+                  int         datatype;
+                  int         hdutype;
+
+                  int         status;
+                                } ParseData;
+
+typedef enum {
+                  rnd_fct = 1001,
+                  sum_fct,
+                  nelem_fct,
+                  sin_fct,
+                  cos_fct,
+                  tan_fct,
+                  asin_fct,
+                  acos_fct,
+                  atan_fct,
+                  sinh_fct,
+                  cosh_fct,
+                  tanh_fct,
+                  exp_fct,
+                  log_fct,
+                  log10_fct,
+                  sqrt_fct,
+                  abs_fct,
+                  atan2_fct,
+                  ceil_fct,
+                  floor_fct,
+                  round_fct,
+		  min1_fct,
+		  min2_fct,
+		  max1_fct,
+		  max2_fct,
+                  near_fct,
+                  circle_fct,
+                  box_fct,
+                  elps_fct,
+                  isnull_fct,
+                  defnull_fct,
+                  gtifilt_fct,
+                  regfilt_fct,
+                  ifthenelse_fct,
+                  row_fct,
+                  null_fct,
+		  median_fct,
+		  average_fct,
+		  stddev_fct,
+		  nonnull_fct,
+		  angsep_fct,
+		  gasrnd_fct,
+		  poirnd_fct,
+		  strmid_fct,
+		  strpos_fct
+                                } funcOp;
+
+extern ParseData gParse;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+   int  ffparse(void);
+   int  fflex(void);
+   void ffrestart(FILE*);
+
+   void Evaluate_Parser( long firstRow, long nRows );
+
+#ifdef __cplusplus
+    }
+#endif
diff --git a/src/plugins/cfitsio/eval_f.c b/src/plugins/cfitsio/eval_f.c
new file mode 100644
index 0000000..f9bdd05
--- /dev/null
+++ b/src/plugins/cfitsio/eval_f.c
@@ -0,0 +1,2823 @@
+/************************************************************************/
+/*                                                                      */
+/*                       CFITSIO Lexical Parser                         */
+/*                                                                      */
+/* This file is one of 3 files containing code which parses an          */
+/* arithmetic expression and evaluates it in the context of an input    */
+/* FITS file table extension.  The CFITSIO lexical parser is divided    */
+/* into the following 3 parts/files: the CFITSIO "front-end",           */
+/* eval_f.c, contains the interface between the user/CFITSIO and the    */
+/* real core of the parser; the FLEX interpreter, eval_l.c, takes the   */
+/* input string and parses it into tokens and identifies the FITS       */
+/* information required to evaluate the expression (ie, keywords and    */
+/* columns); and, the BISON grammar and evaluation routines, eval_y.c,  */
+/* receives the FLEX output and determines and performs the actual      */
+/* operations.  The files eval_l.c and eval_y.c are produced from       */
+/* running flex and bison on the files eval.l and eval.y, respectively. */
+/* (flex and bison are available from any GNU archive: see www.gnu.org) */
+/*                                                                      */
+/* The grammar rules, rather than evaluating the expression in situ,    */
+/* builds a tree, or Nodal, structure mapping out the order of          */
+/* operations and expression dependencies.  This "compilation" process  */
+/* allows for much faster processing of multiple rows.  This technique  */
+/* was developed by Uwe Lammers of the XMM Science Analysis System,     */
+/* although the CFITSIO implementation is entirely code original.       */
+/*                                                                      */
+/*                                                                      */
+/* Modification History:                                                */
+/*                                                                      */
+/*   Kent Blackburn      c1992  Original parser code developed for the  */
+/*                              FTOOLS software package, in particular, */
+/*                              the fselect task.                       */
+/*   Kent Blackburn      c1995  BIT column support added                */
+/*   Peter D Wilson   Feb 1998  Vector column support added             */
+/*   Peter D Wilson   May 1998  Ported to CFITSIO library.  User        */
+/*                              interface routines written, in essence  */
+/*                              making fselect, fcalc, and maketime     */
+/*                              capabilities available to all tools     */
+/*                              via single function calls.              */
+/*   Peter D Wilson   Jun 1998  Major rewrite of parser core, so as to  */
+/*                              create a run-time evaluation tree,      */
+/*                              inspired by the work of Uwe Lammers,    */
+/*                              resulting in a speed increase of        */
+/*                              10-100 times.                           */
+/*   Peter D Wilson   Jul 1998  gtifilter(a,b,c,d) function added       */
+/*   Peter D Wilson   Aug 1998  regfilter(a,b,c,d) function added       */
+/*   Peter D Wilson   Jul 1999  Make parser fitsfile-independent,       */
+/*                              allowing a purely vector-based usage    */
+/*   Peter D Wilson   Aug 1999  Add row-offset capability               */
+/*   Peter D Wilson   Sep 1999  Add row-range capability to ffcalc_rng  */
+/*                                                                      */
+/************************************************************************/
+
+#include <limits.h>
+#include <ctype.h>
+#include "eval_defs.h"
+#include "region.h"
+
+typedef struct {
+     int  datatype;   /* Data type to cast parse results into for user       */
+     void *dataPtr;   /* Pointer to array of results, NULL if to use iterCol */
+     void *nullPtr;   /* Pointer to nulval, use zero if NULL                 */
+     long maxRows;    /* Max No. of rows to process, -1=all, 0=1 iteration   */
+     int  anyNull;    /* Flag indicating at least 1 undef value encountered  */
+} parseInfo;
+
+/*  Internal routines needed to allow the evaluator to operate on FITS data  */
+
+static void Setup_DataArrays( int nCols, iteratorCol *cols,
+                              long fRow, long nRows );
+static int  find_column( char *colName, void *itslval );
+static int  find_keywd ( char *key,     void *itslval );
+static int  allocateCol( int nCol, int *status );
+static int  load_column( int varNum, long fRow, long nRows,
+                         void *data, char *undef );
+
+static int DEBUG_PIXFILTER;
+
+#define FREE(x) { if (x) free(x); else printf("invalid free(" #x ") at %s:%d\n", __FILE__, __LINE__); }
+
+/*---------------------------------------------------------------------------*/
+int fffrow( fitsfile *fptr,         /* I - Input FITS file                   */
+            char     *expr,         /* I - Boolean expression                */
+            long     firstrow,      /* I - First row of table to eval        */
+            long     nrows,         /* I - Number of rows to evaluate        */
+            long     *n_good_rows,  /* O - Number of rows eval to True       */
+            char     *row_status,   /* O - Array of boolean results          */
+            int      *status )      /* O - Error status                      */
+/*                                                                           */
+/* Evaluate a boolean expression using the indicated rows, returning an      */
+/* array of flags indicating which rows evaluated to TRUE/FALSE              */
+/*---------------------------------------------------------------------------*/
+{
+   parseInfo Info;
+   int naxis, constant;
+   long nelem, naxes[MAXDIMS], elem;
+   char result;
+
+   if( *status ) return( *status );
+
+   FFLOCK;
+   if( ffiprs( fptr, 0, expr, MAXDIMS, &Info.datatype, &nelem, &naxis,
+               naxes, status ) ) {
+      ffcprs();
+      FFUNLOCK;
+      return( *status );
+   }
+   if( nelem<0 ) {
+      constant = 1;
+      nelem = -nelem;
+   } else
+      constant = 0;
+
+   if( Info.datatype!=TLOGICAL || nelem!=1 ) {
+      ffcprs();
+      ffpmsg("Expression does not evaluate to a logical scalar.");
+      FFUNLOCK;
+      return( *status = PARSE_BAD_TYPE );
+   }
+
+   if( constant ) { /* No need to call parser... have result from ffiprs */
+      result = gParse.Nodes[gParse.resultNode].value.data.log;
+      *n_good_rows = nrows;
+      for( elem=0; elem<nrows; elem++ )
+         row_status[elem] = result;
+   } else {
+      firstrow     = (firstrow>1 ? firstrow : 1);
+      Info.dataPtr = row_status;
+      Info.nullPtr = NULL;
+      Info.maxRows = nrows;
+
+      if( ffiter( gParse.nCols, gParse.colData, firstrow-1, 0,
+                  parse_data, (void*)&Info, status ) == -1 )
+         *status = 0;  /* -1 indicates exitted without error before end... OK */
+
+      if( *status ) {
+
+         /***********************/
+         /* Error... Do nothing */
+         /***********************/
+
+      } else {
+
+         /***********************************/
+         /* Count number of good rows found */
+         /***********************************/
+
+         *n_good_rows = 0L;
+         for( elem=0; elem<Info.maxRows; elem++ ) {
+            if( row_status[elem]==1 ) ++*n_good_rows;
+         }
+      }
+   }
+
+   ffcprs();
+   FFUNLOCK;
+   return(*status);
+}
+
+/*--------------------------------------------------------------------------*/
+int ffsrow( fitsfile *infptr,   /* I - Input FITS file                      */
+            fitsfile *outfptr,  /* I - Output FITS file                     */
+            char     *expr,     /* I - Boolean expression                   */
+            int      *status )  /* O - Error status                         */
+/*                                                                          */
+/* Evaluate an expression on all rows of a table.  If the input and output  */
+/* files are not the same, copy the TRUE rows to the output file.  If the   */
+/* files are the same, delete the FALSE rows (preserve the TRUE rows).      */
+/* Can copy rows between extensions of the same file, *BUT* if output       */
+/* extension is before the input extension, the second extension *MUST* be  */
+/* opened using ffreopen, so that CFITSIO can handle changing file lengths. */
+/*--------------------------------------------------------------------------*/
+{
+   parseInfo Info;
+   int naxis, constant;
+   long nelem, rdlen, naxes[MAXDIMS], maxrows, nbuff, nGood, inloc, outloc;
+   LONGLONG ntodo, inbyteloc, outbyteloc, hsize;
+   long freespace;
+   unsigned char *buffer, result;
+   struct {
+      LONGLONG rowLength, numRows, heapSize;
+      LONGLONG dataStart, heapStart;
+   } inExt, outExt;
+
+   if( *status ) return( *status );
+
+   FFLOCK;
+   if( ffiprs( infptr, 0, expr, MAXDIMS, &Info.datatype, &nelem, &naxis,
+               naxes, status ) ) {
+      ffcprs();
+      FFUNLOCK;
+      return( *status );
+   }
+
+   if( nelem<0 ) {
+      constant = 1;
+      nelem = -nelem;
+   } else
+      constant = 0;
+
+   /**********************************************************************/
+   /* Make sure expression evaluates to the right type... logical scalar */
+   /**********************************************************************/
+
+   if( Info.datatype!=TLOGICAL || nelem!=1 ) {
+      ffcprs();
+      ffpmsg("Expression does not evaluate to a logical scalar.");
+      FFUNLOCK;
+      return( *status = PARSE_BAD_TYPE );
+   }
+
+   /***********************************************************/
+   /*  Extract various table information from each extension  */
+   /***********************************************************/
+
+   if( infptr->HDUposition != (infptr->Fptr)->curhdu )
+      ffmahd( infptr, (infptr->HDUposition) + 1, NULL, status );
+   if( *status ) {
+      ffcprs();
+      FFUNLOCK;
+      return( *status );
+   }
+   inExt.rowLength = (long) (infptr->Fptr)->rowlength;
+   inExt.numRows   = (infptr->Fptr)->numrows;
+   inExt.heapSize  = (infptr->Fptr)->heapsize;
+   if( inExt.numRows == 0 ) { /* Nothing to copy */
+      ffcprs();
+      FFUNLOCK;
+      return( *status );
+   }
+
+   if( outfptr->HDUposition != (outfptr->Fptr)->curhdu )
+      ffmahd( outfptr, (outfptr->HDUposition) + 1, NULL, status );
+   if( (outfptr->Fptr)->datastart < 0 )
+      ffrdef( outfptr, status );
+   if( *status ) {
+      ffcprs();
+      FFUNLOCK;
+      return( *status );
+   }
+   outExt.rowLength = (long) (outfptr->Fptr)->rowlength;
+   outExt.numRows   = (outfptr->Fptr)->numrows;
+   if( !outExt.numRows )
+      (outfptr->Fptr)->heapsize = 0L;
+   outExt.heapSize  = (outfptr->Fptr)->heapsize;
+
+   if( inExt.rowLength != outExt.rowLength ) {
+      ffpmsg("Output table has different row length from input");
+      ffcprs();
+      FFUNLOCK;
+      return( *status = PARSE_BAD_OUTPUT );
+   }
+
+   /***********************************/
+   /*  Fill out Info data for parser  */
+   /***********************************/
+
+   Info.dataPtr = (char *)malloc( (size_t) ((inExt.numRows + 1) * sizeof(char)) );
+   Info.nullPtr = NULL;
+   Info.maxRows = (long) inExt.numRows;
+   if( !Info.dataPtr ) {
+      ffpmsg("Unable to allocate memory for row selection");
+      ffcprs();
+      FFUNLOCK;
+      return( *status = MEMORY_ALLOCATION );
+   }
+   
+   /* make sure array is zero terminated */
+   ((char*)Info.dataPtr)[inExt.numRows] = 0;
+
+   if( constant ) { /*  Set all rows to the same value from constant result  */
+
+      result = gParse.Nodes[gParse.resultNode].value.data.log;
+      for( ntodo = 0; ntodo<inExt.numRows; ntodo++ )
+         ((char*)Info.dataPtr)[ntodo] = result;
+      nGood = (long) (result ? inExt.numRows : 0);
+
+   } else {
+
+      ffiter( gParse.nCols, gParse.colData, 0L, 0L,
+              parse_data, (void*)&Info, status );
+
+      nGood = 0;
+      for( ntodo = 0; ntodo<inExt.numRows; ntodo++ )
+         if( ((char*)Info.dataPtr)[ntodo] ) nGood++;
+   }
+
+   if( *status ) {
+      /* Error... Do nothing */
+   } else {
+      rdlen  = (long) inExt.rowLength;
+      buffer = (unsigned char *)malloc(maxvalue(500000,rdlen) * sizeof(char) );
+      if( buffer==NULL ) {
+         ffcprs();
+         FFUNLOCK;
+         return( *status=MEMORY_ALLOCATION );
+      }
+      maxrows = maxvalue( (500000L/rdlen), 1);
+      nbuff = 0;
+      inloc = 1;
+      if( infptr==outfptr ) { /* Skip initial good rows if input==output file */
+         while( ((char*)Info.dataPtr)[inloc-1] ) inloc++;
+         outloc = inloc;
+      } else {
+         outloc = (long) (outExt.numRows + 1);
+         if (outloc > 1) 
+            ffirow( outfptr, outExt.numRows, nGood, status );
+      }
+
+      do {
+         if( ((char*)Info.dataPtr)[inloc-1] ) {
+            ffgtbb( infptr, inloc, 1L, rdlen, buffer+rdlen*nbuff, status );
+            nbuff++;
+            if( nbuff==maxrows ) {
+               ffptbb( outfptr, outloc, 1L, rdlen*nbuff, buffer,  status );
+               outloc += nbuff;
+               nbuff = 0;
+            }
+         }
+         inloc++;
+      } while( !*status && inloc<=inExt.numRows );
+
+      if( nbuff ) {
+         ffptbb( outfptr, outloc, 1L, rdlen*nbuff, buffer,  status );
+         outloc += nbuff;
+      }
+
+      if( infptr==outfptr ) {
+
+         if( outloc<=inExt.numRows )
+            ffdrow( infptr, outloc, inExt.numRows-outloc+1, status );
+
+      } else if( inExt.heapSize && nGood ) {
+
+         /* Copy heap, if it exists and at least one row copied */
+
+         /********************************************************/
+         /*  Get location information from the output extension  */
+         /********************************************************/
+
+         if( outfptr->HDUposition != (outfptr->Fptr)->curhdu )
+            ffmahd( outfptr, (outfptr->HDUposition) + 1, NULL, status );
+         outExt.dataStart = (outfptr->Fptr)->datastart;
+         outExt.heapStart = (outfptr->Fptr)->heapstart;
+
+         /*************************************************/
+         /*  Insert more space into outfptr if necessary  */
+         /*************************************************/
+
+         hsize     = outExt.heapStart + outExt.heapSize;
+         freespace = (long) (( ( (hsize + 2879) / 2880) * 2880) - hsize);
+         ntodo     = inExt.heapSize;
+
+         if ( (freespace - ntodo) < 0) {       /* not enough existing space? */
+            ntodo = (ntodo - freespace + 2879) / 2880;  /* number of blocks  */
+            ffiblk(outfptr, (long) ntodo, 1, status);   /* insert the blocks */
+         }
+         ffukyj( outfptr, "PCOUNT", inExt.heapSize+outExt.heapSize,
+                 NULL, status );
+
+         /*******************************************************/
+         /*  Get location information from the input extension  */
+         /*******************************************************/
+
+         if( infptr->HDUposition != (infptr->Fptr)->curhdu )
+            ffmahd( infptr, (infptr->HDUposition) + 1, NULL, status );
+         inExt.dataStart = (infptr->Fptr)->datastart;
+         inExt.heapStart = (infptr->Fptr)->heapstart;
+
+         /**********************************/
+         /*  Finally copy heap to outfptr  */
+         /**********************************/
+
+         ntodo  =  inExt.heapSize;
+         inbyteloc  =  inExt.heapStart +  inExt.dataStart;
+         outbyteloc = outExt.heapStart + outExt.dataStart + outExt.heapSize;
+
+         while ( ntodo && !*status ) {
+            rdlen = (long) minvalue(ntodo,500000);
+            ffmbyt( infptr,  inbyteloc,  REPORT_EOF, status );
+            ffgbyt( infptr,  rdlen,  buffer,     status );
+            ffmbyt( outfptr, outbyteloc, IGNORE_EOF, status );
+            ffpbyt( outfptr, rdlen,  buffer,     status );
+            inbyteloc  += rdlen;
+            outbyteloc += rdlen;
+            ntodo  -= rdlen;
+         }
+
+         /***********************************************************/
+         /*  But must update DES if data is being appended to a     */
+         /*  pre-existing heap space.  Edit each new entry in file  */
+         /***********************************************************/
+
+         if( outExt.heapSize ) {
+            LONGLONG repeat, offset, j;
+            int i;
+            for( i=1; i<=(outfptr->Fptr)->tfield; i++ ) {
+               if( (outfptr->Fptr)->tableptr[i-1].tdatatype<0 ) {
+                  for( j=outExt.numRows+1; j<=outExt.numRows+nGood; j++ ) {
+                     ffgdesll( outfptr, i, j, &repeat, &offset, status );
+                     offset += outExt.heapSize;
+                     ffpdes( outfptr, i, j, repeat, offset, status );
+                  }
+               }
+            }
+         }
+
+      } /*  End of HEAP copy  */
+
+      FREE(buffer);
+   }
+
+   FREE(Info.dataPtr);
+   ffcprs();
+
+   ffcmph(outfptr, status);  /* compress heap, deleting any orphaned data */
+   FFUNLOCK;
+   return(*status);
+}
+
+/*---------------------------------------------------------------------------*/
+int ffcrow( fitsfile *fptr,      /* I - Input FITS file                      */
+            int      datatype,   /* I - Datatype to return results as        */
+            char     *expr,      /* I - Arithmetic expression                */
+            long     firstrow,   /* I - First row to evaluate                */
+            long     nelements,  /* I - Number of elements to return         */
+            void     *nulval,    /* I - Ptr to value to use as UNDEF         */
+            void     *array,     /* O - Array of results                     */
+            int      *anynul,    /* O - Were any UNDEFs encountered?         */
+            int      *status )   /* O - Error status                         */
+/*                                                                           */
+/* Calculate an expression for the indicated rows of a table, returning      */
+/* the results, cast as datatype (TSHORT, TDOUBLE, etc), in array.  If       */
+/* nulval==NULL, UNDEFs will be zeroed out.  For vector results, the number  */
+/* of elements returned may be less than nelements if nelements is not an    */
+/* even multiple of the result dimension.  Call fftexp to obtain the         */
+/* dimensions of the results.                                                */
+/*---------------------------------------------------------------------------*/
+{
+   parseInfo Info;
+   int naxis;
+   long nelem1, naxes[MAXDIMS];
+
+   if( *status ) return( *status );
+
+   FFLOCK;
+   if( ffiprs( fptr, 0, expr, MAXDIMS, &Info.datatype, &nelem1, &naxis,
+               naxes, status ) ) {
+      ffcprs();
+      FFUNLOCK;
+      return( *status );
+   }
+   if( nelem1<0 ) nelem1 = - nelem1;
+
+   if( nelements<nelem1 ) {
+      ffcprs();
+      ffpmsg("Array not large enough to hold at least one row of data.");
+      FFUNLOCK;
+      return( *status = PARSE_LRG_VECTOR );
+   }
+
+   firstrow = (firstrow>1 ? firstrow : 1);
+
+   if( datatype ) Info.datatype = datatype;
+
+   Info.dataPtr = array;
+   Info.nullPtr = nulval;
+   Info.maxRows = nelements / nelem1;
+   
+   if( ffiter( gParse.nCols, gParse.colData, firstrow-1, 0,
+               parse_data, (void*)&Info, status ) == -1 )
+      *status=0;  /* -1 indicates exitted without error before end... OK */
+
+   *anynul = Info.anyNull;
+   ffcprs();
+   FFUNLOCK;
+   return( *status );
+}
+
+/*--------------------------------------------------------------------------*/
+int ffcalc( fitsfile *infptr,   /* I - Input FITS file                      */
+            char     *expr,     /* I - Arithmetic expression                */
+            fitsfile *outfptr,  /* I - Output fits file                     */
+            char     *parName,  /* I - Name of output parameter             */
+            char     *parInfo,  /* I - Extra information on parameter       */
+            int      *status )  /* O - Error status                         */
+/*                                                                          */
+/* Evaluate an expression for all rows of a table.  Call ffcalc_rng with    */
+/* a row range of 1-MAX.                                                    */
+{
+   long start=1, end=LONG_MAX;
+
+   return ffcalc_rng( infptr, expr, outfptr, parName, parInfo,
+                      1, &start, &end, status );
+}
+
+/*--------------------------------------------------------------------------*/
+int ffcalc_rng( fitsfile *infptr,   /* I - Input FITS file                  */
+                char     *expr,     /* I - Arithmetic expression            */
+                fitsfile *outfptr,  /* I - Output fits file                 */
+                char     *parName,  /* I - Name of output parameter         */
+                char     *parInfo,  /* I - Extra information on parameter   */
+                int      nRngs,     /* I - Row range info                   */
+                long     *start,    /* I - Row range info                   */
+                long     *end,      /* I - Row range info                   */
+                int      *status )  /* O - Error status                     */
+/*                                                                          */
+/* Evaluate an expression using the data in the input FITS file and place   */
+/* the results into either a column or keyword in the output fits file,     */
+/* depending on the value of parName (keywords normally prefixed with '#')  */
+/* and whether the expression evaluates to a constant or a table column.    */
+/* The logic is as follows:                                                 */
+/*    (1) If a column exists with name, parName, put results there.         */
+/*    (2) If parName starts with '#', as in #NAXIS, put result there,       */
+/*        with parInfo used as the comment. If expression does not evaluate */
+/*        to a constant, flag an error.                                     */
+/*    (3) If a keyword exists with name, parName, and expression is a       */
+/*        constant, put result there, using parInfo as the new comment.     */
+/*    (4) Else, create a new column with name parName and TFORM parInfo.    */
+/*        If parInfo is NULL, use a default data type for the column.       */
+/*--------------------------------------------------------------------------*/
+{
+   parseInfo Info;
+   int naxis, constant, typecode, newNullKwd=0;
+   long nelem, naxes[MAXDIMS], repeat, width;
+   int col_cnt, colNo;
+   Node *result;
+   char card[81], tform[16], nullKwd[9], tdimKwd[9];
+
+   if( *status ) return( *status );
+
+   FFLOCK;
+   if( ffiprs( infptr, 0, expr, MAXDIMS, &Info.datatype, &nelem, &naxis,
+               naxes, status ) ) {
+
+      ffcprs();
+      FFUNLOCK;
+      return( *status );
+   }
+   if( nelem<0 ) {
+      constant = 1;
+      nelem = -nelem;
+   } else
+      constant = 0;
+
+   /*  Case (1): If column exists put it there  */
+
+   colNo = 0;
+   if( ffgcno( outfptr, CASEINSEN, parName, &colNo, status )==COL_NOT_FOUND ) {
+
+      /*  Output column doesn't exist.  Test for keyword. */
+
+      /* Case (2): Does parName indicate result should be put into keyword */
+
+      *status = 0;
+      if( parName[0]=='#' ) {
+         if( ! constant ) {
+            ffcprs();
+            ffpmsg( "Cannot put tabular result into keyword (ffcalc)" );
+            FFUNLOCK;
+            return( *status = PARSE_BAD_TYPE );
+         }
+         parName++;
+
+      } else if( constant ) {
+
+         /* Case (3): Does a keyword named parName already exist */
+
+         if( ffgcrd( outfptr, parName, card, status )==KEY_NO_EXIST ) {
+            colNo = -1;
+         } else if( *status ) {
+            ffcprs();
+            FFUNLOCK;
+            return( *status );
+         }
+
+      } else
+         colNo = -1;
+
+      if( colNo<0 ) {
+
+         /* Case (4): Create new column */
+
+         *status = 0;
+         ffgncl( outfptr, &colNo, status );
+         colNo++;
+         if( parInfo==NULL || *parInfo=='\0' ) {
+            /*  Figure out best default column type  */
+            if( gParse.hdutype==BINARY_TBL ) {
+               sprintf(tform,"%ld",nelem);
+               switch( Info.datatype ) {
+               case TLOGICAL:  strcat(tform,"L");  break;
+               case TLONG:     strcat(tform,"J");  break;
+               case TDOUBLE:   strcat(tform,"D");  break;
+               case TSTRING:   strcat(tform,"A");  break;
+               case TBIT:      strcat(tform,"X");  break;
+               case TLONGLONG: strcat(tform,"K");  break;
+               }
+            } else {
+               switch( Info.datatype ) {
+               case TLOGICAL:
+                  ffcprs();
+                  ffpmsg("Cannot create LOGICAL column in ASCII table");
+                  FFUNLOCK;
+                  return( *status = NOT_BTABLE );
+               case TLONG:     strcpy(tform,"I11");     break;
+               case TDOUBLE:   strcpy(tform,"D23.15");  break;
+               case TSTRING:   
+               case TBIT:      sprintf(tform,"A%ld",nelem);  break;
+               }
+            }
+            parInfo = tform;
+         } else if( !(isdigit((int) *parInfo)) && gParse.hdutype==BINARY_TBL ) {
+            if( Info.datatype==TBIT && *parInfo=='B' )
+               nelem = (nelem+7)/8;
+            sprintf(tform,"%ld%s",nelem,parInfo);
+            parInfo = tform;
+         }
+         fficol( outfptr, colNo, parName, parInfo, status );
+         if( naxis>1 )
+            ffptdm( outfptr, colNo, naxis, naxes, status );
+
+         /*  Setup TNULLn keyword in case NULLs are encountered  */
+
+         ffkeyn("TNULL", colNo, nullKwd, status);
+         if( ffgcrd( outfptr, nullKwd, card, status )==KEY_NO_EXIST ) {
+            *status = 0;
+            if( gParse.hdutype==BINARY_TBL ) {
+	       LONGLONG nullVal=0;
+               fits_binary_tform( parInfo, &typecode, &repeat, &width, status );
+               if( typecode==TBYTE )
+                  nullVal = UCHAR_MAX;
+               else if( typecode==TSHORT )
+                  nullVal = SHRT_MIN;
+               else if( typecode==TINT )
+                  nullVal = INT_MIN;
+               else if( typecode==TLONG )
+                  nullVal = LONG_MIN;
+               else if( typecode==TLONGLONG )
+                  nullVal = LONGLONG_MIN;
+		  
+               if( nullVal ) {
+                  ffpkyj( outfptr, nullKwd, nullVal, "Null value", status );
+                  fits_set_btblnull( outfptr, colNo, nullVal, status );
+                  newNullKwd = 1;
+               }
+            } else if( gParse.hdutype==ASCII_TBL ) {
+               ffpkys( outfptr, nullKwd, "NULL", "Null value string", status );
+               fits_set_atblnull( outfptr, colNo, "NULL", status );
+               newNullKwd = 1;
+            }
+         }
+
+      }
+
+   } else if( *status ) {
+      ffcprs();
+      FFUNLOCK;
+      return( *status );
+   } else {
+
+      /********************************************************/
+      /*  Check if a TDIM keyword should be written/updated.  */
+      /********************************************************/
+
+      ffkeyn("TDIM", colNo, tdimKwd, status);
+      ffgcrd( outfptr, tdimKwd, card, status );
+      if( *status==0 ) {
+         /*  TDIM exists, so update it with result's dimension  */
+         ffptdm( outfptr, colNo, naxis, naxes, status );
+      } else if( *status==KEY_NO_EXIST ) {
+         /*  TDIM does not exist, so clear error stack and     */
+         /*  write a TDIM only if result is multi-dimensional  */
+         *status = 0;
+         ffcmsg();
+         if( naxis>1 )
+            ffptdm( outfptr, colNo, naxis, naxes, status );
+      }
+      if( *status ) {
+         /*  Either some other error happened in ffgcrd   */
+         /*  or one happened in ffptdm                    */
+         ffcprs();
+         FFUNLOCK;
+         return( *status );
+      }
+
+   }
+
+   if( colNo>0 ) {
+
+      /*  Output column exists (now)... put results into it  */
+
+      int anyNull = 0;
+      int nPerLp, i;
+      long totaln;
+
+      ffgkyj(infptr, "NAXIS2", &totaln, 0, status);
+
+      /*************************************/
+      /* Create new iterator Output Column */
+      /*************************************/
+
+      col_cnt = gParse.nCols;
+      if( allocateCol( col_cnt, status ) ) {
+         ffcprs();
+         FFUNLOCK;
+         return( *status );
+      }
+
+      fits_iter_set_by_num( gParse.colData+col_cnt, outfptr,
+                            colNo, 0, OutputCol );
+      gParse.nCols++;
+
+      for( i=0; i<nRngs; i++ ) {
+         Info.dataPtr = NULL;
+         Info.maxRows = end[i]-start[i]+1;
+
+          /*
+            If there is only 1 range, and it includes all the rows,
+            and there are 10 or more rows, then set nPerLp = 0 so
+            that the iterator function will dynamically choose the
+            most efficient number of rows to process in each loop.
+            Otherwise, set nPerLp to the number of rows in this range.
+         */
+
+         if( (Info.maxRows >= 10) && (nRngs == 1) &&
+             (start[0] == 1) && (end[0] == totaln))
+              nPerLp = 0;
+         else
+              nPerLp = Info.maxRows;
+
+         if( ffiter( gParse.nCols, gParse.colData, start[i]-1,
+                     nPerLp, parse_data, (void*)&Info, status ) == -1 )
+            *status = 0;
+         else if( *status ) {
+            ffcprs();
+            FFUNLOCK;
+            return( *status );
+         }
+         if( Info.anyNull ) anyNull = 1;
+      }
+
+      if( newNullKwd && !anyNull ) {
+         ffdkey( outfptr, nullKwd, status );
+      }
+
+   } else {
+
+      /* Put constant result into keyword */
+
+      result  = gParse.Nodes + gParse.resultNode;
+      switch( Info.datatype ) {
+      case TDOUBLE:
+         ffukyd( outfptr, parName, result->value.data.dbl, 15,
+                 parInfo, status );
+         break;
+      case TLONG:
+         ffukyj( outfptr, parName, result->value.data.lng, parInfo, status );
+         break;
+      case TLOGICAL:
+         ffukyl( outfptr, parName, result->value.data.log, parInfo, status );
+         break;
+      case TBIT:
+      case TSTRING:
+         ffukys( outfptr, parName, result->value.data.str, parInfo, status );
+         break;
+      }
+   }
+
+   ffcprs();
+   FFUNLOCK;
+   return( *status );
+}
+
+/*--------------------------------------------------------------------------*/
+int fftexp( fitsfile *fptr,      /* I - Input FITS file                     */
+            char     *expr,      /* I - Arithmetic expression               */
+            int      maxdim,     /* I - Max Dimension of naxes              */
+            int      *datatype,  /* O - Data type of result                 */
+            long     *nelem,     /* O - Vector length of result             */
+            int      *naxis,     /* O - # of dimensions of result           */
+            long     *naxes,     /* O - Size of each dimension              */
+            int      *status )   /* O - Error status                        */
+/*                                                                          */
+/* Evaluate the given expression and return information on the result.      */
+/*--------------------------------------------------------------------------*/
+{
+   FFLOCK;
+   ffiprs( fptr, 0, expr, maxdim, datatype, nelem, naxis, naxes, status );
+   ffcprs();
+   FFUNLOCK;
+   return( *status );
+}
+
+/*--------------------------------------------------------------------------*/
+int ffiprs( fitsfile *fptr,      /* I - Input FITS file                     */
+            int      compressed, /* I - Is FITS file hkunexpanded?          */
+            char     *expr,      /* I - Arithmetic expression               */
+            int      maxdim,     /* I - Max Dimension of naxes              */
+            int      *datatype,  /* O - Data type of result                 */
+            long     *nelem,     /* O - Vector length of result             */
+            int      *naxis,     /* O - # of dimensions of result           */
+            long     *naxes,     /* O - Size of each dimension              */
+            int      *status )   /* O - Error status                        */
+/*                                                                          */
+/* Initialize the parser and determine what type of result the expression   */
+/* produces.                                                                */
+/*--------------------------------------------------------------------------*/
+{
+   Node *result;
+   int  i,lexpr, tstatus = 0;
+   int xaxis, bitpix;
+   long xaxes[9];
+   static iteratorCol dmyCol;
+
+   if( *status ) return( *status );
+
+   /* make sure all internal structures for this HDU are current */
+   if ( ffrdef(fptr, status) ) return(*status);
+
+   /*  Initialize the Parser structure  */
+
+   gParse.def_fptr   = fptr;
+   gParse.compressed = compressed;
+   gParse.nCols      = 0;
+   gParse.colData    = NULL;
+   gParse.varData    = NULL;
+   gParse.getData    = find_column;
+   gParse.loadData   = load_column;
+   gParse.Nodes      = NULL;
+   gParse.nNodesAlloc= 0;
+   gParse.nNodes     = 0;
+   gParse.hdutype    = 0;
+   gParse.status     = 0;
+
+   fits_get_hdu_type(fptr, &gParse.hdutype, status );
+
+   if (gParse.hdutype == IMAGE_HDU) {
+
+      fits_get_img_param(fptr, 9, &bitpix, &xaxis, xaxes, status);
+      if (*status) {
+         ffpmsg("ffiprs: unable to get image dimensions");
+         return( *status );
+      }
+      gParse.totalRows = xaxis > 0 ? 1 : 0;
+      for (i = 0; i < xaxis; ++i)
+         gParse.totalRows *= xaxes[i];
+      if (DEBUG_PIXFILTER)
+         printf("naxis=%d, gParse.totalRows=%ld\n", xaxis, gParse.totalRows);
+   }
+   else if( ffgkyj(fptr, "NAXIS2", &gParse.totalRows, 0, &tstatus) )
+   {
+      /* this might be a 1D or null image with no NAXIS2 keyword */
+      gParse.totalRows = 0;
+   } 
+   
+
+   /*  Copy expression into parser... read from file if necessary  */
+
+
+   if( expr[0]=='@' ) {
+      if( ffimport_file( expr+1, &gParse.expr, status ) ) return( *status );
+      lexpr = strlen(gParse.expr);
+   } else {
+      lexpr = strlen(expr);
+      gParse.expr = (char*)malloc( (2+lexpr)*sizeof(char));
+      strcpy(gParse.expr,expr);
+   }
+   strcat(gParse.expr + lexpr,"\n");
+   gParse.index    = 0;
+   gParse.is_eobuf = 0;
+
+   /*  Parse the expression, building the Nodes and determing  */
+   /*  which columns are needed and what data type is returned  */
+
+   ffrestart(NULL);
+   if( ffparse() ) {
+      return( *status = PARSE_SYNTAX_ERR );
+   }
+   /*  Check results  */
+
+   *status = gParse.status;
+   if( *status ) return(*status);
+
+   if( !gParse.nNodes ) {
+      ffpmsg("Blank expression");
+      return( *status = PARSE_SYNTAX_ERR );
+   }
+   if( !gParse.nCols ) {
+      dmyCol.fptr = fptr;         /* This allows iterator to know value of */
+      gParse.colData = &dmyCol;   /* fptr when no columns are referenced   */
+   }
+
+   result = gParse.Nodes + gParse.resultNode;
+
+   *naxis = result->value.naxis;
+   *nelem = result->value.nelem;
+   for( i=0; i<*naxis && i<maxdim; i++ )
+      naxes[i] = result->value.naxes[i];
+
+   switch( result->type ) {
+   case BOOLEAN:
+      *datatype = TLOGICAL;
+      break;
+   case LONG:
+      *datatype = TLONG;
+      break;
+   case DOUBLE:
+      *datatype = TDOUBLE;
+      break;
+   case BITSTR:
+      *datatype = TBIT;
+      break;
+   case STRING:
+      *datatype = TSTRING;
+      break;
+   default:
+      *datatype = 0;
+      ffpmsg("Bad return data type");
+      *status = gParse.status = PARSE_BAD_TYPE;
+      break;
+   }
+   gParse.datatype = *datatype;
+   FREE(gParse.expr);
+
+   if( result->operation==CONST_OP ) *nelem = - *nelem;
+   return(*status);
+}
+
+/*--------------------------------------------------------------------------*/
+void ffcprs( void )  /*  No parameters                                      */
+/*                                                                          */
+/* Clear the parser, making it ready to accept a new expression.            */
+/*--------------------------------------------------------------------------*/
+{
+   int col, node, i;
+
+   if( gParse.nCols > 0 ) {
+      FREE( gParse.colData  );
+      for( col=0; col<gParse.nCols; col++ ) {
+         if( gParse.varData[col].undef == NULL ) continue;
+         if( gParse.varData[col].type  == BITSTR )
+           FREE( ((char**)gParse.varData[col].data)[0] );
+         free( gParse.varData[col].undef );
+      }
+      FREE( gParse.varData );
+      gParse.nCols = 0;
+   }
+
+   if( gParse.nNodes > 0 ) {
+      node = gParse.nNodes;
+      while( node-- ) {
+         if( gParse.Nodes[node].operation==gtifilt_fct ) {
+            i = gParse.Nodes[node].SubNodes[0];
+            if (gParse.Nodes[ i ].value.data.ptr)
+	        FREE( gParse.Nodes[ i ].value.data.ptr );
+         }
+         else if( gParse.Nodes[node].operation==regfilt_fct ) {
+            i = gParse.Nodes[node].SubNodes[0];
+            fits_free_region( (SAORegion *)gParse.Nodes[ i ].value.data.ptr );
+         }
+      }
+      gParse.nNodes = 0;
+   }
+   if( gParse.Nodes ) free( gParse.Nodes );
+   gParse.Nodes = NULL;
+
+   gParse.hdutype = ANY_HDU;
+   gParse.pixFilter = 0;
+}
+
+/*---------------------------------------------------------------------------*/
+int parse_data( long    totalrows,     /* I - Total rows to be processed     */
+                long    offset,        /* I - Number of rows skipped at start*/
+                long    firstrow,      /* I - First row of this iteration    */
+                long    nrows,         /* I - Number of rows in this iter    */
+                int      nCols,        /* I - Number of columns in use       */
+                iteratorCol *colData,  /* IO- Column information/data        */
+                void    *userPtr )     /* I - Data handling instructions     */
+/*                                                                           */
+/* Iterator work function which calls the parser and copies the results      */
+/* into either an OutputCol or a data pointer supplied in the userPtr        */
+/* structure.                                                                */
+/*---------------------------------------------------------------------------*/
+{
+    int status, constant=0, anyNullThisTime=0;
+    long jj, kk, idx, remain, ntodo;
+    Node *result;
+    iteratorCol * outcol;
+
+    /* declare variables static to preserve their values between calls */
+    static void *Data, *Null;
+    static int  datasize;
+    static long lastRow, repeat, resDataSize;
+    static LONGLONG jnull;
+    static parseInfo *userInfo;
+    static long zeros[4] = {0,0,0,0};
+
+    if (DEBUG_PIXFILTER)
+       printf("parse_data(total=%ld, offset=%ld, first=%ld, rows=%ld, cols=%d)\n",
+                totalrows, offset, firstrow, nrows, nCols);
+    /*--------------------------------------------------------*/
+    /*  Initialization procedures: execute on the first call  */
+    /*--------------------------------------------------------*/
+    outcol = colData + (nCols - 1);
+    if (firstrow == offset+1)
+    {
+       userInfo = (parseInfo*)userPtr;
+       userInfo->anyNull = 0;
+
+       if( userInfo->maxRows>0 )
+          userInfo->maxRows = minvalue(totalrows,userInfo->maxRows);
+       else if( userInfo->maxRows<0 )
+          userInfo->maxRows = totalrows;
+       else
+          userInfo->maxRows = nrows;
+
+       lastRow = firstrow + userInfo->maxRows - 1;
+
+       if( userInfo->dataPtr==NULL ) {
+
+          if( outcol->iotype == InputCol ) {
+             ffpmsg("Output column for parser results not found!");
+             return( PARSE_NO_OUTPUT );
+          }
+          /* Data gets set later */
+          Null = outcol->array;
+          userInfo->datatype = outcol->datatype;
+
+          /* Check for a TNULL/BLANK keyword for output column/image */
+
+          status = 0;
+          jnull = 0;
+          if (gParse.hdutype == IMAGE_HDU) {
+             if (gParse.pixFilter->blank)
+                jnull = (LONGLONG) gParse.pixFilter->blank;
+          }
+          else {
+             ffgknjj( outcol->fptr, "TNULL", outcol->colnum,
+                        1, &jnull, (int*)&jj, &status );
+
+             if( status==BAD_INTKEY ) {
+                /*  Probably ASCII table with text TNULL keyword  */
+                switch( userInfo->datatype ) {
+                   case TSHORT:  jnull = (LONGLONG) SHRT_MIN;      break;
+                   case TINT:    jnull = (LONGLONG) INT_MIN;       break;
+                   case TLONG:   jnull = (LONGLONG) LONG_MIN;      break;
+                }
+             }
+          }
+          repeat = outcol->repeat;
+          if (DEBUG_PIXFILTER)
+            printf("parse_data: using null value %ld\n", jnull);
+       } else {
+
+          Data = userInfo->dataPtr;
+          Null = (userInfo->nullPtr ? userInfo->nullPtr : zeros);
+          repeat = gParse.Nodes[gParse.resultNode].value.nelem;
+
+       }
+
+       /* Determine the size of each element of the returned result */
+
+       switch( userInfo->datatype ) {
+       case TBIT:       /*  Fall through to TBYTE  */
+       case TLOGICAL:   /*  Fall through to TBYTE  */
+       case TBYTE:     datasize = sizeof(char);     break;
+       case TSHORT:    datasize = sizeof(short);    break;
+       case TINT:      datasize = sizeof(int);      break;
+       case TLONG:     datasize = sizeof(long);     break;
+       case TLONGLONG: datasize = sizeof(LONGLONG); break;
+       case TFLOAT:    datasize = sizeof(float);    break;
+       case TDOUBLE:   datasize = sizeof(double);   break;
+       case TSTRING:   datasize = sizeof(char*);    break;
+       }
+
+       /* Determine the size of each element of the calculated result */
+       /*   (only matters for numeric/logical data)                   */
+
+       switch( gParse.Nodes[gParse.resultNode].type ) {
+       case BOOLEAN:   resDataSize = sizeof(char);    break;
+       case LONG:      resDataSize = sizeof(long);    break;
+       case DOUBLE:    resDataSize = sizeof(double);  break;
+       }
+    }
+
+    /*-------------------------------------------*/
+    /*  Main loop: process all the rows of data  */
+    /*-------------------------------------------*/
+
+    /*  If writing to output column, set first element to appropriate  */
+    /*  null value.  If no NULLs encounter, zero out before returning. */
+          if (DEBUG_PIXFILTER)
+            printf("parse_data: using null value %ld\n", jnull);
+
+
+    if( userInfo->dataPtr == NULL ) {
+       /* First, reset Data pointer to start of output array */
+       Data = (char*) outcol->array + datasize;
+
+       switch( userInfo->datatype ) {
+       case TLOGICAL: *(char  *)Null = 'U';             break;
+       case TBYTE:    *(char  *)Null = (char )jnull;    break;
+       case TSHORT:   *(short *)Null = (short)jnull;    break;
+       case TINT:     *(int   *)Null = (int  )jnull;    break;
+       case TLONG:    *(long  *)Null = (long )jnull;    break;
+       case TLONGLONG: *(LONGLONG  *)Null = (LONGLONG )jnull;    break;
+       case TFLOAT:   *(float *)Null = FLOATNULLVALUE;  break;
+       case TDOUBLE:  *(double*)Null = DOUBLENULLVALUE; break;
+       case TSTRING: (*(char **)Null)[0] = '\1';
+                     (*(char **)Null)[1] = '\0';        break;
+       }
+    }
+
+    /* Alter nrows in case calling routine didn't want to do all rows */
+
+    nrows = minvalue(nrows,lastRow-firstrow+1);
+
+    Setup_DataArrays( nCols, colData, firstrow, nrows );
+
+    /* Parser allocates arrays for each column and calculation it performs. */
+    /* Limit number of rows processed during each pass to reduce memory     */
+    /* requirements... In most cases, iterator will limit rows to less      */
+    /* than 2500 rows per iteration, so this is really only relevant for    */
+    /* hk-compressed files which must be decompressed in memory and sent    */
+    /* whole to parse_data in a single iteration.                           */
+
+    remain = nrows;
+    while( remain ) {
+       ntodo = minvalue(remain,2500);
+       Evaluate_Parser ( firstrow, ntodo );
+       if( gParse.status ) break;
+
+       firstrow += ntodo;
+       remain   -= ntodo;
+
+       /*  Copy results into data array  */
+
+       result = gParse.Nodes + gParse.resultNode;
+       if( result->operation==CONST_OP ) constant = 1;
+
+       switch( result->type ) {
+
+       case BOOLEAN:
+       case LONG:
+       case DOUBLE:
+          if( constant ) {
+             char undef=0;
+             for( kk=0; kk<ntodo; kk++ )
+                for( jj=0; jj<repeat; jj++ )
+                   ffcvtn( gParse.datatype,
+                           &(result->value.data),
+                           &undef, result->value.nelem /* 1 */,
+                           userInfo->datatype, Null,
+                           (char*)Data + (kk*repeat+jj)*datasize,
+                           &anyNullThisTime, &gParse.status );
+          } else {
+             if ( repeat == result->value.nelem ) {
+                ffcvtn( gParse.datatype,
+                        result->value.data.ptr,
+                        result->value.undef,
+                        result->value.nelem*ntodo,
+                        userInfo->datatype, Null, Data,
+                        &anyNullThisTime, &gParse.status );
+             } else if( result->value.nelem == 1 ) {
+                for( kk=0; kk<ntodo; kk++ )
+                   for( jj=0; jj<repeat; jj++ ) {
+                      ffcvtn( gParse.datatype,
+                              (char*)result->value.data.ptr + kk*resDataSize,
+                              (char*)result->value.undef + kk,
+                              1, userInfo->datatype, Null,
+                              (char*)Data + (kk*repeat+jj)*datasize,
+                              &anyNullThisTime, &gParse.status );
+                   }
+             } else {
+                int nCopy;
+                nCopy = minvalue( repeat, result->value.nelem );
+                for( kk=0; kk<ntodo; kk++ ) {
+                   ffcvtn( gParse.datatype,
+                           (char*)result->value.data.ptr
+                                  + kk*result->value.nelem*resDataSize,
+                           (char*)result->value.undef
+                                  + kk*result->value.nelem,
+                           nCopy, userInfo->datatype, Null,
+                           (char*)Data + (kk*repeat)*datasize,
+                           &anyNullThisTime, &gParse.status );
+                   if( nCopy < repeat ) {
+                      memset( (char*)Data + (kk*repeat+nCopy)*datasize,
+                              0, (repeat-nCopy)*datasize);
+                   }
+                }
+
+             }
+             if( result->operation>0 ) {
+                FREE( result->value.data.ptr );
+             }
+          }
+          if( gParse.status==OVERFLOW_ERR ) {
+             gParse.status = NUM_OVERFLOW;
+             ffpmsg("Numerical overflow while converting expression to necessary datatype");
+          }
+          break;
+
+       case BITSTR:
+          switch( userInfo->datatype ) {
+          case TBYTE:
+             idx = -1;
+             for( kk=0; kk<ntodo; kk++ ) {
+                for( jj=0; jj<result->value.nelem; jj++ ) {
+                   if( jj%8 == 0 )
+                      ((char*)Data)[++idx] = 0;
+                   if( constant ) {
+                      if( result->value.data.str[jj]=='1' )
+                         ((char*)Data)[idx] |= 128>>(jj%8);
+                   } else {
+                      if( result->value.data.strptr[kk][jj]=='1' )
+                         ((char*)Data)[idx] |= 128>>(jj%8);
+                   }
+                }
+             }
+             break;
+          case TBIT:
+          case TLOGICAL:
+             if( constant ) {
+                for( kk=0; kk<ntodo; kk++ )
+                   for( jj=0; jj<result->value.nelem; jj++ ) {
+                      ((char*)Data)[ jj+kk*result->value.nelem ] =
+                         ( result->value.data.str[jj]=='1' );
+                   }
+             } else {
+                for( kk=0; kk<ntodo; kk++ )
+                   for( jj=0; jj<result->value.nelem; jj++ ) {
+                      ((char*)Data)[ jj+kk*result->value.nelem ] =
+                         ( result->value.data.strptr[kk][jj]=='1' );
+                   }
+             }
+             break; 
+          case TSTRING:
+             if( constant ) {
+                for( jj=0; jj<ntodo; jj++ ) {
+                   strcpy( ((char**)Data)[jj], result->value.data.str );
+                }
+             } else {
+                for( jj=0; jj<ntodo; jj++ ) {
+                   strcpy( ((char**)Data)[jj], result->value.data.strptr[jj] );
+                }
+             }
+             break;
+          default:
+             ffpmsg("Cannot convert bit expression to desired type.");
+             gParse.status = PARSE_BAD_TYPE;
+             break;
+          }
+          if( result->operation>0 ) {
+             FREE( result->value.data.strptr[0] );
+             FREE( result->value.data.strptr );
+          }
+          break;
+
+       case STRING:
+          if( userInfo->datatype==TSTRING ) {
+             if( constant ) {
+                for( jj=0; jj<ntodo; jj++ )
+                   strcpy( ((char**)Data)[jj], result->value.data.str );
+             } else {
+                for( jj=0; jj<ntodo; jj++ )
+                   if( result->value.undef[jj] ) {
+                      anyNullThisTime = 1;
+                      strcpy( ((char**)Data)[jj],
+                              *(char **)Null );
+                   } else {
+                      strcpy( ((char**)Data)[jj],
+                              result->value.data.strptr[jj] );
+                   }
+             }
+          } else {
+             ffpmsg("Cannot convert string expression to desired type.");
+             gParse.status = PARSE_BAD_TYPE;
+          }
+          if( result->operation>0 ) {
+             FREE( result->value.data.strptr[0] );
+             FREE( result->value.data.strptr );
+          }
+          break;
+       }
+
+       if( gParse.status ) break;
+
+       /*  Increment Data to point to where the next block should go  */
+
+       if( result->type==BITSTR && userInfo->datatype==TBYTE )
+          Data = (char*)Data
+                    + datasize * ( (result->value.nelem+7)/8 ) * ntodo;
+       else if( result->type==STRING )
+          Data = (char*)Data + datasize * ntodo;
+       else
+          Data = (char*)Data + datasize * ntodo * repeat;
+    }
+
+    /* If no NULLs encountered during this pass, set Null value to */
+    /* zero to make the writing of the output column data faster   */
+
+    if( anyNullThisTime )
+       userInfo->anyNull = 1;
+    else if( userInfo->dataPtr == NULL ) {
+       if( userInfo->datatype == TSTRING )
+          memcpy( *(char **)Null, zeros, 2 );
+       else 
+          memcpy( Null, zeros, datasize );
+    }
+
+    /*-------------------------------------------------------*/
+    /*  Clean up procedures:  after processing all the rows  */
+    /*-------------------------------------------------------*/
+
+    /*  if the calling routine specified that only a limited number    */
+    /*  of rows in the table should be processed, return a value of -1 */
+    /*  once all the rows have been done, if no other error occurred.  */
+
+    if (gParse.hdutype != IMAGE_HDU && firstrow - 1 == lastRow) {
+           if (!gParse.status && userInfo->maxRows<totalrows) {
+                  return (-1);
+           }
+    }
+
+    return(gParse.status);  /* return successful status */
+}
+
+static void Setup_DataArrays( int nCols, iteratorCol *cols,
+                              long fRow, long nRows )
+    /***********************************************************************/
+    /*  Setup the varData array in gParse to contain the fits column data. */
+    /*  Then, allocate and initialize the necessary UNDEF arrays for each  */
+    /*  column used by the parser.                                         */
+    /***********************************************************************/
+{
+   int     i;
+   long    nelem, len, row, idx;
+   char  **bitStrs;
+   char  **sptr;
+   char   *barray;
+   long   *iarray;
+   double *rarray;
+   char msg[80];
+
+   gParse.firstDataRow = fRow;
+   gParse.nDataRows    = nRows;
+
+   /*  Resize and fill in UNDEF arrays for each column  */
+
+   for( i=0; i<nCols; i++ ) {
+
+      iteratorCol *icol = cols + i;
+      DataInfo *varData = gParse.varData + i;
+
+      if( icol->iotype == OutputCol ) continue;
+
+      nelem  = varData->nelem;
+      len    = nelem * nRows;
+
+      switch ( varData->type ) {
+
+      case BITSTR:
+      /* No need for UNDEF array, but must make string DATA array */
+         len = (nelem+1)*nRows;   /* Count '\0' */
+         bitStrs = (char**)varData->data;
+         if( bitStrs ) FREE( bitStrs[0] );
+         free( bitStrs );
+         bitStrs = (char**)malloc( nRows*sizeof(char*) );
+         if( bitStrs==NULL ) {
+            varData->data = varData->undef = NULL;
+            gParse.status = MEMORY_ALLOCATION;
+            break;
+         }
+         bitStrs[0] = (char*)malloc( len*sizeof(char) );
+         if( bitStrs[0]==NULL ) {
+            free( bitStrs );
+            varData->data = varData->undef = NULL;
+            gParse.status = MEMORY_ALLOCATION;
+            break;
+         }
+
+         for( row=0; row<nRows; row++ ) {
+            bitStrs[row] = bitStrs[0] + row*(nelem+1);
+            idx = (row)*( (nelem+7)/8 ) + 1;
+            for(len=0; len<nelem; len++) {
+               if( ((char*)icol->array)[idx] & (1<<(7-len%8)) )
+                  bitStrs[row][len] = '1';
+               else
+                  bitStrs[row][len] = '0';
+               if( len%8==7 ) idx++;
+            }
+            bitStrs[row][len] = '\0';
+         }
+         varData->undef = (char*)bitStrs;
+         varData->data  = (char*)bitStrs;
+         break;
+
+      case STRING:
+         sptr = (char**)icol->array;
+         if (varData->undef)
+            free( varData->undef );
+         varData->undef = (char*)malloc( nRows*sizeof(char) );
+         if( varData->undef==NULL ) {
+            gParse.status = MEMORY_ALLOCATION;
+            break;
+         }
+         row = nRows;
+         while( row-- )
+            varData->undef[row] =
+               ( **sptr != '\0' && FSTRCMP( sptr[0], sptr[row+1] )==0 );
+         varData->data  = sptr + 1;
+         break;
+
+      case BOOLEAN:
+         barray = (char*)icol->array;
+         if (varData->undef)
+            free( varData->undef );
+         varData->undef = (char*)malloc( len*sizeof(char) );
+         if( varData->undef==NULL ) {
+            gParse.status = MEMORY_ALLOCATION;
+            break;
+         }
+         while( len-- ) {
+            varData->undef[len] = 
+               ( barray[0]!=0 && barray[0]==barray[len+1] );
+         }
+         varData->data  = barray + 1;
+         break;
+
+      case LONG:
+         iarray = (long*)icol->array;
+         if (varData->undef)
+            free( varData->undef );
+         varData->undef = (char*)malloc( len*sizeof(char) );
+         if( varData->undef==NULL ) {
+            gParse.status = MEMORY_ALLOCATION;
+            break;
+         }
+         while( len-- ) {
+            varData->undef[len] = 
+               ( iarray[0]!=0L && iarray[0]==iarray[len+1] );
+         }
+         varData->data  = iarray + 1;
+         break;
+
+      case DOUBLE:
+         rarray = (double*)icol->array;
+         if (varData->undef)
+            free( varData->undef );
+         varData->undef = (char*)malloc( len*sizeof(char) );
+         if( varData->undef==NULL ) {
+            gParse.status = MEMORY_ALLOCATION;
+            break;
+         }
+         while( len-- ) {
+            varData->undef[len] = 
+               ( rarray[0]!=0.0 && rarray[0]==rarray[len+1]);
+         }
+         varData->data  = rarray + 1;
+         break;
+
+      default:
+         sprintf(msg, "SetupDataArrays, unhandled type %d\n",
+                varData->type);
+         ffpmsg(msg);
+      }
+
+      if( gParse.status ) {  /*  Deallocate NULL arrays of previous columns */
+         while( i-- ) {
+            varData = gParse.varData + i;
+            if( varData->type==BITSTR )
+               FREE( ((char**)varData->data)[0] );
+            FREE( varData->undef );
+            varData->undef = NULL;
+         }
+         return;
+      }
+   }
+}
+
+/*--------------------------------------------------------------------------*/
+int ffcvtn( int   inputType,  /* I - Data type of input array               */
+            void  *input,     /* I - Input array of type inputType          */
+            char  *undef,     /* I - Array of flags indicating UNDEF elems  */
+            long  ntodo,      /* I - Number of elements to process          */
+            int   outputType, /* I - Data type of output array              */
+            void  *nulval,    /* I - Ptr to value to use for UNDEF elements */
+            void  *output,    /* O - Output array of type outputType        */
+            int   *anynull,   /* O - Any nulls flagged?                     */
+            int   *status )   /* O - Error status                           */
+/*                                                                          */
+/* Convert an array of any input data type to an array of any output        */
+/* data type, using an array of UNDEF flags to assign nulvals to            */
+/*--------------------------------------------------------------------------*/
+{
+   long i;
+
+   switch( outputType ) {
+
+   case TLOGICAL:
+      switch( inputType ) {
+      case TLOGICAL:
+      case TBYTE:
+         for( i=0; i<ntodo; i++ )
+            if( ((unsigned char*)input)[i] )
+                ((unsigned char*)output)[i] = 1;
+            else
+                ((unsigned char*)output)[i] = 0;
+         break;
+      case TSHORT:
+         for( i=0; i<ntodo; i++ )
+            if( ((short*)input)[i] )
+                ((unsigned char*)output)[i] = 1;
+            else
+                ((unsigned char*)output)[i] = 0;
+         break;
+      case TLONG:
+         for( i=0; i<ntodo; i++ )
+            if( ((long*)input)[i] )
+                ((unsigned char*)output)[i] = 1;
+            else
+                ((unsigned char*)output)[i] = 0;
+         break;
+      case TFLOAT:
+         for( i=0; i<ntodo; i++ )
+            if( ((float*)input)[i] )
+                ((unsigned char*)output)[i] = 1;
+            else
+                ((unsigned char*)output)[i] = 0;
+         break;
+      case TDOUBLE:
+         for( i=0; i<ntodo; i++ )
+            if( ((double*)input)[i] )
+                ((unsigned char*)output)[i] = 1;
+            else
+                ((unsigned char*)output)[i] = 0;
+         break;
+      default:
+         *status = BAD_DATATYPE;
+         break;
+      }
+      for(i=0;i<ntodo;i++) {
+         if( undef[i] ) {
+            ((unsigned char*)output)[i] = *(unsigned char*)nulval;
+            *anynull = 1;
+         }
+      }
+      break;
+
+   case TBYTE:
+      switch( inputType ) {
+      case TLOGICAL:
+      case TBYTE:
+         for( i=0; i<ntodo; i++ )
+            ((unsigned char*)output)[i] = ((unsigned char*)input)[i];
+         break;
+      case TSHORT:
+         fffi2i1((short*)input,ntodo,1.,0.,0,0,0,NULL,NULL,(unsigned char*)output,status);
+         break;
+      case TLONG:
+         for (i = 0; i < ntodo; i++) {
+            if( undef[i] ) {
+               ((unsigned char*)output)[i] = *(unsigned char*)nulval;
+               *anynull = 1;
+            } else {
+               if( ((long*)input)[i] < 0 ) {
+                  *status = OVERFLOW_ERR;
+                  ((unsigned char*)output)[i] = 0;
+               } else if( ((long*)input)[i] > UCHAR_MAX ) {
+                  *status = OVERFLOW_ERR;
+                  ((unsigned char*)output)[i] = UCHAR_MAX;
+               } else
+                  ((unsigned char*)output)[i] = 
+                     (unsigned char) ((long*)input)[i];
+            }
+         }
+         return( *status );
+      case TFLOAT:
+         fffr4i1((float*)input,ntodo,1.,0.,0,0,NULL,NULL,
+                 (unsigned char*)output,status);
+         break;
+      case TDOUBLE:
+         fffr8i1((double*)input,ntodo,1.,0.,0,0,NULL,NULL,
+                 (unsigned char*)output,status);
+         break;
+      default:
+         *status = BAD_DATATYPE;
+         break;
+      }
+      for(i=0;i<ntodo;i++) {
+         if( undef[i] ) {
+            ((unsigned char*)output)[i] = *(unsigned char*)nulval;
+            *anynull = 1;
+         }
+      }
+      break;
+
+   case TSHORT:
+      switch( inputType ) {
+      case TLOGICAL:
+      case TBYTE:
+         for( i=0; i<ntodo; i++ )
+            ((short*)output)[i] = ((unsigned char*)input)[i];
+         break;
+      case TSHORT:
+         for( i=0; i<ntodo; i++ )
+            ((short*)output)[i] = ((short*)input)[i];
+         break;
+      case TLONG:
+         for (i = 0; i < ntodo; i++) {
+            if( undef[i] ) {
+               ((short*)output)[i] = *(short*)nulval;
+               *anynull = 1;
+            } else {
+               if( ((long*)input)[i] < SHRT_MIN ) {
+                  *status = OVERFLOW_ERR;
+                  ((short*)output)[i] = SHRT_MIN;
+               } else if ( ((long*)input)[i] > SHRT_MAX ) {
+                  *status = OVERFLOW_ERR;
+                  ((short*)output)[i] = SHRT_MAX;
+               } else
+                  ((short*)output)[i] = (short) ((long*)input)[i];
+            }
+         }
+         return( *status );
+      case TFLOAT:
+         fffr4i2((float*)input,ntodo,1.,0.,0,0,NULL,NULL,
+                 (short*)output,status);
+         break;
+      case TDOUBLE:
+         fffr8i2((double*)input,ntodo,1.,0.,0,0,NULL,NULL,
+                 (short*)output,status);
+         break;
+      default:
+         *status = BAD_DATATYPE;
+         break;
+      }
+      for(i=0;i<ntodo;i++) {
+         if( undef[i] ) {
+            ((short*)output)[i] = *(short*)nulval;
+            *anynull = 1;
+         }
+      }
+      break;
+
+   case TINT:
+      switch( inputType ) {
+      case TLOGICAL:
+      case TBYTE:
+         for( i=0; i<ntodo; i++ )
+            ((int*)output)[i] = ((unsigned char*)input)[i];
+         break;
+      case TSHORT:
+         for( i=0; i<ntodo; i++ )
+            ((int*)output)[i] = ((short*)input)[i];
+         break;
+      case TLONG:
+         for( i=0; i<ntodo; i++ )
+            ((int*)output)[i] = ((long*)input)[i];
+         break;
+      case TFLOAT:
+         fffr4int((float*)input,ntodo,1.,0.,0,0,NULL,NULL,
+                  (int*)output,status);
+         break;
+      case TDOUBLE:
+         fffr8int((double*)input,ntodo,1.,0.,0,0,NULL,NULL,
+                  (int*)output,status);
+         break;
+      default:
+         *status = BAD_DATATYPE;
+         break;
+      }
+      for(i=0;i<ntodo;i++) {
+         if( undef[i] ) {
+            ((int*)output)[i] = *(int*)nulval;
+            *anynull = 1;
+         }
+      }
+      break;
+
+   case TLONG:
+      switch( inputType ) {
+      case TLOGICAL:
+      case TBYTE:
+         for( i=0; i<ntodo; i++ )
+            ((long*)output)[i] = ((unsigned char*)input)[i];
+         break;
+      case TSHORT:
+         for( i=0; i<ntodo; i++ )
+            ((long*)output)[i] = ((short*)input)[i];
+         break;
+      case TLONG:
+         for( i=0; i<ntodo; i++ )
+            ((long*)output)[i] = ((long*)input)[i];
+         break;
+      case TFLOAT:
+         fffr4i4((float*)input,ntodo,1.,0.,0,0,NULL,NULL,
+                 (long*)output,status);
+         break;
+      case TDOUBLE:
+         fffr8i4((double*)input,ntodo,1.,0.,0,0,NULL,NULL,
+                 (long*)output,status);
+         break;
+      default:
+         *status = BAD_DATATYPE;
+         break;
+      }
+      for(i=0;i<ntodo;i++) {
+         if( undef[i] ) {
+            ((long*)output)[i] = *(long*)nulval;
+            *anynull = 1;
+         }
+      }
+      break;
+
+   case TLONGLONG:
+      switch( inputType ) {
+      case TLOGICAL:
+      case TBYTE:
+         for( i=0; i<ntodo; i++ )
+            ((LONGLONG*)output)[i] = ((unsigned char*)input)[i];
+         break;
+      case TSHORT:
+         for( i=0; i<ntodo; i++ )
+            ((LONGLONG*)output)[i] = ((short*)input)[i];
+         break;
+      case TLONG:
+         for( i=0; i<ntodo; i++ )
+            ((LONGLONG*)output)[i] = ((long*)input)[i];
+         break;
+      case TFLOAT:
+         fffr4i8((float*)input,ntodo,1.,0.,0,0,NULL,NULL,
+                 (LONGLONG*)output,status);
+         break;
+      case TDOUBLE:
+         fffr8i8((double*)input,ntodo,1.,0.,0,0,NULL,NULL,
+                 (LONGLONG*)output,status);
+
+         break;
+      default:
+         *status = BAD_DATATYPE;
+         break;
+      }
+      for(i=0;i<ntodo;i++) {
+         if( undef[i] ) {
+            ((LONGLONG*)output)[i] = *(LONGLONG*)nulval;
+            *anynull = 1;
+         }
+      }
+      break;
+
+   case TFLOAT:
+      switch( inputType ) {
+      case TLOGICAL:
+      case TBYTE:
+         for( i=0; i<ntodo; i++ )
+            ((float*)output)[i] = ((unsigned char*)input)[i];
+         break;
+      case TSHORT:
+         for( i=0; i<ntodo; i++ )
+            ((float*)output)[i] = ((short*)input)[i];
+         break;
+      case TLONG:
+         for( i=0; i<ntodo; i++ )
+            ((float*)output)[i] = (float) ((long*)input)[i];
+         break;
+      case TFLOAT:
+         for( i=0; i<ntodo; i++ )
+            ((float*)output)[i] = ((float*)input)[i];
+         break;
+      case TDOUBLE:
+         fffr8r4((double*)input,ntodo,1.,0.,0,0,NULL,NULL,
+                 (float*)output,status);
+         break;
+      default:
+         *status = BAD_DATATYPE;
+         break;
+      }
+      for(i=0;i<ntodo;i++) {
+         if( undef[i] ) {
+            ((float*)output)[i] = *(float*)nulval;
+            *anynull = 1;
+         }
+      }
+      break;
+
+   case TDOUBLE:
+      switch( inputType ) {
+      case TLOGICAL:
+      case TBYTE:
+         for( i=0; i<ntodo; i++ )
+            ((double*)output)[i] = ((unsigned char*)input)[i];
+         break;
+      case TSHORT:
+         for( i=0; i<ntodo; i++ )
+            ((double*)output)[i] = ((short*)input)[i];
+         break;
+      case TLONG:
+         for( i=0; i<ntodo; i++ )
+            ((double*)output)[i] = ((long*)input)[i];
+         break;
+      case TFLOAT:
+         for( i=0; i<ntodo; i++ )
+            ((double*)output)[i] = ((float*)input)[i];
+         break;
+      case TDOUBLE:
+         for( i=0; i<ntodo; i++ )
+            ((double*)output)[i] = ((double*)input)[i];
+         break;
+      default:
+         *status = BAD_DATATYPE;
+         break;
+      }
+      for(i=0;i<ntodo;i++) {
+         if( undef[i] ) {
+            ((double*)output)[i] = *(double*)nulval;
+            *anynull = 1;
+         }
+      }
+      break;
+
+   default:
+      *status = BAD_DATATYPE;
+      break;
+   }
+
+   return ( *status );
+}
+
+/*---------------------------------------------------------------------------*/
+int fffrwc( fitsfile *fptr,        /* I - Input FITS file                    */
+            char     *expr,        /* I - Boolean expression                 */
+            char     *timeCol,     /* I - Name of time column                */
+            char     *parCol,      /* I - Name of parameter column           */
+            char     *valCol,      /* I - Name of value column               */
+            long     ntimes,       /* I - Number of distinct times in file   */
+            double   *times,       /* O - Array of times in file             */
+            char     *time_status, /* O - Array of boolean results           */
+            int      *status )     /* O - Error status                       */
+/*                                                                           */
+/* Evaluate a boolean expression for each time in a compressed file,         */
+/* returning an array of flags indicating which times evaluated to TRUE/FALSE*/
+/*---------------------------------------------------------------------------*/
+{
+   parseInfo Info;
+   long alen, width;
+   int parNo, typecode;
+   int naxis, constant, nCol=0;
+   long nelem, naxes[MAXDIMS], elem;
+   char result;
+
+   if( *status ) return( *status );
+
+   fits_get_colnum( fptr, CASEINSEN, timeCol, &gParse.timeCol, status );
+   fits_get_colnum( fptr, CASEINSEN, parCol,  &gParse.parCol , status );
+   fits_get_colnum( fptr, CASEINSEN, valCol,  &gParse.valCol, status );
+   if( *status ) return( *status );
+   
+   if( ffiprs( fptr, 1, expr, MAXDIMS, &Info.datatype, &nelem,
+               &naxis, naxes, status ) ) {
+      ffcprs();
+      return( *status );
+   }
+   if( nelem<0 ) {
+      constant = 1;
+      nelem = -nelem;
+      nCol = gParse.nCols;
+      gParse.nCols = 0;    /*  Ignore all column references  */
+   } else
+      constant = 0;
+
+   if( Info.datatype!=TLOGICAL || nelem!=1 ) {
+      ffcprs();
+      ffpmsg("Expression does not evaluate to a logical scalar.");
+      return( *status = PARSE_BAD_TYPE );
+   }
+
+   /*******************************************/
+   /* Allocate data arrays for each parameter */
+   /*******************************************/
+   
+   parNo = gParse.nCols;
+   while( parNo-- ) {
+      switch( gParse.colData[parNo].datatype ) {
+      case TLONG:
+         if( (gParse.colData[parNo].array =
+              (long *)malloc( (ntimes+1)*sizeof(long) )) )
+            ((long*)gParse.colData[parNo].array)[0] = 1234554321;
+         else
+            *status = MEMORY_ALLOCATION;
+         break;
+      case TDOUBLE:
+         if( (gParse.colData[parNo].array =
+              (double *)malloc( (ntimes+1)*sizeof(double) )) )
+            ((double*)gParse.colData[parNo].array)[0] = DOUBLENULLVALUE;
+         else
+            *status = MEMORY_ALLOCATION;
+         break;
+      case TSTRING:
+         if( !fits_get_coltype( fptr, gParse.valCol, &typecode,
+                                &alen, &width, status ) ) {
+            alen++;
+            if( (gParse.colData[parNo].array =
+                 (char **)malloc( (ntimes+1)*sizeof(char*) )) ) {
+               if( (((char **)gParse.colData[parNo].array)[0] =
+                    (char *)malloc( (ntimes+1)*sizeof(char)*alen )) ) {
+                  for( elem=1; elem<=ntimes; elem++ )
+                     ((char **)gParse.colData[parNo].array)[elem] =
+                        ((char **)gParse.colData[parNo].array)[elem-1]+alen;
+                  ((char **)gParse.colData[parNo].array)[0][0] = '\0';
+               } else {
+                  free( gParse.colData[parNo].array );
+                  *status = MEMORY_ALLOCATION;
+               }
+            } else {
+               *status = MEMORY_ALLOCATION;
+            }
+         }
+         break;
+      }
+      if( *status ) {
+         while( parNo-- ) {
+            if( gParse.colData[parNo].datatype==TSTRING )
+               FREE( ((char **)gParse.colData[parNo].array)[0] );
+            FREE( gParse.colData[parNo].array );
+         }
+         return( *status );
+      }
+   }
+   
+   /**********************************************************************/
+   /* Read data from columns needed for the expression and then parse it */
+   /**********************************************************************/
+   
+   if( !uncompress_hkdata( fptr, ntimes, times, status ) ) {
+      if( constant ) {
+         result = gParse.Nodes[gParse.resultNode].value.data.log;
+         elem = ntimes;
+         while( elem-- ) time_status[elem] = result;
+      } else {
+         Info.dataPtr  = time_status;
+         Info.nullPtr  = NULL;
+         Info.maxRows  = ntimes;
+         *status       = parse_data( ntimes, 0, 1, ntimes, gParse.nCols,
+                                     gParse.colData, (void*)&Info );
+      }
+   }
+   
+   /************/
+   /* Clean up */
+   /************/
+   
+   parNo = gParse.nCols;
+   while ( parNo-- ) {
+      if( gParse.colData[parNo].datatype==TSTRING )
+         FREE( ((char **)gParse.colData[parNo].array)[0] );
+      FREE( gParse.colData[parNo].array );
+   }
+   
+   if( constant ) gParse.nCols = nCol;
+
+   ffcprs();
+   return(*status);
+}
+
+/*---------------------------------------------------------------------------*/
+int uncompress_hkdata( fitsfile *fptr,
+                       long     ntimes,
+                       double   *times,
+                       int      *status )
+/*                                                                           */
+/* description                                                               */
+/*---------------------------------------------------------------------------*/
+{
+   char parName[256], *sPtr[1], found[1000];
+   int parNo, anynul;
+   long naxis2, row, currelem;
+   double currtime, newtime;
+
+   sPtr[0] = parName;
+   currelem = 0;
+   currtime = -1e38;
+
+   parNo=gParse.nCols;
+   while( parNo-- ) found[parNo] = 0;
+
+   if( ffgkyj( fptr, "NAXIS2", &naxis2, NULL, status ) ) return( *status );
+
+   for( row=1; row<=naxis2; row++ ) {
+      if( ffgcvd( fptr, gParse.timeCol, row, 1L, 1L, 0.0,
+                  &newtime, &anynul, status ) ) return( *status );
+      if( newtime != currtime ) {
+         /*  New time encountered... propogate parameters to next row  */
+         if( currelem==ntimes ) {
+            ffpmsg("Found more unique time stamps than caller indicated");
+            return( *status = PARSE_BAD_COL );
+         }
+         times[currelem++] = currtime = newtime;
+         parNo = gParse.nCols;
+         while( parNo-- ) {
+            switch( gParse.colData[parNo].datatype ) {
+            case TLONG:
+               ((long*)gParse.colData[parNo].array)[currelem] =
+                  ((long*)gParse.colData[parNo].array)[currelem-1];
+               break;
+            case TDOUBLE:
+               ((double*)gParse.colData[parNo].array)[currelem] =
+                  ((double*)gParse.colData[parNo].array)[currelem-1];
+               break;
+            case TSTRING:
+               strcpy( ((char **)gParse.colData[parNo].array)[currelem],
+                       ((char **)gParse.colData[parNo].array)[currelem-1] );
+               break;
+            }
+         }
+      }
+
+      if( ffgcvs( fptr, gParse.parCol, row, 1L, 1L, "",
+                  sPtr, &anynul, status ) ) return( *status );
+      parNo = gParse.nCols;
+      while( parNo-- )
+         if( !strcasecmp( parName, gParse.varData[parNo].name ) ) break;
+
+      if( parNo>=0 ) {
+         found[parNo] = 1; /* Flag this parameter as found */
+         switch( gParse.colData[parNo].datatype ) {
+         case TLONG:
+            ffgcvj( fptr, gParse.valCol, row, 1L, 1L,
+                    ((long*)gParse.colData[parNo].array)[0],
+                    ((long*)gParse.colData[parNo].array)+currelem,
+                    &anynul, status );
+            break;
+         case TDOUBLE:
+            ffgcvd( fptr, gParse.valCol, row, 1L, 1L,
+                    ((double*)gParse.colData[parNo].array)[0],
+                    ((double*)gParse.colData[parNo].array)+currelem,
+                    &anynul, status );
+            break;
+         case TSTRING:
+            ffgcvs( fptr, gParse.valCol, row, 1L, 1L,
+                    ((char**)gParse.colData[parNo].array)[0],
+                    ((char**)gParse.colData[parNo].array)+currelem,
+                    &anynul, status );
+            break;
+         }
+         if( *status ) return( *status );
+      }
+   }
+
+   if( currelem<ntimes ) {
+      ffpmsg("Found fewer unique time stamps than caller indicated");
+      return( *status = PARSE_BAD_COL );
+   }
+
+   /*  Check for any parameters which were not located in the table  */
+   parNo = gParse.nCols;
+   while( parNo-- )
+      if( !found[parNo] ) {
+         sprintf( parName, "Parameter not found: %-30s", 
+                  gParse.varData[parNo].name );
+         ffpmsg( parName );
+         *status = PARSE_SYNTAX_ERR;
+      }
+   return( *status );
+}
+
+/*---------------------------------------------------------------------------*/
+int ffffrw( fitsfile *fptr,         /* I - Input FITS file                   */
+            char     *expr,         /* I - Boolean expression                */
+            long     *rownum,       /* O - First row of table to eval to T   */
+            int      *status )      /* O - Error status                      */
+/*                                                                           */
+/* Evaluate a boolean expression, returning the row number of the first      */
+/* row which evaluates to TRUE                                               */
+/*---------------------------------------------------------------------------*/
+{
+   int naxis, constant, dtype;
+   long nelem, naxes[MAXDIMS];
+   char result;
+
+   if( *status ) return( *status );
+
+   FFLOCK;
+   if( ffiprs( fptr, 0, expr, MAXDIMS, &dtype, &nelem, &naxis,
+               naxes, status ) ) {
+      ffcprs();
+      FFUNLOCK;
+      return( *status );
+   }
+   if( nelem<0 ) {
+      constant = 1;
+      nelem = -nelem;
+   } else
+      constant = 0;
+
+   if( dtype!=TLOGICAL || nelem!=1 ) {
+      ffcprs();
+      ffpmsg("Expression does not evaluate to a logical scalar.");
+      FFUNLOCK;
+      return( *status = PARSE_BAD_TYPE );
+   }
+
+   *rownum = 0;
+   if( constant ) { /* No need to call parser... have result from ffiprs */
+      result = gParse.Nodes[gParse.resultNode].value.data.log;
+      if( result ) {
+         /*  Make sure there is at least 1 row in table  */
+         ffgnrw( fptr, &nelem, status );
+         if( nelem )
+            *rownum = 1;
+      }
+   } else {
+      if( ffiter( gParse.nCols, gParse.colData, 0, 0,
+                  ffffrw_work, (void*)rownum, status ) == -1 )
+         *status = 0;  /* -1 indicates exitted without error before end... OK */
+   }
+
+   ffcprs();
+   FFUNLOCK;
+   return(*status);
+}
+
+/*---------------------------------------------------------------------------*/
+int ffffrw_work(long        totalrows, /* I - Total rows to be processed     */
+                long        offset,    /* I - Number of rows skipped at start*/
+                long        firstrow,  /* I - First row of this iteration    */
+                long        nrows,     /* I - Number of rows in this iter    */
+                int         nCols,     /* I - Number of columns in use       */
+                iteratorCol *colData,  /* IO- Column information/data        */
+                void        *userPtr ) /* I - Data handling instructions     */
+/*                                                                           */
+/* Iterator work function which calls the parser and searches for the        */
+/* first row which evaluates to TRUE.                                        */
+/*---------------------------------------------------------------------------*/
+{
+    long idx;
+    Node *result;
+
+    Evaluate_Parser( firstrow, nrows );
+
+    if( !gParse.status ) {
+
+       result = gParse.Nodes + gParse.resultNode;
+       if( result->operation==CONST_OP ) {
+
+          if( result->value.data.log ) {
+             *(long*)userPtr = firstrow;
+             return( -1 );
+          }
+
+       } else {
+
+          for( idx=0; idx<nrows; idx++ )
+             if( result->value.data.logptr[idx] && !result->value.undef[idx] ) {
+                *(long*)userPtr = firstrow + idx;
+                return( -1 );
+             }
+       }
+    }
+
+    return( gParse.status );
+}
+
+
+static int set_image_col_types (fitsfile * fptr, const char * name, int bitpix,
+                DataInfo * varInfo, iteratorCol *colIter) {
+
+   int istatus;
+   double tscale, tzero;
+   char temp[80];
+
+   switch (bitpix) {
+      case BYTE_IMG:
+      case SHORT_IMG:
+      case LONG_IMG:
+         istatus = 0;
+         if (fits_read_key(fptr, TDOUBLE, "BZERO", &tzero, NULL, &istatus))
+            tzero = 0.0;
+
+         istatus = 0;
+         if (fits_read_key(fptr, TDOUBLE, "BSCALE", &tscale, NULL, &istatus))
+            tscale = 1.0;
+
+         if (tscale == 1.0 && (tzero == 0.0 || tzero == 32768.0 )) {
+            varInfo->type     = LONG;
+            colIter->datatype = TLONG;
+         }
+         else {
+            varInfo->type     = DOUBLE;
+            colIter->datatype = TDOUBLE;
+            if (DEBUG_PIXFILTER)
+                printf("use DOUBLE for %s with BSCALE=%g/BZERO=%g\n",
+                        name, tscale, tzero);
+         }
+         break;
+
+      case LONGLONG_IMG:
+      case FLOAT_IMG:
+      case DOUBLE_IMG:
+         varInfo->type     = DOUBLE;
+         colIter->datatype = TDOUBLE;
+         break;
+      default:
+         sprintf(temp, "set_image_col_types: unrecognized image bitpix [%d]\n",
+                bitpix);
+         ffpmsg(temp);
+         return gParse.status = PARSE_BAD_TYPE;
+   }
+   return 0;
+}
+
+
+/*************************************************************************
+
+        Functions used by the evaluator to access FITS data
+            (find_column, find_keywd, allocateCol, load_column)
+
+ *************************************************************************/
+
+static int find_column( char *colName, void *itslval )
+{
+   FFSTYPE *thelval = (FFSTYPE*)itslval;
+   int col_cnt, status;
+   int colnum, typecode, type;
+   long repeat, width;
+   fitsfile *fptr;
+   char temp[80];
+   double tzero,tscale;
+   int istatus;
+   DataInfo *varInfo;
+   iteratorCol *colIter;
+
+if (DEBUG_PIXFILTER)
+   printf("find_column(%s)\n", colName);
+
+   if( *colName == '#' )
+      return( find_keywd( colName + 1, itslval ) );
+
+   fptr = gParse.def_fptr;
+
+   status = 0;
+   col_cnt = gParse.nCols;
+
+if (gParse.hdutype == IMAGE_HDU) {
+   int i;
+   if (!gParse.pixFilter) {
+      gParse.status = COL_NOT_FOUND;
+      ffpmsg("find_column: IMAGE_HDU but no PixelFilter");
+      return pERROR;
+   }
+
+   colnum = -1;
+   for (i = 0; i < gParse.pixFilter->count; ++i) {
+      if (!strcasecmp(colName, gParse.pixFilter->tag[i]))
+         colnum = i;
+   }
+   if (colnum < 0) {
+      sprintf(temp, "find_column: PixelFilter tag %s not found", colName);
+      ffpmsg(temp);
+      gParse.status = COL_NOT_FOUND;
+      return pERROR;
+   }
+
+   if( allocateCol( col_cnt, &gParse.status ) ) return pERROR;
+
+   varInfo = gParse.varData + col_cnt;
+   colIter = gParse.colData + col_cnt;
+
+   fptr = gParse.pixFilter->ifptr[colnum];
+   fits_get_img_param(fptr,
+                MAXDIMS,
+                &typecode, /* actually bitpix */
+                &varInfo->naxis,
+                &varInfo->naxes[0],
+                &status);
+   varInfo->nelem = 1;
+   type = COLUMN;
+   if (set_image_col_types(fptr, colName, typecode, varInfo, colIter))
+      return pERROR;
+   colIter->fptr = fptr;
+   colIter->iotype = InputCol;
+}
+else { /* HDU holds a table */
+   if( gParse.compressed )
+      colnum = gParse.valCol;
+   else
+      if( fits_get_colnum( fptr, CASEINSEN, colName, &colnum, &status ) ) {
+         if( status == COL_NOT_FOUND ) {
+            type = find_keywd( colName, itslval );
+            if( type != pERROR ) ffcmsg();
+            return( type );
+         }
+         gParse.status = status;
+         return pERROR;
+      }
+   
+   if( fits_get_coltype( fptr, colnum, &typecode,
+                         &repeat, &width, &status ) ) {
+      gParse.status = status;
+      return pERROR;
+   }
+
+   if( allocateCol( col_cnt, &gParse.status ) ) return pERROR;
+
+   varInfo = gParse.varData + col_cnt;
+   colIter = gParse.colData + col_cnt;
+
+   fits_iter_set_by_num( colIter, fptr, colnum, 0, InputCol );
+}
+
+   /*  Make sure we don't overflow variable name array  */
+   strncpy(varInfo->name,colName,MAXVARNAME);
+   varInfo->name[MAXVARNAME] = '\0';
+
+if (gParse.hdutype != IMAGE_HDU) {
+   switch( typecode ) {
+   case TBIT:
+      varInfo->type     = BITSTR;
+      colIter->datatype = TBYTE;
+      type = BITCOL;
+      break;
+   case TBYTE:
+   case TSHORT:
+   case TLONG:
+      /* The datatype of column with TZERO and TSCALE keywords might be 
+         float or double. 
+      */
+      sprintf(temp,"TZERO%d",colnum);
+      istatus = 0;
+      if(fits_read_key(fptr,TDOUBLE,temp,&tzero,NULL,&istatus)) {
+          tzero = 0.0;
+      } 
+      sprintf(temp,"TSCAL%d",colnum);
+      istatus = 0;
+      if(fits_read_key(fptr,TDOUBLE,temp,&tscale,NULL,&istatus)) {
+          tscale = 1.0;
+      } 
+      if (tscale == 1.0 && (tzero == 0.0 || tzero == 32768.0 )) {
+          varInfo->type     = LONG;
+          colIter->datatype = TLONG;
+/*    Reading an unsigned long column as a long can cause overflow errors.
+      Treat the column as a double instead.
+      } else if (tscale == 1.0 &&  tzero == 2147483648.0 ) {
+          varInfo->type     = LONG;
+          colIter->datatype = TULONG;
+ */
+
+      }
+      else {
+          varInfo->type     = DOUBLE;
+          colIter->datatype = TDOUBLE;
+      }
+      type = COLUMN;
+      break;
+/* 
+  For now, treat 8-byte integer columns as type double.
+  This can lose precision, so the better long term solution
+  will be to add support for TLONGLONG as a separate datatype.
+*/
+   case TLONGLONG:
+   case TFLOAT:
+   case TDOUBLE:
+      varInfo->type     = DOUBLE;
+      colIter->datatype = TDOUBLE;
+      type = COLUMN;
+      break;
+   case TLOGICAL:
+      varInfo->type     = BOOLEAN;
+      colIter->datatype = TLOGICAL;
+      type = BCOLUMN;
+      break;
+   case TSTRING:
+      varInfo->type     = STRING;
+      colIter->datatype = TSTRING;
+      type = SCOLUMN;
+      if ( width >= MAX_STRLEN ) {
+	sprintf(temp, "column %d is wider than maximum %d characters",
+		colnum, MAX_STRLEN-1);
+        ffpmsg(temp);
+	gParse.status = PARSE_LRG_VECTOR;
+	return pERROR;
+      }
+      if( gParse.hdutype == ASCII_TBL ) repeat = width;
+      break;
+   default:
+      if (typecode < 0) {
+        sprintf(temp, "variable-length array columns are not supported. typecode = %d", typecode);
+        ffpmsg(temp);
+      }
+      gParse.status = PARSE_BAD_TYPE;
+      return pERROR;
+   }
+   varInfo->nelem = repeat;
+   if( repeat>1 && typecode!=TSTRING ) {
+      if( fits_read_tdim( fptr, colnum, MAXDIMS,
+                          &varInfo->naxis,
+                          &varInfo->naxes[0], &status )
+          ) {
+         gParse.status = status;
+         return pERROR;
+      }
+   } else {
+      varInfo->naxis = 1;
+      varInfo->naxes[0] = 1;
+   }
+}
+   gParse.nCols++;
+   thelval->lng = col_cnt;
+
+   return( type );
+}
+
+static int find_keywd(char *keyname, void *itslval )
+{
+   FFSTYPE *thelval = (FFSTYPE*)itslval;
+   int status, type;
+   char keyvalue[FLEN_VALUE], dtype;
+   fitsfile *fptr;
+   double rval;
+   int bval;
+   long ival;
+
+   status = 0;
+   fptr = gParse.def_fptr;
+   if( fits_read_keyword( fptr, keyname, keyvalue, NULL, &status ) ) {
+      if( status == KEY_NO_EXIST ) {
+         /*  Do this since ffgkey doesn't put an error message on stack  */
+         sprintf(keyvalue, "ffgkey could not find keyword: %s",keyname);
+         ffpmsg(keyvalue);
+      }
+      gParse.status = status;
+      return( pERROR );
+   }
+      
+   if( fits_get_keytype( keyvalue, &dtype, &status ) ) {
+      gParse.status = status;
+      return( pERROR );
+   }
+      
+   switch( dtype ) {
+   case 'C':
+      fits_read_key_str( fptr, keyname, keyvalue, NULL, &status );
+      type = STRING;
+      strcpy( thelval->str , keyvalue );
+      break;
+   case 'L':
+      fits_read_key_log( fptr, keyname, &bval, NULL, &status );
+      type = BOOLEAN;
+      thelval->log = bval;
+      break;
+   case 'I':
+      fits_read_key_lng( fptr, keyname, &ival, NULL, &status );
+      type = LONG;
+      thelval->lng = ival;
+      break;
+   case 'F':
+      fits_read_key_dbl( fptr, keyname, &rval, NULL, &status );
+      type = DOUBLE;
+      thelval->dbl = rval;
+      break;
+   default:
+      type = pERROR;
+      break;
+   }
+
+   if( status ) {
+      gParse.status=status;
+      return pERROR;
+   }
+
+   return( type );
+}
+
+static int allocateCol( int nCol, int *status )
+{
+   if( (nCol%25)==0 ) {
+      if( nCol ) {
+         gParse.colData  = (iteratorCol*) realloc( gParse.colData,
+                                              (nCol+25)*sizeof(iteratorCol) );
+         gParse.varData  = (DataInfo   *) realloc( gParse.varData,
+                                              (nCol+25)*sizeof(DataInfo)    );
+      } else {
+         gParse.colData  = (iteratorCol*) malloc( 25*sizeof(iteratorCol) );
+         gParse.varData  = (DataInfo   *) malloc( 25*sizeof(DataInfo)    );
+      }
+      if(    gParse.colData  == NULL
+          || gParse.varData  == NULL    ) {
+         if( gParse.colData  ) free(gParse.colData);
+         if( gParse.varData  ) free(gParse.varData);
+         gParse.colData = NULL;
+         gParse.varData = NULL;
+         return( *status = MEMORY_ALLOCATION );
+      }
+   }
+   gParse.varData[nCol].data  = NULL;
+   gParse.varData[nCol].undef = NULL;
+   return 0;
+}
+
+static int load_column( int varNum, long fRow, long nRows,
+                        void *data, char *undef )
+{
+   iteratorCol *var = gParse.colData+varNum;
+   long nelem,nbytes,row,len,idx;
+   char **bitStrs, msg[80];
+   unsigned char *bytes;
+   int status = 0, anynul;
+
+  if (gParse.hdutype == IMAGE_HDU) {
+    /* This test would need to be on a per varNum basis to support
+     * cross HDU operations */
+    fits_read_imgnull(var->fptr, var->datatype, fRow, nRows,
+                data, undef, &anynul, &status);
+    if (DEBUG_PIXFILTER)
+        printf("load_column: IMAGE_HDU fRow=%ld, nRows=%ld => %d\n",
+                        fRow, nRows, status);
+  } else { 
+
+   nelem = nRows * var->repeat;
+
+   switch( var->datatype ) {
+   case TBYTE:
+      nbytes = ((var->repeat+7)/8) * nRows;
+      bytes = (unsigned char *)malloc( nbytes * sizeof(char) );
+
+      ffgcvb(var->fptr, var->colnum, fRow, 1L, nbytes,
+             0, bytes, &anynul, &status);
+
+      nelem = var->repeat;
+      bitStrs = (char **)data;
+      for( row=0; row<nRows; row++ ) {
+         idx = (row)*( (nelem+7)/8 ) + 1;
+         for(len=0; len<nelem; len++) {
+            if( bytes[idx] & (1<<(7-len%8)) )
+               bitStrs[row][len] = '1';
+            else
+               bitStrs[row][len] = '0';
+            if( len%8==7 ) idx++;
+         }
+         bitStrs[row][len] = '\0';
+      }
+
+      FREE( (char *)bytes );
+      break;
+   case TSTRING:
+      ffgcfs(var->fptr, var->colnum, fRow, 1L, nRows,
+             (char **)data, undef, &anynul, &status);
+      break;
+   case TLOGICAL:
+      ffgcfl(var->fptr, var->colnum, fRow, 1L, nelem,
+             (char *)data, undef, &anynul, &status);
+      break;
+   case TLONG:
+      ffgcfj(var->fptr, var->colnum, fRow, 1L, nelem,
+             (long *)data, undef, &anynul, &status);
+      break;
+   case TDOUBLE:
+      ffgcfd(var->fptr, var->colnum, fRow, 1L, nelem,
+             (double *)data, undef, &anynul, &status);
+      break;
+   default:
+      sprintf(msg,"load_column: unexpected datatype %d", var->datatype);
+      ffpmsg(msg);
+   }
+  }
+   if( status ) {
+      gParse.status = status;
+      return pERROR;
+   }
+
+   return 0;
+}
+
+
+/*--------------------------------------------------------------------------*/
+int fits_pixel_filter (PixelFilter * filter, int * status)
+/* Evaluate an expression using the data in the input FITS file(s)          */
+/*--------------------------------------------------------------------------*/
+{
+   parseInfo Info = { 0 };
+   int naxis, bitpix;
+   long nelem, naxes[MAXDIMS];
+   int col_cnt;
+   Node *result;
+   int datatype;
+   fitsfile * infptr;
+   fitsfile * outfptr;
+   char * DEFAULT_TAGS[] = { "X" };
+   char msg[256];
+   int writeBlankKwd = 0;   /* write BLANK if any output nulls? */
+
+   DEBUG_PIXFILTER = getenv("DEBUG_PIXFILTER") ? 1 : 0;
+
+   if (*status)
+      return (*status);
+
+   FFLOCK;
+   if (!filter->tag || !filter->tag[0] || !filter->tag[0][0]) {
+      filter->tag = DEFAULT_TAGS;
+      if (DEBUG_PIXFILTER)
+         printf("using default tag '%s'\n", filter->tag[0]);
+   }
+
+   infptr = filter->ifptr[0];
+   outfptr = filter->ofptr;
+   gParse.pixFilter = filter;
+
+   if (ffiprs(infptr, 0, filter->expression, MAXDIMS,
+            &Info.datatype, &nelem, &naxis, naxes, status)) {
+      goto CLEANUP;
+   }
+
+   if (nelem < 0) {
+      nelem = -nelem;
+   }
+
+   {
+      /* validate result type */
+      const char * type = 0;
+      switch (Info.datatype) {
+         case TLOGICAL:  type = "LOGICAL"; break;
+         case TLONG:     type = "LONG"; break;
+         case TDOUBLE:   type = "DOUBLE"; break;
+         case TSTRING:   type = "STRING";
+                         *status = pERROR;
+                         ffpmsg("pixel_filter: cannot have string image");
+         case TBIT:      type = "BIT";
+                         if (DEBUG_PIXFILTER)
+                            printf("hmm, image from bits?\n");
+                         break;
+         default:       type = "UNKNOWN?!";
+                        *status = pERROR;
+                        ffpmsg("pixel_filter: unexpected result datatype");
+      }
+      if (DEBUG_PIXFILTER)
+         printf("result type is %s [%d]\n", type, Info.datatype);
+      if (*status)
+         goto CLEANUP;
+   }
+
+   if (fits_get_img_param(infptr, MAXDIMS,
+            &bitpix, &naxis, &naxes[0], status)) {
+      ffpmsg("pixel_filter: unable to read input image parameters");
+      goto CLEANUP;
+   }
+
+   if (DEBUG_PIXFILTER)
+      printf("input bitpix %d\n", bitpix);
+
+   if (Info.datatype == TDOUBLE) {
+       /*  for floating point expressions, set the default output image to
+           bitpix = -32 (float) unless the default is already a double */
+       if (bitpix != DOUBLE_IMG)
+           bitpix = FLOAT_IMG;
+   }
+
+   /* override output image bitpix if specified by caller */
+   if (filter->bitpix)
+      bitpix = filter->bitpix;
+   if (DEBUG_PIXFILTER)
+      printf("output bitpix %d\n", bitpix);
+
+   if (fits_create_img(outfptr, bitpix, naxis, naxes, status)) {
+      ffpmsg("pixel_filter: unable to create output image");
+      goto CLEANUP;
+   }
+
+   /* transfer keycards */
+   {
+      int i, ncards, more;
+      if (fits_get_hdrspace(infptr, &ncards, &more, status)) {
+         ffpmsg("pixel_filter: unable to determine number of keycards");
+         goto CLEANUP;
+      }
+
+      for (i = 1; i <= ncards; ++i) {
+
+         int keyclass;
+         char card[FLEN_CARD];
+
+         if (fits_read_record(infptr, i, card, status)) {
+            sprintf(msg, "pixel_filter: unable to read keycard %d", i);
+            ffpmsg(msg);
+            goto CLEANUP;
+         }
+
+         keyclass = fits_get_keyclass(card);
+         if (keyclass == TYP_STRUC_KEY) {
+            /* output structure defined by fits_create_img */
+         }
+         else if (keyclass == TYP_COMM_KEY && i < 12) {
+            /* assume this is one of the FITS standard comments */
+         }
+         else if (keyclass == TYP_NULL_KEY && bitpix < 0) {
+            /* do not transfer BLANK to real output image */
+         }
+         else if (keyclass == TYP_SCAL_KEY && bitpix < 0) {
+            /* do not transfer BZERO, BSCALE to real output image */
+         }
+         else if (fits_write_record(outfptr, card, status)) {
+            sprintf(msg, "pixel_filter: unable to write keycard '%s' [%d]\n",
+                        card, *status);
+            ffpmsg(msg);
+            goto CLEANUP;
+         }
+      }
+   }
+
+   switch (bitpix) {
+      case BYTE_IMG: datatype = TLONG; Info.datatype = TBYTE; break;
+      case SHORT_IMG: datatype = TLONG; Info.datatype = TSHORT; break;
+      case LONG_IMG: datatype = TLONG; Info.datatype = TLONG; break;
+      case FLOAT_IMG: datatype = TDOUBLE; Info.datatype = TFLOAT; break;
+      case DOUBLE_IMG: datatype = TDOUBLE; Info.datatype = TDOUBLE; break;
+
+      default:
+           sprintf(msg, "pixel_filter: unexpected output bitpix %d\n", bitpix);
+           ffpmsg(msg);
+           *status = pERROR;
+           goto CLEANUP;
+   }
+
+   if (bitpix > 0) { /* arrange for NULLs in output */
+      long nullVal = filter->blank;
+      if (!filter->blank) {
+         int tstatus = 0;
+         if (fits_read_key_lng(infptr, "BLANK", &nullVal, 0, &tstatus)) {
+
+            writeBlankKwd = 1;
+
+            if (bitpix == BYTE_IMG)
+                nullVal = UCHAR_MAX;
+            else if (bitpix == SHORT_IMG)
+                nullVal = SHRT_MIN;
+            else if (bitpix == LONG_IMG)
+                nullVal = LONG_MIN;
+            else
+                printf("unhandled positive output BITPIX %d\n", bitpix);
+         }
+
+         filter->blank = nullVal;
+      }
+
+      fits_set_imgnull(outfptr, filter->blank, status);
+      if (DEBUG_PIXFILTER)
+         printf("using blank %ld\n", nullVal);
+
+   }
+
+   if (!filter->keyword[0]) {
+      iteratorCol * colIter;
+      DataInfo * varInfo;
+
+      /*************************************/
+      /* Create new iterator Output Column */
+      /*************************************/
+      col_cnt = gParse.nCols;
+      if (allocateCol(col_cnt, status))
+         goto CLEANUP;
+      gParse.nCols++;
+
+      colIter = &gParse.colData[col_cnt];
+      colIter->fptr = filter->ofptr;
+      colIter->iotype = OutputCol;
+      varInfo = &gParse.varData[col_cnt];
+      set_image_col_types(colIter->fptr, "CREATED", bitpix, varInfo, colIter);
+
+      Info.maxRows = -1;
+
+      if (ffiter(gParse.nCols, gParse.colData, 0,
+                     0, parse_data, &Info, status) == -1)
+            *status = 0;
+      else if (*status)
+         goto CLEANUP;
+
+      if (Info.anyNull) {
+         if (writeBlankKwd) {
+            fits_update_key_lng(outfptr, "BLANK", filter->blank, "NULL pixel value", status);
+            if (*status)
+                ffpmsg("pixel_filter: unable to write BLANK keyword");
+            if (DEBUG_PIXFILTER) {
+                printf("output has NULLs\n");
+                printf("wrote blank [%d]\n", *status);
+            }
+         }
+      }
+      else if (bitpix > 0) /* never used a null */
+         if (fits_set_imgnull(outfptr, -1234554321, status))
+            ffpmsg("pixel_filter: unable to reset imgnull");
+   }
+   else {
+
+      /* Put constant result into keyword */
+      char * parName = filter->keyword;
+      char * parInfo = filter->comment;
+
+      result  = gParse.Nodes + gParse.resultNode;
+      switch (Info.datatype) {
+      case TDOUBLE:
+         ffukyd(outfptr, parName, result->value.data.dbl, 15, parInfo, status);
+         break;
+      case TLONG:
+         ffukyj(outfptr, parName, result->value.data.lng, parInfo, status);
+         break;
+      case TLOGICAL:
+         ffukyl(outfptr, parName, result->value.data.log, parInfo, status);
+         break;
+      case TBIT:
+      case TSTRING:
+         ffukys(outfptr, parName, result->value.data.str, parInfo, status);
+         break;
+      default:
+         sprintf(msg, "pixel_filter: unexpected constant result type [%d]\n",
+                Info.datatype);
+         ffpmsg(msg);
+      }
+   }
+
+CLEANUP:
+   ffcprs();
+   FFUNLOCK;
+   return (*status);
+}
diff --git a/src/plugins/cfitsio/eval_l.c b/src/plugins/cfitsio/eval_l.c
new file mode 100644
index 0000000..7dcb5dc
--- /dev/null
+++ b/src/plugins/cfitsio/eval_l.c
@@ -0,0 +1,2252 @@
+/* A lexical scanner generated by flex */
+
+/* Scanner skeleton version:
+ * $Header: /software/lheasoft/lheavc/hip/cfitsio/eval_l.c,v 3.47 2009/09/04 18:35:05 pence Exp $
+ */
+
+#define FLEX_SCANNER
+#define FF_FLEX_MAJOR_VERSION 2
+#define FF_FLEX_MINOR_VERSION 5
+
+#include <stdio.h>
+
+
+/* cfront 1.2 defines "c_plusplus" instead of "__cplusplus" */
+#ifdef c_plusplus
+#ifndef __cplusplus
+#define __cplusplus
+#endif
+#endif
+
+
+#ifdef __cplusplus
+
+#include <stdlib.h>
+#include <unistd.h>
+
+/* Use prototypes in function declarations. */
+#define FF_USE_PROTOS
+
+/* The "const" storage-class-modifier is valid. */
+#define FF_USE_CONST
+
+#else	/* ! __cplusplus */
+
+#if __STDC__
+
+#define FF_USE_PROTOS
+#define FF_USE_CONST
+
+#endif	/* __STDC__ */
+#endif	/* ! __cplusplus */
+
+#ifdef __TURBOC__
+ #pragma warn -rch
+ #pragma warn -use
+#include <io.h>
+#include <stdlib.h>
+#define FF_USE_CONST
+#define FF_USE_PROTOS
+#endif
+
+#ifdef FF_USE_CONST
+#define ffconst const
+#else
+#define ffconst
+#endif
+
+
+#ifdef FF_USE_PROTOS
+#define FF_PROTO(proto) proto
+#else
+#define FF_PROTO(proto) ()
+#endif
+
+/* Returned upon end-of-file. */
+#define FF_NULL 0
+
+/* Promotes a possibly negative, possibly signed char to an unsigned
+ * integer for use as an array index.  If the signed char is negative,
+ * we want to instead treat it as an 8-bit unsigned char, hence the
+ * double cast.
+ */
+#define FF_SC_TO_UI(c) ((unsigned int) (unsigned char) c)
+
+/* Enter a start condition.  This macro really ought to take a parameter,
+ * but we do it the disgusting crufty way forced on us by the ()-less
+ * definition of BEGIN.
+ */
+#define BEGIN ff_start = 1 + 2 *
+
+/* Translate the current start state into a value that can be later handed
+ * to BEGIN to return to the state.  The FFSTATE alias is for lex
+ * compatibility.
+ */
+#define FF_START ((ff_start - 1) / 2)
+#define FFSTATE FF_START
+
+/* Action number for EOF rule of a given start state. */
+#define FF_STATE_EOF(state) (FF_END_OF_BUFFER + state + 1)
+
+/* Special action meaning "start processing a new file". */
+#define FF_NEW_FILE ffrestart( ffin )
+
+#define FF_END_OF_BUFFER_CHAR 0
+
+/* Size of default input buffer. */
+#define FF_BUF_SIZE 16384
+
+typedef struct ff_buffer_state *FF_BUFFER_STATE;
+
+extern int ffleng;
+extern FILE *ffin, *ffout;
+
+#define EOB_ACT_CONTINUE_SCAN 0
+#define EOB_ACT_END_OF_FILE 1
+#define EOB_ACT_LAST_MATCH 2
+
+/* The funky do-while in the following #define is used to turn the definition
+ * int a single C statement (which needs a semi-colon terminator).  This
+ * avoids problems with code like:
+ *
+ * 	if ( condition_holds )
+ *		ffless( 5 );
+ *	else
+ *		do_something_else();
+ *
+ * Prior to using the do-while the compiler would get upset at the
+ * "else" because it interpreted the "if" statement as being all
+ * done when it reached the ';' after the ffless() call.
+ */
+
+/* Return all but the first 'n' matched characters back to the input stream. */
+
+#define ffless(n) \
+	do \
+		{ \
+		/* Undo effects of setting up fftext. */ \
+		*ff_cp = ff_hold_char; \
+		FF_RESTORE_FF_MORE_OFFSET \
+		ff_c_buf_p = ff_cp = ff_bp + n - FF_MORE_ADJ; \
+		FF_DO_BEFORE_ACTION; /* set up fftext again */ \
+		} \
+	while ( 0 )
+
+#define unput(c) ffunput( c, fftext_ptr )
+
+/* The following is because we cannot portably get our hands on size_t
+ * (without autoconf's help, which isn't available because we want
+ * flex-generated scanners to compile on their own).
+ */
+typedef unsigned int ff_size_t;
+
+
+struct ff_buffer_state
+	{
+	FILE *ff_input_file;
+
+	char *ff_ch_buf;		/* input buffer */
+	char *ff_buf_pos;		/* current position in input buffer */
+
+	/* Size of input buffer in bytes, not including room for EOB
+	 * characters.
+	 */
+	ff_size_t ff_buf_size;
+
+	/* Number of characters read into ff_ch_buf, not including EOB
+	 * characters.
+	 */
+	int ff_n_chars;
+
+	/* Whether we "own" the buffer - i.e., we know we created it,
+	 * and can realloc() it to grow it, and should free() it to
+	 * delete it.
+	 */
+	int ff_is_our_buffer;
+
+	/* Whether this is an "interactive" input source; if so, and
+	 * if we're using stdio for input, then we want to use getc()
+	 * instead of fread(), to make sure we stop fetching input after
+	 * each newline.
+	 */
+	int ff_is_interactive;
+
+	/* Whether we're considered to be at the beginning of a line.
+	 * If so, '^' rules will be active on the next match, otherwise
+	 * not.
+	 */
+	int ff_at_bol;
+
+	/* Whether to try to fill the input buffer when we reach the
+	 * end of it.
+	 */
+	int ff_fill_buffer;
+
+	int ff_buffer_status;
+#define FF_BUFFER_NEW 0
+#define FF_BUFFER_NORMAL 1
+	/* When an EOF's been seen but there's still some text to process
+	 * then we mark the buffer as FF_EOF_PENDING, to indicate that we
+	 * shouldn't try reading from the input source any more.  We might
+	 * still have a bunch of tokens to match, though, because of
+	 * possible backing-up.
+	 *
+	 * When we actually see the EOF, we change the status to "new"
+	 * (via ffrestart()), so that the user can continue scanning by
+	 * just pointing ffin at a new input file.
+	 */
+#define FF_BUFFER_EOF_PENDING 2
+	};
+
+static FF_BUFFER_STATE ff_current_buffer = 0;
+
+/* We provide macros for accessing buffer states in case in the
+ * future we want to put the buffer states in a more general
+ * "scanner state".
+ */
+#define FF_CURRENT_BUFFER ff_current_buffer
+
+
+/* ff_hold_char holds the character lost when fftext is formed. */
+static char ff_hold_char;
+
+static int ff_n_chars;		/* number of characters read into ff_ch_buf */
+
+
+int ffleng;
+
+/* Points to current character in buffer. */
+static char *ff_c_buf_p = (char *) 0;
+static int ff_init = 1;		/* whether we need to initialize */
+static int ff_start = 0;	/* start state number */
+
+/* Flag which is used to allow ffwrap()'s to do buffer switches
+ * instead of setting up a fresh ffin.  A bit of a hack ...
+ */
+static int ff_did_buffer_switch_on_eof;
+
+void ffrestart FF_PROTO(( FILE *input_file ));
+
+void ff_switch_to_buffer FF_PROTO(( FF_BUFFER_STATE new_buffer ));
+void ff_load_buffer_state FF_PROTO(( void ));
+FF_BUFFER_STATE ff_create_buffer FF_PROTO(( FILE *file, int size ));
+void ff_delete_buffer FF_PROTO(( FF_BUFFER_STATE b ));
+void ff_init_buffer FF_PROTO(( FF_BUFFER_STATE b, FILE *file ));
+void ff_flush_buffer FF_PROTO(( FF_BUFFER_STATE b ));
+#define FF_FLUSH_BUFFER ff_flush_buffer( ff_current_buffer )
+
+FF_BUFFER_STATE ff_scan_buffer FF_PROTO(( char *base, ff_size_t size ));
+FF_BUFFER_STATE ff_scan_string FF_PROTO(( ffconst char *ff_str ));
+FF_BUFFER_STATE ff_scan_bytes FF_PROTO(( ffconst char *bytes, int len ));
+
+static void *ff_flex_alloc FF_PROTO(( ff_size_t ));
+static void *ff_flex_realloc FF_PROTO(( void *, ff_size_t ));
+static void ff_flex_free FF_PROTO(( void * ));
+
+#define ff_new_buffer ff_create_buffer
+
+#define ff_set_interactive(is_interactive) \
+	{ \
+	if ( ! ff_current_buffer ) \
+		ff_current_buffer = ff_create_buffer( ffin, FF_BUF_SIZE ); \
+	ff_current_buffer->ff_is_interactive = is_interactive; \
+	}
+
+#define ff_set_bol(at_bol) \
+	{ \
+	if ( ! ff_current_buffer ) \
+		ff_current_buffer = ff_create_buffer( ffin, FF_BUF_SIZE ); \
+	ff_current_buffer->ff_at_bol = at_bol; \
+	}
+
+#define FF_AT_BOL() (ff_current_buffer->ff_at_bol)
+
+typedef unsigned char FF_CHAR;
+FILE *ffin = (FILE *) 0, *ffout = (FILE *) 0;
+typedef int ff_state_type;
+extern char *fftext;
+#define fftext_ptr fftext
+
+static ff_state_type ff_get_previous_state FF_PROTO(( void ));
+static ff_state_type ff_try_NUL_trans FF_PROTO(( ff_state_type current_state ));
+static int ff_get_next_buffer FF_PROTO(( void ));
+static void ff_fatal_error FF_PROTO(( ffconst char msg[] ));
+
+/* Done after the current pattern has been matched and before the
+ * corresponding action - sets up fftext.
+ */
+#define FF_DO_BEFORE_ACTION \
+	fftext_ptr = ff_bp; \
+	ffleng = (int) (ff_cp - ff_bp); \
+	ff_hold_char = *ff_cp; \
+	*ff_cp = '\0'; \
+	ff_c_buf_p = ff_cp;
+
+#define FF_NUM_RULES 26
+#define FF_END_OF_BUFFER 27
+static ffconst short int ff_accept[160] =
+    {   0,
+        0,    0,   27,   25,    1,   24,   15,   25,   25,   25,
+       25,   25,   25,   25,    7,    5,   21,   25,   20,   10,
+       10,   10,   10,    6,   10,   10,   10,   10,   10,   14,
+       10,   10,   10,   10,   10,   10,   10,   25,    1,   19,
+        0,    9,    0,    8,    0,   10,   17,    0,    0,    0,
+        0,    0,    0,    0,   14,    0,    7,    0,    0,    0,
+        0,    0,    0,    0,    0,    0,    0,    0,    0,    7,
+        5,    0,   23,   18,   22,   10,   10,   10,    2,   10,
+       10,   10,    4,   10,   10,   10,   10,    3,   10,   10,
+       10,   10,   10,   10,   10,   10,   10,   10,   16,    0,
+
+        8,    0,    0,    0,    0,    0,    0,    0,    0,    0,
+        0,    0,    0,    0,    0,    0,    0,    0,    0,    0,
+        0,    0,    0,    0,    0,    0,    0,    7,   11,   10,
+       20,   21,   10,   10,   10,    0,    0,    0,    0,    0,
+        0,    0,    0,    0,    0,   15,    0,    0,   12,    0,
+        0,    0,    0,    0,    0,    0,   13,    0,    0
+    } ;
+
+static ffconst int ff_ec[256] =
+    {   0,
+        1,    1,    1,    1,    1,    1,    1,    1,    2,    3,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    2,    4,    5,    6,    7,    1,    8,    9,   10,
+       11,   12,   13,    1,   13,   14,    1,   15,   15,   16,
+       16,   16,   16,   16,   16,   17,   17,    1,    1,   18,
+       19,   20,    1,    1,   21,   22,   23,   24,   25,   26,
+       27,   28,   29,   30,   30,   31,   30,   32,   33,   30,
+       34,   35,   30,   36,   37,   30,   30,   38,   30,   30,
+        1,    1,    1,   39,   40,    1,   41,   42,   23,   43,
+
+       44,   45,   46,   28,   47,   30,   30,   48,   30,   49,
+       50,   30,   51,   52,   30,   53,   54,   30,   30,   38,
+       30,   30,    1,   55,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1
+    } ;
+
+static ffconst int ff_meta[56] =
+    {   0,
+        1,    1,    2,    1,    1,    1,    3,    1,    1,    1,
+        1,    1,    1,    1,    4,    4,    4,    1,    1,    1,
+        4,    4,    4,    4,    4,    4,    4,    4,    4,    4,
+        4,    4,    4,    4,    4,    4,    4,    4,    1,    4,
+        4,    4,    4,    4,    4,    4,    4,    4,    4,    4,
+        4,    4,    4,    4,    1
+    } ;
+
+static ffconst short int ff_base[167] =
+    {   0,
+        0,    0,  367,  368,  364,  368,  346,  359,  356,  355,
+      353,  351,   32,  347,   66,  103,  339,   44,  338,   25,
+       52,  316,   26,  315,   34,  133,   48,   61,  125,  368,
+        0,   29,   45,   60,   81,   82,   93,  299,  351,  368,
+      347,  368,  344,  343,  342,  368,  368,  339,  314,  315,
+      313,  294,  295,  293,  368,  121,  164,  307,  301,   70,
+      117,   43,  296,  276,  271,   58,   86,   79,  269,  152,
+      168,  181,  368,  368,  368,  151,  162,    0,  180,  189,
+      190,  191,  309,  196,  199,  205,  204,  211,  214,  207,
+      223,  224,  232,  238,  243,  245,  222,  246,  368,  311,
+
+      310,  279,  282,  278,  259,  262,  258,  252,  286,  295,
+      294,  293,  292,  291,  290,  267,  288,  258,  285,  284,
+      278,  270,  268,  259,  218,  252,  264,  272,  368,  251,
+      368,  368,  260,  280,  283,  236,  222,  230,  193,  184,
+      212,  208,  202,  173,  156,  368,  133,  126,  368,  104,
+       98,  119,  132,   80,   94,   92,  368,   78,  368,  323,
+      325,  329,  333,   68,   67,  337
+    } ;
+
+static ffconst short int ff_def[167] =
+    {   0,
+      159,    1,  159,  159,  159,  159,  159,  160,  161,  162,
+      159,  163,  159,  159,  159,  159,  159,  159,  159,  164,
+      164,  164,  164,  164,  164,  164,  164,  164,  164,  159,
+      165,  164,  164,  164,  164,  164,  164,  159,  159,  159,
+      160,  159,  166,  161,  162,  159,  159,  163,  159,  159,
+      159,  159,  159,  159,  159,  159,  159,  159,  159,  159,
+      159,  159,  159,  159,  159,  159,  159,  159,  159,  159,
+      159,  159,  159,  159,  159,  164,  164,  165,  164,  164,
+      164,  164,   26,  164,  164,  164,  164,  164,  164,  164,
+      164,  164,  164,  164,  164,  164,  164,  164,  159,  166,
+
+      166,  159,  159,  159,  159,  159,  159,  159,  159,  159,
+      159,  159,  159,  159,  159,  159,  159,  159,  159,  159,
+      159,  159,  159,  159,  159,  159,  159,  159,  159,  164,
+      159,  159,  164,  164,  164,  159,  159,  159,  159,  159,
+      159,  159,  159,  159,  159,  159,  159,  159,  159,  159,
+      159,  159,  159,  159,  159,  159,  159,  159,    0,  159,
+      159,  159,  159,  159,  159,  159
+    } ;
+
+static ffconst short int ff_nxt[424] =
+    {   0,
+        4,    5,    6,    7,    8,    9,   10,   11,   12,   13,
+        4,   14,    4,   15,   16,   16,   16,   17,   18,   19,
+       20,   21,   22,   22,   23,   24,   25,   26,   22,   22,
+       27,   28,   29,   22,   22,   24,   22,   22,   30,   31,
+       32,   21,   22,   33,   24,   34,   22,   35,   36,   37,
+       22,   22,   24,   22,   38,   49,   77,   50,   81,   80,
+       51,   73,   74,   75,   78,   78,   79,  115,   78,   82,
+       78,   76,   84,   78,   52,  116,   53,   90,   54,   56,
+       57,   57,   57,   85,   78,   86,   58,   78,  157,   79,
+       59,   78,   60,   87,  111,   91,   61,   62,   63,   78,
+
+       78,  120,  157,   92,  157,  112,   64,   88,   88,   65,
+      121,   66,   93,   67,   68,   69,   70,   71,   71,   71,
+       78,   78,  124,  158,   94,   96,   72,   72,  125,  122,
+       88,   97,   78,   95,   56,  108,  108,  108,  123,   88,
+       88,  113,  157,  156,   98,   72,   72,   83,   83,   83,
+      155,  154,  114,   83,   83,   83,   83,   83,   83,   89,
+      129,  153,   88,  152,   78,   56,   57,   57,   57,  146,
+       83,  129,   78,   83,   83,   83,   83,   83,   57,   57,
+       57,   70,   71,   71,   71,  130,   47,   72,   72,  129,
+       78,   72,   72,  127,   79,  128,  128,  128,  129,  129,
+
+      129,   78,   74,   75,  131,  129,   72,   72,  129,   73,
+       72,   72,  132,  129,  129,  146,  129,   79,   40,   78,
+      129,   47,  149,  129,  151,   88,   88,   99,   78,   78,
+       78,  129,  129,  129,  150,   78,   74,   75,   78,  133,
+      149,  129,  148,   78,   78,  131,   78,  129,   88,  134,
+       78,   73,  129,   78,  129,  129,  132,  147,   40,   99,
+      129,   78,   78,   78,   47,   99,  108,  108,  108,  129,
+      145,   78,   40,  146,  135,   72,   72,   78,  128,  128,
+      128,  132,   78,   73,   78,   78,  128,  128,  128,  129,
+       78,  131,  129,   47,   72,   72,  146,   75,   74,   78,
+
+      144,   99,  143,   40,  132,   73,  131,   75,   74,  142,
+      141,  140,  139,  138,  137,  136,  101,  101,  129,   78,
+      126,  119,   78,   41,  118,   41,   41,   44,   44,   45,
+      117,   45,   45,   48,  110,   48,   48,  100,  109,  100,
+      100,  107,  106,  105,  104,  103,  102,   42,   46,  159,
+      101,   42,   39,   99,   78,   78,   75,   73,   55,   42,
+       47,   46,   43,   42,   40,   39,  159,    3,  159,  159,
+      159,  159,  159,  159,  159,  159,  159,  159,  159,  159,
+      159,  159,  159,  159,  159,  159,  159,  159,  159,  159,
+      159,  159,  159,  159,  159,  159,  159,  159,  159,  159,
+
+      159,  159,  159,  159,  159,  159,  159,  159,  159,  159,
+      159,  159,  159,  159,  159,  159,  159,  159,  159,  159,
+      159,  159,  159
+    } ;
+
+static ffconst short int ff_chk[424] =
+    {   0,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
+        1,    1,    1,    1,    1,   13,   20,   13,   25,   23,
+       13,   18,   18,   18,   20,   23,   21,   62,   32,   25,
+      165,  164,   27,   25,   13,   62,   13,   32,   13,   15,
+       15,   15,   15,   27,   33,   28,   15,   27,  158,   21,
+       15,   21,   15,   28,   60,   33,   15,   15,   15,   34,
+
+       28,   66,  156,   34,  155,   60,   15,   37,   37,   15,
+       66,   15,   34,   15,   15,   15,   16,   16,   16,   16,
+       35,   36,   68,  154,   35,   36,   16,   16,   68,   67,
+       37,   36,   37,   35,   56,   56,   56,   56,   67,   29,
+       29,   61,  153,  152,   37,   16,   16,   26,   26,   26,
+      151,  150,   61,   26,   26,   26,   26,   26,   26,   29,
+       76,  148,   29,  147,   29,   70,   70,   70,   70,  145,
+       26,   77,   26,   26,   26,   26,   26,   26,   57,   57,
+       57,   71,   71,   71,   71,   77,  144,   57,   57,   79,
+       76,   71,   71,   72,   79,   72,   72,   72,   80,   81,
+
+       82,   77,   80,   81,   82,   84,   57,   57,   85,   84,
+       71,   71,   85,   87,   86,  143,   90,   79,   86,   79,
+       88,  142,  141,   89,  140,   88,   88,   89,   80,   81,
+       82,   97,   91,   92,  139,   84,   91,   92,   85,   87,
+      138,   93,  137,   87,   86,   93,   90,   94,   88,   90,
+       88,   94,   95,   89,   96,   98,   95,  136,   96,   98,
+      130,   97,   91,   92,  130,  126,  108,  108,  108,  133,
+      125,   93,  124,  133,   97,  108,  108,   94,  127,  127,
+      127,  123,   95,  122,   96,   98,  128,  128,  128,  134,
+      130,  121,  135,  134,  108,  108,  135,  120,  119,  133,
+
+      118,  117,  116,  115,  114,  113,  112,  111,  110,  109,
+      107,  106,  105,  104,  103,  102,  101,  100,   83,  134,
+       69,   65,  135,  160,   64,  160,  160,  161,  161,  162,
+       63,  162,  162,  163,   59,  163,  163,  166,   58,  166,
+      166,   54,   53,   52,   51,   50,   49,   48,   45,   44,
+       43,   41,   39,   38,   24,   22,   19,   17,   14,   12,
+       11,   10,    9,    8,    7,    5,    3,  159,  159,  159,
+      159,  159,  159,  159,  159,  159,  159,  159,  159,  159,
+      159,  159,  159,  159,  159,  159,  159,  159,  159,  159,
+      159,  159,  159,  159,  159,  159,  159,  159,  159,  159,
+
+      159,  159,  159,  159,  159,  159,  159,  159,  159,  159,
+      159,  159,  159,  159,  159,  159,  159,  159,  159,  159,
+      159,  159,  159
+    } ;
+
+static ff_state_type ff_last_accepting_state;
+static char *ff_last_accepting_cpos;
+
+/* The intent behind this definition is that it'll catch
+ * any uses of REJECT which flex missed.
+ */
+#define REJECT reject_used_but_not_detected
+#define ffmore() ffmore_used_but_not_detected
+#define FF_MORE_ADJ 0
+#define FF_RESTORE_FF_MORE_OFFSET
+char *fftext;
+#line 1 "eval.l"
+#define INITIAL 0
+#line 2 "eval.l"
+/************************************************************************/
+/*                                                                      */
+/*                       CFITSIO Lexical Parser                         */
+/*                                                                      */
+/* This file is one of 3 files containing code which parses an          */
+/* arithmetic expression and evaluates it in the context of an input    */
+/* FITS file table extension.  The CFITSIO lexical parser is divided    */
+/* into the following 3 parts/files: the CFITSIO "front-end",           */
+/* eval_f.c, contains the interface between the user/CFITSIO and the    */
+/* real core of the parser; the FLEX interpreter, eval_l.c, takes the   */
+/* input string and parses it into tokens and identifies the FITS       */
+/* information required to evaluate the expression (ie, keywords and    */
+/* columns); and, the BISON grammar and evaluation routines, eval_y.c,  */
+/* receives the FLEX output and determines and performs the actual      */
+/* operations.  The files eval_l.c and eval_y.c are produced from       */
+/* running flex and bison on the files eval.l and eval.y, respectively. */
+/* (flex and bison are available from any GNU archive: see www.gnu.org) */
+/*                                                                      */
+/* The grammar rules, rather than evaluating the expression in situ,    */
+/* builds a tree, or Nodal, structure mapping out the order of          */
+/* operations and expression dependencies.  This "compilation" process  */
+/* allows for much faster processing of multiple rows.  This technique  */
+/* was developed by Uwe Lammers of the XMM Science Analysis System,     */
+/* although the CFITSIO implementation is entirely code original.       */
+/*                                                                      */
+/*                                                                      */
+/* Modification History:                                                */
+/*                                                                      */
+/*   Kent Blackburn      c1992  Original parser code developed for the  */
+/*                              FTOOLS software package, in particular, */
+/*                              the fselect task.                       */
+/*   Kent Blackburn      c1995  BIT column support added                */
+/*   Peter D Wilson   Feb 1998  Vector column support added             */
+/*   Peter D Wilson   May 1998  Ported to CFITSIO library.  User        */
+/*                              interface routines written, in essence  */
+/*                              making fselect, fcalc, and maketime     */
+/*                              capabilities available to all tools     */
+/*                              via single function calls.              */
+/*   Peter D Wilson   Jun 1998  Major rewrite of parser core, so as to  */
+/*                              create a run-time evaluation tree,      */
+/*                              inspired by the work of Uwe Lammers,    */
+/*                              resulting in a speed increase of        */
+/*                              10-100 times.                           */
+/*   Peter D Wilson   Jul 1998  gtifilter(a,b,c,d) function added       */
+/*   Peter D Wilson   Aug 1998  regfilter(a,b,c,d) function added       */
+/*   Peter D Wilson   Jul 1999  Make parser fitsfile-independent,       */
+/*                              allowing a purely vector-based usage    */
+/*                                                                      */
+/************************************************************************/
+
+#include <math.h>
+#include <string.h>
+#include <ctype.h>
+#ifdef sparc
+#include <malloc.h>
+#else
+#include <stdlib.h>
+#endif
+#include "eval_defs.h"
+
+ParseData gParse;     /* Global structure holding all parser information     */
+
+/*****  Internal functions  *****/
+
+       int ffGetVariable( char *varName, FFSTYPE *varVal );
+
+static int find_variable( char *varName );
+static int expr_read( char *buf, int nbytes );
+
+/*****  Definitions  *****/
+
+#define FF_NO_UNPUT   /*  Don't include FFUNPUT function  */
+#define FF_NEVER_INTERACTIVE 1
+
+#define MAXCHR 256
+#define MAXBIT 128
+
+#define OCT_0 "000"
+#define OCT_1 "001"
+#define OCT_2 "010"
+#define OCT_3 "011"
+#define OCT_4 "100"
+#define OCT_5 "101"
+#define OCT_6 "110"
+#define OCT_7 "111"
+#define OCT_X "xxx"
+
+#define HEX_0 "0000"
+#define HEX_1 "0001"
+#define HEX_2 "0010"
+#define HEX_3 "0011"
+#define HEX_4 "0100"
+#define HEX_5 "0101"
+#define HEX_6 "0110"
+#define HEX_7 "0111"
+#define HEX_8 "1000"
+#define HEX_9 "1001"
+#define HEX_A "1010"
+#define HEX_B "1011"
+#define HEX_C "1100"
+#define HEX_D "1101"
+#define HEX_E "1110"
+#define HEX_F "1111"
+#define HEX_X "xxxx"
+
+/* 
+   MJT - 13 June 1996
+   read from buffer instead of stdin
+   (as per old ftools.skel)
+*/
+#undef FF_INPUT
+#define FF_INPUT(buf,result,max_size) \
+        if ( (result = expr_read( (char *) buf, max_size )) < 0 ) \
+            FF_FATAL_ERROR( "read() in flex scanner failed" );
+
+
+/* Macros after this point can all be overridden by user definitions in
+ * section 1.
+ */
+
+#ifndef FF_SKIP_FFWRAP
+#ifdef __cplusplus
+extern "C" int ffwrap FF_PROTO(( void ));
+#else
+extern int ffwrap FF_PROTO(( void ));
+#endif
+#endif
+
+#ifndef FF_NO_UNPUT
+static void ffunput FF_PROTO(( int c, char *buf_ptr ));
+#endif
+
+#ifndef fftext_ptr
+static void ff_flex_strncpy FF_PROTO(( char *, ffconst char *, int ));
+#endif
+
+#ifdef FF_NEED_STRLEN
+static int ff_flex_strlen FF_PROTO(( ffconst char * ));
+#endif
+
+#ifndef FF_NO_INPUT
+#ifdef __cplusplus
+static int ffinput FF_PROTO(( void ));
+#else
+static int input FF_PROTO(( void ));
+#endif
+#endif
+
+#if FF_STACK_USED
+static int ff_start_stack_ptr = 0;
+static int ff_start_stack_depth = 0;
+static int *ff_start_stack = 0;
+#ifndef FF_NO_PUSH_STATE
+static void ff_push_state FF_PROTO(( int new_state ));
+#endif
+#ifndef FF_NO_POP_STATE
+static void ff_pop_state FF_PROTO(( void ));
+#endif
+#ifndef FF_NO_TOP_STATE
+static int ff_top_state FF_PROTO(( void ));
+#endif
+
+#else
+#define FF_NO_PUSH_STATE 1
+#define FF_NO_POP_STATE 1
+#define FF_NO_TOP_STATE 1
+#endif
+
+#ifdef FF_MALLOC_DECL
+FF_MALLOC_DECL
+#else
+#if __STDC__
+#ifndef __cplusplus
+#include <stdlib.h>
+#endif
+#else
+/* Just try to get by without declaring the routines.  This will fail
+ * miserably on non-ANSI systems for which sizeof(size_t) != sizeof(int)
+ * or sizeof(void*) != sizeof(int).
+ */
+#endif
+#endif
+
+/* Amount of stuff to slurp up with each read. */
+#ifndef FF_READ_BUF_SIZE
+#define FF_READ_BUF_SIZE 8192
+#endif
+
+/* Copy whatever the last rule matched to the standard output. */
+
+#ifndef ECHO
+/* This used to be an fputs(), but since the string might contain NUL's,
+ * we now use fwrite().
+ */
+#define ECHO (void) fwrite( fftext, ffleng, 1, ffout )
+#endif
+
+/* Gets input and stuffs it into "buf".  number of characters read, or FF_NULL,
+ * is returned in "result".
+ */
+#ifndef FF_INPUT
+#define FF_INPUT(buf,result,max_size) \
+	if ( ff_current_buffer->ff_is_interactive ) \
+		{ \
+		int c = '*', n; \
+		for ( n = 0; n < max_size && \
+			     (c = getc( ffin )) != EOF && c != '\n'; ++n ) \
+			buf[n] = (char) c; \
+		if ( c == '\n' ) \
+			buf[n++] = (char) c; \
+		if ( c == EOF && ferror( ffin ) ) \
+			FF_FATAL_ERROR( "input in flex scanner failed" ); \
+		result = n; \
+		} \
+	else if ( ((result = fread( buf, 1, max_size, ffin )) == 0) \
+		  && ferror( ffin ) ) \
+		FF_FATAL_ERROR( "input in flex scanner failed" );
+#endif
+
+/* No semi-colon after return; correct usage is to write "ffterminate();" -
+ * we don't want an extra ';' after the "return" because that will cause
+ * some compilers to complain about unreachable statements.
+ */
+#ifndef ffterminate
+#define ffterminate() return FF_NULL
+#endif
+
+/* Number of entries by which start-condition stack grows. */
+#ifndef FF_START_STACK_INCR
+#define FF_START_STACK_INCR 25
+#endif
+
+/* Report a fatal error. */
+#ifndef FF_FATAL_ERROR
+#define FF_FATAL_ERROR(msg) ff_fatal_error( msg )
+#endif
+
+/* Default declaration of generated scanner - a define so the user can
+ * easily add parameters.
+ */
+#ifndef FF_DECL
+#define FF_DECL int fflex FF_PROTO(( void ))
+#endif
+
+/* Code executed at the beginning of each rule, after fftext and ffleng
+ * have been set up.
+ */
+#ifndef FF_USER_ACTION
+#define FF_USER_ACTION
+#endif
+
+/* Code executed at the end of each rule. */
+#ifndef FF_BREAK
+#define FF_BREAK break;
+#endif
+
+#define FF_RULE_SETUP \
+	FF_USER_ACTION
+
+FF_DECL
+	{
+	register ff_state_type ff_current_state;
+	register char *ff_cp, *ff_bp;
+	register int ff_act;
+
+#line 142 "eval.l"
+
+
+
+	if ( ff_init )
+		{
+		ff_init = 0;
+
+#ifdef FF_USER_INIT
+		FF_USER_INIT;
+#endif
+
+		if ( ! ff_start )
+			ff_start = 1;	/* first start state */
+
+		if ( ! ffin )
+			ffin = stdin;
+
+		if ( ! ffout )
+			ffout = stdout;
+
+		if ( ! ff_current_buffer )
+			ff_current_buffer =
+				ff_create_buffer( ffin, FF_BUF_SIZE );
+
+		ff_load_buffer_state();
+		}
+
+	while ( 1 )		/* loops until end-of-file is reached */
+		{
+		ff_cp = ff_c_buf_p;
+
+		/* Support of fftext. */
+		*ff_cp = ff_hold_char;
+
+		/* ff_bp points to the position in ff_ch_buf of the start of
+		 * the current run.
+		 */
+		ff_bp = ff_cp;
+
+		ff_current_state = ff_start;
+ff_match:
+		do
+			{
+			register FF_CHAR ff_c = ff_ec[FF_SC_TO_UI(*ff_cp)];
+			if ( ff_accept[ff_current_state] )
+				{
+				ff_last_accepting_state = ff_current_state;
+				ff_last_accepting_cpos = ff_cp;
+				}
+			while ( ff_chk[ff_base[ff_current_state] + ff_c] != ff_current_state )
+				{
+				ff_current_state = (int) ff_def[ff_current_state];
+				if ( ff_current_state >= 160 )
+					ff_c = ff_meta[(unsigned int) ff_c];
+				}
+			ff_current_state = ff_nxt[ff_base[ff_current_state] + (unsigned int) ff_c];
+			++ff_cp;
+			}
+		while ( ff_base[ff_current_state] != 368 );
+
+ff_find_action:
+		ff_act = ff_accept[ff_current_state];
+		if ( ff_act == 0 )
+			{ /* have to back up */
+			ff_cp = ff_last_accepting_cpos;
+			ff_current_state = ff_last_accepting_state;
+			ff_act = ff_accept[ff_current_state];
+			}
+
+		FF_DO_BEFORE_ACTION;
+
+
+do_action:	/* This label is used only to access EOF actions. */
+
+
+		switch ( ff_act )
+	{ /* beginning of action switch */
+			case 0: /* must back up */
+			/* undo the effects of FF_DO_BEFORE_ACTION */
+			*ff_cp = ff_hold_char;
+			ff_cp = ff_last_accepting_cpos;
+			ff_current_state = ff_last_accepting_state;
+			goto ff_find_action;
+
+case 1:
+FF_RULE_SETUP
+#line 144 "eval.l"
+;
+	FF_BREAK
+case 2:
+FF_RULE_SETUP
+#line 145 "eval.l"
+{
+                  int len;
+                  len = strlen(fftext);
+		  while (fftext[len] == ' ')
+			len--;
+                  len = len - 1;
+		  strncpy(fflval.str,&fftext[1],len);
+		  fflval.str[len] = '\0';
+		  return( BITSTR );
+		}
+	FF_BREAK
+case 3:
+FF_RULE_SETUP
+#line 155 "eval.l"
+{
+                  int len;
+                  char tmpstring[256];
+                  char bitstring[256];
+                  len = strlen(fftext);
+		  if (len >= 256) {
+		    char errMsg[100];
+		    gParse.status = PARSE_SYNTAX_ERR;
+		    strcpy (errMsg,"Bit string exceeds maximum length: '");
+		    strncat(errMsg, &(fftext[0]), 20);
+		    strcat (errMsg,"...'");
+		    ffpmsg (errMsg);
+		    len = 0;
+		  } else {
+		    while (fftext[len] == ' ')
+		      len--;
+		    len = len - 1;
+		    strncpy(tmpstring,&fftext[1],len);
+		  }
+                  tmpstring[len] = '\0';
+                  bitstring[0] = '\0';
+		  len = 0;
+                  while ( tmpstring[len] != '\0')
+                       {
+			switch ( tmpstring[len] )
+			      {
+			       case '0':
+					strcat(bitstring,OCT_0);
+					break;
+			       case '1':
+					strcat(bitstring,OCT_1);
+					break;
+			       case '2':
+					strcat(bitstring,OCT_2);
+					break;
+			       case '3':
+					strcat(bitstring,OCT_3);
+					break;
+			       case '4':
+					strcat(bitstring,OCT_4);
+					break;
+			       case '5':
+					strcat(bitstring,OCT_5);
+					break;
+			       case '6':
+					strcat(bitstring,OCT_6);
+					break;
+			       case '7':
+					strcat(bitstring,OCT_7);
+					break;
+			       case 'x':
+			       case 'X':
+					strcat(bitstring,OCT_X);
+					break;
+			      }
+			len++;
+                       }
+                  strcpy( fflval.str, bitstring );
+		  return( BITSTR );
+		}
+	FF_BREAK
+case 4:
+FF_RULE_SETUP
+#line 215 "eval.l"
+{
+                  int len;
+                  char tmpstring[256];
+                  char bitstring[256];
+                  len = strlen(fftext);
+		  if (len >= 256) {
+		    char errMsg[100];
+		    gParse.status = PARSE_SYNTAX_ERR;
+		    strcpy (errMsg,"Hex string exceeds maximum length: '");
+		    strncat(errMsg, &(fftext[0]), 20);
+		    strcat (errMsg,"...'");
+		    ffpmsg (errMsg);
+		    len = 0;
+		  } else {
+		    while (fftext[len] == ' ')
+		      len--;
+		    len = len - 1;
+		    strncpy(tmpstring,&fftext[1],len);
+		  }
+                  tmpstring[len] = '\0';
+                  bitstring[0] = '\0';
+		  len = 0;
+                  while ( tmpstring[len] != '\0')
+                       {
+			switch ( tmpstring[len] )
+			      {
+			       case '0':
+					strcat(bitstring,HEX_0);
+					break;
+			       case '1':
+					strcat(bitstring,HEX_1);
+					break;
+			       case '2':
+					strcat(bitstring,HEX_2);
+					break;
+			       case '3':
+					strcat(bitstring,HEX_3);
+					break;
+			       case '4':
+					strcat(bitstring,HEX_4);
+					break;
+			       case '5':
+					strcat(bitstring,HEX_5);
+					break;
+			       case '6':
+					strcat(bitstring,HEX_6);
+					break;
+			       case '7':
+					strcat(bitstring,HEX_7);
+					break;
+			       case '8':
+					strcat(bitstring,HEX_8);
+					break;
+			       case '9':
+					strcat(bitstring,HEX_9);
+					break;
+			       case 'a':
+			       case 'A':
+					strcat(bitstring,HEX_A);
+					break;
+			       case 'b':
+			       case 'B':
+					strcat(bitstring,HEX_B);
+					break;
+			       case 'c':
+			       case 'C':
+					strcat(bitstring,HEX_C);
+					break;
+			       case 'd':
+			       case 'D':
+					strcat(bitstring,HEX_D);
+					break;
+			       case 'e':
+			       case 'E':
+					strcat(bitstring,HEX_E);
+					break;
+			       case 'f':
+			       case 'F':
+					strcat(bitstring,HEX_F);
+					break;
+			       case 'x':
+			       case 'X':
+					strcat(bitstring,HEX_X);
+					break;
+			      }
+			len++;
+                       }
+
+                  strcpy( fflval.str, bitstring );
+		  return( BITSTR );
+		}
+	FF_BREAK
+case 5:
+FF_RULE_SETUP
+#line 306 "eval.l"
+{
+                  fflval.lng = atol(fftext);
+		  return( LONG );
+		}
+	FF_BREAK
+case 6:
+FF_RULE_SETUP
+#line 310 "eval.l"
+{
+                  if ((fftext[0] == 't') || (fftext[0] == 'T'))
+		    fflval.log = 1;
+		  else
+		    fflval.log = 0;
+		  return( BOOLEAN );
+		}
+	FF_BREAK
+case 7:
+FF_RULE_SETUP
+#line 317 "eval.l"
+{
+                  fflval.dbl = atof(fftext);
+		  return( DOUBLE );
+		}
+	FF_BREAK
+case 8:
+FF_RULE_SETUP
+#line 321 "eval.l"
+{
+                  if(        !strcasecmp(fftext,"#PI") ) {
+		     fflval.dbl = (double)(4) * atan((double)(1));
+		     return( DOUBLE );
+		  } else if( !strcasecmp(fftext,"#E") ) {
+		     fflval.dbl = exp((double)(1));
+		     return( DOUBLE );
+		  } else if( !strcasecmp(fftext,"#DEG") ) {
+		     fflval.dbl = ((double)4)*atan((double)1)/((double)180);
+		     return( DOUBLE );
+		  } else if( !strcasecmp(fftext,"#ROW") ) {
+		     return( ROWREF );
+		  } else if( !strcasecmp(fftext,"#NULL") ) {
+		     return( NULLREF );
+		  } else if( !strcasecmp(fftext,"#SNULL") ) {
+		     return( SNULLREF );
+		  } else {
+                     int len; 
+                     if (fftext[1] == '$') {
+                        len = strlen(fftext) - 3;
+                        fflval.str[0]     = '#';
+                        strncpy(fflval.str+1,&fftext[2],len);
+                        fflval.str[len+1] = '\0';
+                        fftext = fflval.str;
+		     }
+                     return( (*gParse.getData)(fftext, &fflval) );
+                  }
+                }
+	FF_BREAK
+case 9:
+FF_RULE_SETUP
+#line 349 "eval.l"
+{
+                  int len;
+                  len = strlen(fftext) - 2;
+		  if (len >= MAX_STRLEN) {
+		    char errMsg[100];
+		    gParse.status = PARSE_SYNTAX_ERR;
+		    strcpy (errMsg,"String exceeds maximum length: '");
+		    strncat(errMsg, &(fftext[1]), 20);
+		    strcat (errMsg,"...'");
+		    ffpmsg (errMsg);
+		    len = 0;
+		  } else {
+		    strncpy(fflval.str,&fftext[1],len);
+		  }
+		  fflval.str[len] = '\0';
+		  return( STRING );
+		}
+	FF_BREAK
+case 10:
+FF_RULE_SETUP
+#line 366 "eval.l"
+{
+		 int    len,type;
+
+                 if (fftext[0] == '$') {
+		    len = strlen(fftext) - 2;
+		    strncpy(fflval.str,&fftext[1],len);
+		    fflval.str[len] = '\0';
+		    fftext = fflval.str;
+		 } 
+		 type = ffGetVariable(fftext, &fflval);
+		 return( type );
+		}
+	FF_BREAK
+case 11:
+FF_RULE_SETUP
+#line 378 "eval.l"
+{
+                  char *fname;
+		  int len=0;
+                  fname = &fflval.str[0];
+		  while( (fname[len]=toupper(fftext[len])) ) len++;
+
+                  if(      FSTRCMP(fname,"BOX(")==0 
+                        || FSTRCMP(fname,"CIRCLE(")==0 
+                        || FSTRCMP(fname,"ELLIPSE(")==0 
+                        || FSTRCMP(fname,"NEAR(")==0 
+                        || FSTRCMP(fname,"ISNULL(")==0 
+                         )
+                     /* Return type is always boolean  */
+		     return( BFUNCTION );
+
+                  else if( FSTRCMP(fname,"GTIFILTER(")==0 )
+                     return( GTIFILTER );
+
+                  else if( FSTRCMP(fname,"REGFILTER(")==0 )
+                     return( REGFILTER );
+
+                  else if( FSTRCMP(fname,"STRSTR(")==0 )
+                     return( IFUNCTION );  /* Returns integer */
+
+                  else 
+		     return( FUNCTION  );
+		}
+	FF_BREAK
+case 12:
+FF_RULE_SETUP
+#line 405 "eval.l"
+{ return( INTCAST ); }
+	FF_BREAK
+case 13:
+FF_RULE_SETUP
+#line 406 "eval.l"
+{ return( FLTCAST ); }
+	FF_BREAK
+case 14:
+FF_RULE_SETUP
+#line 407 "eval.l"
+{ return( POWER   ); }
+	FF_BREAK
+case 15:
+FF_RULE_SETUP
+#line 408 "eval.l"
+{ return( NOT     ); }
+	FF_BREAK
+case 16:
+FF_RULE_SETUP
+#line 409 "eval.l"
+{ return( OR      ); }
+	FF_BREAK
+case 17:
+FF_RULE_SETUP
+#line 410 "eval.l"
+{ return( AND     ); }
+	FF_BREAK
+case 18:
+FF_RULE_SETUP
+#line 411 "eval.l"
+{ return( EQ      ); }
+	FF_BREAK
+case 19:
+FF_RULE_SETUP
+#line 412 "eval.l"
+{ return( NE      ); }
+	FF_BREAK
+case 20:
+FF_RULE_SETUP
+#line 413 "eval.l"
+{ return( GT      ); }
+	FF_BREAK
+case 21:
+FF_RULE_SETUP
+#line 414 "eval.l"
+{ return( LT      ); }
+	FF_BREAK
+case 22:
+FF_RULE_SETUP
+#line 415 "eval.l"
+{ return( GTE     ); }
+	FF_BREAK
+case 23:
+FF_RULE_SETUP
+#line 416 "eval.l"
+{ return( LTE     ); }
+	FF_BREAK
+case 24:
+FF_RULE_SETUP
+#line 417 "eval.l"
+{ return( '\n'    ); }
+	FF_BREAK
+case 25:
+FF_RULE_SETUP
+#line 418 "eval.l"
+{ return( fftext[0] ); }
+	FF_BREAK
+case 26:
+FF_RULE_SETUP
+#line 419 "eval.l"
+ECHO;
+	FF_BREAK
+case FF_STATE_EOF(INITIAL):
+	ffterminate();
+
+	case FF_END_OF_BUFFER:
+		{
+		/* Amount of text matched not including the EOB char. */
+		int ff_amount_of_matched_text = (int) (ff_cp - fftext_ptr) - 1;
+
+		/* Undo the effects of FF_DO_BEFORE_ACTION. */
+		*ff_cp = ff_hold_char;
+		FF_RESTORE_FF_MORE_OFFSET
+
+		if ( ff_current_buffer->ff_buffer_status == FF_BUFFER_NEW )
+			{
+			/* We're scanning a new file or input source.  It's
+			 * possible that this happened because the user
+			 * just pointed ffin at a new source and called
+			 * fflex().  If so, then we have to assure
+			 * consistency between ff_current_buffer and our
+			 * globals.  Here is the right place to do so, because
+			 * this is the first action (other than possibly a
+			 * back-up) that will match for the new input source.
+			 */
+			ff_n_chars = ff_current_buffer->ff_n_chars;
+			ff_current_buffer->ff_input_file = ffin;
+			ff_current_buffer->ff_buffer_status = FF_BUFFER_NORMAL;
+			}
+
+		/* Note that here we test for ff_c_buf_p "<=" to the position
+		 * of the first EOB in the buffer, since ff_c_buf_p will
+		 * already have been incremented past the NUL character
+		 * (since all states make transitions on EOB to the
+		 * end-of-buffer state).  Contrast this with the test
+		 * in input().
+		 */
+		if ( ff_c_buf_p <= &ff_current_buffer->ff_ch_buf[ff_n_chars] )
+			{ /* This was really a NUL. */
+			ff_state_type ff_next_state;
+
+			ff_c_buf_p = fftext_ptr + ff_amount_of_matched_text;
+
+			ff_current_state = ff_get_previous_state();
+
+			/* Okay, we're now positioned to make the NUL
+			 * transition.  We couldn't have
+			 * ff_get_previous_state() go ahead and do it
+			 * for us because it doesn't know how to deal
+			 * with the possibility of jamming (and we don't
+			 * want to build jamming into it because then it
+			 * will run more slowly).
+			 */
+
+			ff_next_state = ff_try_NUL_trans( ff_current_state );
+
+			ff_bp = fftext_ptr + FF_MORE_ADJ;
+
+			if ( ff_next_state )
+				{
+				/* Consume the NUL. */
+				ff_cp = ++ff_c_buf_p;
+				ff_current_state = ff_next_state;
+				goto ff_match;
+				}
+
+			else
+				{
+				ff_cp = ff_c_buf_p;
+				goto ff_find_action;
+				}
+			}
+
+		else switch ( ff_get_next_buffer() )
+			{
+			case EOB_ACT_END_OF_FILE:
+				{
+				ff_did_buffer_switch_on_eof = 0;
+
+				if ( ffwrap() )
+					{
+					/* Note: because we've taken care in
+					 * ff_get_next_buffer() to have set up
+					 * fftext, we can now set up
+					 * ff_c_buf_p so that if some total
+					 * hoser (like flex itself) wants to
+					 * call the scanner after we return the
+					 * FF_NULL, it'll still work - another
+					 * FF_NULL will get returned.
+					 */
+					ff_c_buf_p = fftext_ptr + FF_MORE_ADJ;
+
+					ff_act = FF_STATE_EOF(FF_START);
+					goto do_action;
+					}
+
+				else
+					{
+					if ( ! ff_did_buffer_switch_on_eof )
+						FF_NEW_FILE;
+					}
+				break;
+				}
+
+			case EOB_ACT_CONTINUE_SCAN:
+				ff_c_buf_p =
+					fftext_ptr + ff_amount_of_matched_text;
+
+				ff_current_state = ff_get_previous_state();
+
+				ff_cp = ff_c_buf_p;
+				ff_bp = fftext_ptr + FF_MORE_ADJ;
+				goto ff_match;
+
+			case EOB_ACT_LAST_MATCH:
+				ff_c_buf_p =
+				&ff_current_buffer->ff_ch_buf[ff_n_chars];
+
+				ff_current_state = ff_get_previous_state();
+
+				ff_cp = ff_c_buf_p;
+				ff_bp = fftext_ptr + FF_MORE_ADJ;
+				goto ff_find_action;
+			}
+		break;
+		}
+
+	default:
+		FF_FATAL_ERROR(
+			"fatal flex scanner internal error--no action found" );
+	} /* end of action switch */
+		} /* end of scanning one token */
+	} /* end of fflex */
+
+
+/* ff_get_next_buffer - try to read in a new buffer
+ *
+ * Returns a code representing an action:
+ *	EOB_ACT_LAST_MATCH -
+ *	EOB_ACT_CONTINUE_SCAN - continue scanning from current position
+ *	EOB_ACT_END_OF_FILE - end of file
+ */
+
+static int ff_get_next_buffer()
+	{
+	register char *dest = ff_current_buffer->ff_ch_buf;
+	register char *source = fftext_ptr;
+	register int number_to_move, i;
+	int ret_val;
+
+	if ( ff_c_buf_p > &ff_current_buffer->ff_ch_buf[ff_n_chars + 1] )
+		FF_FATAL_ERROR(
+		"fatal flex scanner internal error--end of buffer missed" );
+
+	if ( ff_current_buffer->ff_fill_buffer == 0 )
+		{ /* Don't try to fill the buffer, so this is an EOF. */
+		if ( ff_c_buf_p - fftext_ptr - FF_MORE_ADJ == 1 )
+			{
+			/* We matched a single character, the EOB, so
+			 * treat this as a final EOF.
+			 */
+			return EOB_ACT_END_OF_FILE;
+			}
+
+		else
+			{
+			/* We matched some text prior to the EOB, first
+			 * process it.
+			 */
+			return EOB_ACT_LAST_MATCH;
+			}
+		}
+
+	/* Try to read more data. */
+
+	/* First move last chars to start of buffer. */
+	number_to_move = (int) (ff_c_buf_p - fftext_ptr) - 1;
+
+	for ( i = 0; i < number_to_move; ++i )
+		*(dest++) = *(source++);
+
+	if ( ff_current_buffer->ff_buffer_status == FF_BUFFER_EOF_PENDING )
+		/* don't do the read, it's not guaranteed to return an EOF,
+		 * just force an EOF
+		 */
+		ff_current_buffer->ff_n_chars = ff_n_chars = 0;
+
+	else
+		{
+		int num_to_read =
+			ff_current_buffer->ff_buf_size - number_to_move - 1;
+
+		while ( num_to_read <= 0 )
+			{ /* Not enough room in the buffer - grow it. */
+#ifdef FF_USES_REJECT
+			FF_FATAL_ERROR(
+"input buffer overflow, can't enlarge buffer because scanner uses REJECT" );
+#else
+
+			/* just a shorter name for the current buffer */
+			FF_BUFFER_STATE b = ff_current_buffer;
+
+			int ff_c_buf_p_offset =
+				(int) (ff_c_buf_p - b->ff_ch_buf);
+
+			if ( b->ff_is_our_buffer )
+				{
+				int new_size = b->ff_buf_size * 2;
+
+				if ( new_size <= 0 )
+					b->ff_buf_size += b->ff_buf_size / 8;
+				else
+					b->ff_buf_size *= 2;
+
+				b->ff_ch_buf = (char *)
+					/* Include room in for 2 EOB chars. */
+					ff_flex_realloc( (void *) b->ff_ch_buf,
+							 b->ff_buf_size + 2 );
+				}
+			else
+				/* Can't grow it, we don't own it. */
+				b->ff_ch_buf = 0;
+
+			if ( ! b->ff_ch_buf )
+				FF_FATAL_ERROR(
+				"fatal error - scanner input buffer overflow" );
+
+			ff_c_buf_p = &b->ff_ch_buf[ff_c_buf_p_offset];
+
+			num_to_read = ff_current_buffer->ff_buf_size -
+						number_to_move - 1;
+#endif
+			}
+
+		if ( num_to_read > FF_READ_BUF_SIZE )
+			num_to_read = FF_READ_BUF_SIZE;
+
+		/* Read in more data. */
+		FF_INPUT( (&ff_current_buffer->ff_ch_buf[number_to_move]),
+			ff_n_chars, num_to_read );
+
+		ff_current_buffer->ff_n_chars = ff_n_chars;
+		}
+
+	if ( ff_n_chars == 0 )
+		{
+		if ( number_to_move == FF_MORE_ADJ )
+			{
+			ret_val = EOB_ACT_END_OF_FILE;
+			ffrestart( ffin );
+			}
+
+		else
+			{
+			ret_val = EOB_ACT_LAST_MATCH;
+			ff_current_buffer->ff_buffer_status =
+				FF_BUFFER_EOF_PENDING;
+			}
+		}
+
+	else
+		ret_val = EOB_ACT_CONTINUE_SCAN;
+
+	ff_n_chars += number_to_move;
+	ff_current_buffer->ff_ch_buf[ff_n_chars] = FF_END_OF_BUFFER_CHAR;
+	ff_current_buffer->ff_ch_buf[ff_n_chars + 1] = FF_END_OF_BUFFER_CHAR;
+
+	fftext_ptr = &ff_current_buffer->ff_ch_buf[0];
+
+	return ret_val;
+	}
+
+
+/* ff_get_previous_state - get the state just before the EOB char was reached */
+
+static ff_state_type ff_get_previous_state()
+	{
+	register ff_state_type ff_current_state;
+	register char *ff_cp;
+
+	ff_current_state = ff_start;
+
+	for ( ff_cp = fftext_ptr + FF_MORE_ADJ; ff_cp < ff_c_buf_p; ++ff_cp )
+		{
+		register FF_CHAR ff_c = (*ff_cp ? ff_ec[FF_SC_TO_UI(*ff_cp)] : 1);
+		if ( ff_accept[ff_current_state] )
+			{
+			ff_last_accepting_state = ff_current_state;
+			ff_last_accepting_cpos = ff_cp;
+			}
+		while ( ff_chk[ff_base[ff_current_state] + ff_c] != ff_current_state )
+			{
+			ff_current_state = (int) ff_def[ff_current_state];
+			if ( ff_current_state >= 160 )
+				ff_c = ff_meta[(unsigned int) ff_c];
+			}
+		ff_current_state = ff_nxt[ff_base[ff_current_state] + (unsigned int) ff_c];
+		}
+
+	return ff_current_state;
+	}
+
+
+/* ff_try_NUL_trans - try to make a transition on the NUL character
+ *
+ * synopsis
+ *	next_state = ff_try_NUL_trans( current_state );
+ */
+
+#ifdef FF_USE_PROTOS
+static ff_state_type ff_try_NUL_trans( ff_state_type ff_current_state )
+#else
+static ff_state_type ff_try_NUL_trans( ff_current_state )
+ff_state_type ff_current_state;
+#endif
+	{
+	register int ff_is_jam;
+	register char *ff_cp = ff_c_buf_p;
+
+	register FF_CHAR ff_c = 1;
+	if ( ff_accept[ff_current_state] )
+		{
+		ff_last_accepting_state = ff_current_state;
+		ff_last_accepting_cpos = ff_cp;
+		}
+	while ( ff_chk[ff_base[ff_current_state] + ff_c] != ff_current_state )
+		{
+		ff_current_state = (int) ff_def[ff_current_state];
+		if ( ff_current_state >= 160 )
+			ff_c = ff_meta[(unsigned int) ff_c];
+		}
+	ff_current_state = ff_nxt[ff_base[ff_current_state] + (unsigned int) ff_c];
+	ff_is_jam = (ff_current_state == 159);
+
+	return ff_is_jam ? 0 : ff_current_state;
+	}
+
+
+#ifndef FF_NO_UNPUT
+#ifdef FF_USE_PROTOS
+static void ffunput( int c, register char *ff_bp )
+#else
+static void ffunput( c, ff_bp )
+int c;
+register char *ff_bp;
+#endif
+	{
+	register char *ff_cp = ff_c_buf_p;
+
+	/* undo effects of setting up fftext */
+	*ff_cp = ff_hold_char;
+
+	if ( ff_cp < ff_current_buffer->ff_ch_buf + 2 )
+		{ /* need to shift things up to make room */
+		/* +2 for EOB chars. */
+		register int number_to_move = ff_n_chars + 2;
+		register char *dest = &ff_current_buffer->ff_ch_buf[
+					ff_current_buffer->ff_buf_size + 2];
+		register char *source =
+				&ff_current_buffer->ff_ch_buf[number_to_move];
+
+		while ( source > ff_current_buffer->ff_ch_buf )
+			*--dest = *--source;
+
+		ff_cp += (int) (dest - source);
+		ff_bp += (int) (dest - source);
+		ff_current_buffer->ff_n_chars =
+			ff_n_chars = ff_current_buffer->ff_buf_size;
+
+		if ( ff_cp < ff_current_buffer->ff_ch_buf + 2 )
+			FF_FATAL_ERROR( "flex scanner push-back overflow" );
+		}
+
+	*--ff_cp = (char) c;
+
+
+	fftext_ptr = ff_bp;
+	ff_hold_char = *ff_cp;
+	ff_c_buf_p = ff_cp;
+	}
+#endif	/* ifndef FF_NO_UNPUT */
+
+
+#ifdef __cplusplus
+static int ffinput()
+#else
+static int input()
+#endif
+	{
+	int c;
+
+	*ff_c_buf_p = ff_hold_char;
+
+	if ( *ff_c_buf_p == FF_END_OF_BUFFER_CHAR )
+		{
+		/* ff_c_buf_p now points to the character we want to return.
+		 * If this occurs *before* the EOB characters, then it's a
+		 * valid NUL; if not, then we've hit the end of the buffer.
+		 */
+		if ( ff_c_buf_p < &ff_current_buffer->ff_ch_buf[ff_n_chars] )
+			/* This was really a NUL. */
+			*ff_c_buf_p = '\0';
+
+		else
+			{ /* need more input */
+			int offset = ff_c_buf_p - fftext_ptr;
+			++ff_c_buf_p;
+
+			switch ( ff_get_next_buffer() )
+				{
+				case EOB_ACT_LAST_MATCH:
+					/* This happens because ff_g_n_b()
+					 * sees that we've accumulated a
+					 * token and flags that we need to
+					 * try matching the token before
+					 * proceeding.  But for input(),
+					 * there's no matching to consider.
+					 * So convert the EOB_ACT_LAST_MATCH
+					 * to EOB_ACT_END_OF_FILE.
+					 */
+
+					/* Reset buffer status. */
+					ffrestart( ffin );
+
+					/* fall through */
+
+				case EOB_ACT_END_OF_FILE:
+					{
+					if ( ffwrap() )
+						return EOF;
+
+					if ( ! ff_did_buffer_switch_on_eof )
+						FF_NEW_FILE;
+#ifdef __cplusplus
+					return ffinput();
+#else
+					return input();
+#endif
+					}
+
+				case EOB_ACT_CONTINUE_SCAN:
+					ff_c_buf_p = fftext_ptr + offset;
+					break;
+				}
+			}
+		}
+
+	c = *(unsigned char *) ff_c_buf_p;	/* cast for 8-bit char's */
+	*ff_c_buf_p = '\0';	/* preserve fftext */
+	ff_hold_char = *++ff_c_buf_p;
+
+
+	return c;
+	}
+
+
+#ifdef FF_USE_PROTOS
+void ffrestart( FILE *input_file )
+#else
+void ffrestart( input_file )
+FILE *input_file;
+#endif
+	{
+	if ( ! ff_current_buffer )
+		ff_current_buffer = ff_create_buffer( ffin, FF_BUF_SIZE );
+
+	ff_init_buffer( ff_current_buffer, input_file );
+	ff_load_buffer_state();
+	}
+
+
+#ifdef FF_USE_PROTOS
+void ff_switch_to_buffer( FF_BUFFER_STATE new_buffer )
+#else
+void ff_switch_to_buffer( new_buffer )
+FF_BUFFER_STATE new_buffer;
+#endif
+	{
+	if ( ff_current_buffer == new_buffer )
+		return;
+
+	if ( ff_current_buffer )
+		{
+		/* Flush out information for old buffer. */
+		*ff_c_buf_p = ff_hold_char;
+		ff_current_buffer->ff_buf_pos = ff_c_buf_p;
+		ff_current_buffer->ff_n_chars = ff_n_chars;
+		}
+
+	ff_current_buffer = new_buffer;
+	ff_load_buffer_state();
+
+	/* We don't actually know whether we did this switch during
+	 * EOF (ffwrap()) processing, but the only time this flag
+	 * is looked at is after ffwrap() is called, so it's safe
+	 * to go ahead and always set it.
+	 */
+	ff_did_buffer_switch_on_eof = 1;
+	}
+
+
+#ifdef FF_USE_PROTOS
+void ff_load_buffer_state( void )
+#else
+void ff_load_buffer_state()
+#endif
+	{
+	ff_n_chars = ff_current_buffer->ff_n_chars;
+	fftext_ptr = ff_c_buf_p = ff_current_buffer->ff_buf_pos;
+	ffin = ff_current_buffer->ff_input_file;
+	ff_hold_char = *ff_c_buf_p;
+	}
+
+
+#ifdef FF_USE_PROTOS
+FF_BUFFER_STATE ff_create_buffer( FILE *file, int size )
+#else
+FF_BUFFER_STATE ff_create_buffer( file, size )
+FILE *file;
+int size;
+#endif
+	{
+	FF_BUFFER_STATE b;
+
+	b = (FF_BUFFER_STATE) ff_flex_alloc( sizeof( struct ff_buffer_state ) );
+	if ( ! b )
+		FF_FATAL_ERROR( "out of dynamic memory in ff_create_buffer()" );
+
+	b->ff_buf_size = size;
+
+	/* ff_ch_buf has to be 2 characters longer than the size given because
+	 * we need to put in 2 end-of-buffer characters.
+	 */
+	b->ff_ch_buf = (char *) ff_flex_alloc( b->ff_buf_size + 2 );
+	if ( ! b->ff_ch_buf )
+		FF_FATAL_ERROR( "out of dynamic memory in ff_create_buffer()" );
+
+	b->ff_is_our_buffer = 1;
+
+	ff_init_buffer( b, file );
+
+	return b;
+	}
+
+
+#ifdef FF_USE_PROTOS
+void ff_delete_buffer( FF_BUFFER_STATE b )
+#else
+void ff_delete_buffer( b )
+FF_BUFFER_STATE b;
+#endif
+	{
+	if ( ! b )
+		return;
+
+	if ( b == ff_current_buffer )
+		ff_current_buffer = (FF_BUFFER_STATE) 0;
+
+	if ( b->ff_is_our_buffer )
+		ff_flex_free( (void *) b->ff_ch_buf );
+
+	ff_flex_free( (void *) b );
+	}
+
+
+#ifndef FF_ALWAYS_INTERACTIVE
+#ifndef FF_NEVER_INTERACTIVE
+extern int isatty FF_PROTO(( int ));
+#endif
+#endif
+
+#ifdef FF_USE_PROTOS
+void ff_init_buffer( FF_BUFFER_STATE b, FILE *file )
+#else
+void ff_init_buffer( b, file )
+FF_BUFFER_STATE b;
+FILE *file;
+#endif
+
+
+	{
+	ff_flush_buffer( b );
+
+	b->ff_input_file = file;
+	b->ff_fill_buffer = 1;
+
+#if FF_ALWAYS_INTERACTIVE
+	b->ff_is_interactive = 1;
+#else
+#if FF_NEVER_INTERACTIVE
+	b->ff_is_interactive = 0;
+#else
+	b->ff_is_interactive = file ? (isatty( fileno(file) ) > 0) : 0;
+#endif
+#endif
+	}
+
+
+#ifdef FF_USE_PROTOS
+void ff_flush_buffer( FF_BUFFER_STATE b )
+#else
+void ff_flush_buffer( b )
+FF_BUFFER_STATE b;
+#endif
+
+	{
+	if ( ! b )
+		return;
+
+	b->ff_n_chars = 0;
+
+	/* We always need two end-of-buffer characters.  The first causes
+	 * a transition to the end-of-buffer state.  The second causes
+	 * a jam in that state.
+	 */
+	b->ff_ch_buf[0] = FF_END_OF_BUFFER_CHAR;
+	b->ff_ch_buf[1] = FF_END_OF_BUFFER_CHAR;
+
+	b->ff_buf_pos = &b->ff_ch_buf[0];
+
+	b->ff_at_bol = 1;
+	b->ff_buffer_status = FF_BUFFER_NEW;
+
+	if ( b == ff_current_buffer )
+		ff_load_buffer_state();
+	}
+
+
+#ifndef FF_NO_SCAN_BUFFER
+#ifdef FF_USE_PROTOS
+FF_BUFFER_STATE ff_scan_buffer( char *base, ff_size_t size )
+#else
+FF_BUFFER_STATE ff_scan_buffer( base, size )
+char *base;
+ff_size_t size;
+#endif
+	{
+	FF_BUFFER_STATE b;
+
+	if ( size < 2 ||
+	     base[size-2] != FF_END_OF_BUFFER_CHAR ||
+	     base[size-1] != FF_END_OF_BUFFER_CHAR )
+		/* They forgot to leave room for the EOB's. */
+		return 0;
+
+	b = (FF_BUFFER_STATE) ff_flex_alloc( sizeof( struct ff_buffer_state ) );
+	if ( ! b )
+		FF_FATAL_ERROR( "out of dynamic memory in ff_scan_buffer()" );
+
+	b->ff_buf_size = size - 2;	/* "- 2" to take care of EOB's */
+	b->ff_buf_pos = b->ff_ch_buf = base;
+	b->ff_is_our_buffer = 0;
+	b->ff_input_file = 0;
+	b->ff_n_chars = b->ff_buf_size;
+	b->ff_is_interactive = 0;
+	b->ff_at_bol = 1;
+	b->ff_fill_buffer = 0;
+	b->ff_buffer_status = FF_BUFFER_NEW;
+
+	ff_switch_to_buffer( b );
+
+	return b;
+	}
+#endif
+
+
+#ifndef FF_NO_SCAN_STRING
+#ifdef FF_USE_PROTOS
+FF_BUFFER_STATE ff_scan_string( ffconst char *ff_str )
+#else
+FF_BUFFER_STATE ff_scan_string( ff_str )
+ffconst char *ff_str;
+#endif
+	{
+	int len;
+	for ( len = 0; ff_str[len]; ++len )
+		;
+
+	return ff_scan_bytes( ff_str, len );
+	}
+#endif
+
+
+#ifndef FF_NO_SCAN_BYTES
+#ifdef FF_USE_PROTOS
+FF_BUFFER_STATE ff_scan_bytes( ffconst char *bytes, int len )
+#else
+FF_BUFFER_STATE ff_scan_bytes( bytes, len )
+ffconst char *bytes;
+int len;
+#endif
+	{
+	FF_BUFFER_STATE b;
+	char *buf;
+	ff_size_t n;
+	int i;
+
+	/* Get memory for full buffer, including space for trailing EOB's. */
+	n = len + 2;
+	buf = (char *) ff_flex_alloc( n );
+	if ( ! buf )
+		FF_FATAL_ERROR( "out of dynamic memory in ff_scan_bytes()" );
+
+	for ( i = 0; i < len; ++i )
+		buf[i] = bytes[i];
+
+	buf[len] = buf[len+1] = FF_END_OF_BUFFER_CHAR;
+
+	b = ff_scan_buffer( buf, n );
+	if ( ! b )
+		FF_FATAL_ERROR( "bad buffer in ff_scan_bytes()" );
+
+	/* It's okay to grow etc. this buffer, and we should throw it
+	 * away when we're done.
+	 */
+	b->ff_is_our_buffer = 1;
+
+	return b;
+	}
+#endif
+
+
+#ifndef FF_NO_PUSH_STATE
+#ifdef FF_USE_PROTOS
+static void ff_push_state( int new_state )
+#else
+static void ff_push_state( new_state )
+int new_state;
+#endif
+	{
+	if ( ff_start_stack_ptr >= ff_start_stack_depth )
+		{
+		ff_size_t new_size;
+
+		ff_start_stack_depth += FF_START_STACK_INCR;
+		new_size = ff_start_stack_depth * sizeof( int );
+
+		if ( ! ff_start_stack )
+			ff_start_stack = (int *) ff_flex_alloc( new_size );
+
+		else
+			ff_start_stack = (int *) ff_flex_realloc(
+					(void *) ff_start_stack, new_size );
+
+		if ( ! ff_start_stack )
+			FF_FATAL_ERROR(
+			"out of memory expanding start-condition stack" );
+		}
+
+	ff_start_stack[ff_start_stack_ptr++] = FF_START;
+
+	BEGIN(new_state);
+	}
+#endif
+
+
+#ifndef FF_NO_POP_STATE
+static void ff_pop_state()
+	{
+	if ( --ff_start_stack_ptr < 0 )
+		FF_FATAL_ERROR( "start-condition stack underflow" );
+
+	BEGIN(ff_start_stack[ff_start_stack_ptr]);
+	}
+#endif
+
+
+#ifndef FF_NO_TOP_STATE
+static int ff_top_state()
+	{
+	return ff_start_stack[ff_start_stack_ptr - 1];
+	}
+#endif
+
+#ifndef FF_EXIT_FAILURE
+#define FF_EXIT_FAILURE 2
+#endif
+
+#ifdef FF_USE_PROTOS
+static void ff_fatal_error( ffconst char msg[] )
+#else
+static void ff_fatal_error( msg )
+char msg[];
+#endif
+	{
+	(void) fprintf( stderr, "%s\n", msg );
+	exit( FF_EXIT_FAILURE );
+	}
+
+
+
+/* Redefine ffless() so it works in section 3 code. */
+
+#undef ffless
+#define ffless(n) \
+	do \
+		{ \
+		/* Undo effects of setting up fftext. */ \
+		fftext[ffleng] = ff_hold_char; \
+		ff_c_buf_p = fftext + n; \
+		ff_hold_char = *ff_c_buf_p; \
+		*ff_c_buf_p = '\0'; \
+		ffleng = n; \
+		} \
+	while ( 0 )
+
+
+/* Internal utility routines. */
+
+#ifndef fftext_ptr
+#ifdef FF_USE_PROTOS
+static void ff_flex_strncpy( char *s1, ffconst char *s2, int n )
+#else
+static void ff_flex_strncpy( s1, s2, n )
+char *s1;
+ffconst char *s2;
+int n;
+#endif
+	{
+	register int i;
+	for ( i = 0; i < n; ++i )
+		s1[i] = s2[i];
+	}
+#endif
+
+#ifdef FF_NEED_STRLEN
+#ifdef FF_USE_PROTOS
+static int ff_flex_strlen( ffconst char *s )
+#else
+static int ff_flex_strlen( s )
+ffconst char *s;
+#endif
+	{
+	register int n;
+	for ( n = 0; s[n]; ++n )
+		;
+
+	return n;
+	}
+#endif
+
+
+#ifdef FF_USE_PROTOS
+static void *ff_flex_alloc( ff_size_t size )
+#else
+static void *ff_flex_alloc( size )
+ff_size_t size;
+#endif
+	{
+	return (void *) malloc( size );
+	}
+
+#ifdef FF_USE_PROTOS
+static void *ff_flex_realloc( void *ptr, ff_size_t size )
+#else
+static void *ff_flex_realloc( ptr, size )
+void *ptr;
+ff_size_t size;
+#endif
+	{
+	/* The cast to (char *) in the following accommodates both
+	 * implementations that use char* generic pointers, and those
+	 * that use void* generic pointers.  It works with the latter
+	 * because both ANSI C and C++ allow castless assignment from
+	 * any pointer type to void*, and deal with argument conversions
+	 * as though doing an assignment.
+	 */
+	return (void *) realloc( (char *) ptr, size );
+	}
+
+#ifdef FF_USE_PROTOS
+static void ff_flex_free( void *ptr )
+#else
+static void ff_flex_free( ptr )
+void *ptr;
+#endif
+	{
+	free( ptr );
+	}
+
+#if FF_MAIN
+int main()
+	{
+	fflex();
+	return 0;
+	}
+#endif
+#line 419 "eval.l"
+
+
+int ffwrap()
+{
+  /* MJT -- 13 June 1996
+     Supplied for compatibility with
+     pre-2.5.1 versions of flex which
+     do not recognize %option noffwrap 
+  */
+  return(1);
+}
+
+/* 
+   expr_read is lifted from old ftools.skel. 
+   Now we can use any version of flex with
+   no .skel file necessary! MJT - 13 June 1996
+
+   keep a memory of how many bytes have been
+   read previously, so that an unlimited-sized
+   buffer can be supported. PDW - 28 Feb 1998
+*/
+
+static int expr_read(char *buf, int nbytes)
+{
+ int n;
+ 
+ n = 0;
+ if( !gParse.is_eobuf ) {
+     do {
+        buf[n++] = gParse.expr[gParse.index++];
+       } while ((n<nbytes)&&(gParse.expr[gParse.index] != '\0'));
+     if( gParse.expr[gParse.index] == '\0' ) gParse.is_eobuf = 1;
+ }
+ buf[n] = '\0';
+ return(n);
+}
+
+int ffGetVariable( char *varName, FFSTYPE *thelval )
+{
+   int varNum, type;
+   char errMsg[MAXVARNAME+25];
+
+   varNum = find_variable( varName );
+   if( varNum<0 ) {
+      if( gParse.getData ) {
+	 type = (*gParse.getData)( varName, thelval );
+      } else {
+	 type = pERROR;
+	 gParse.status = PARSE_SYNTAX_ERR;
+	 strcpy (errMsg,"Unable to find data: ");
+	 strncat(errMsg, varName, MAXVARNAME);
+	 ffpmsg (errMsg);
+      }
+   } else {
+      /*  Convert variable type into expression type  */
+      switch( gParse.varData[ varNum ].type ) {
+      case LONG:
+      case DOUBLE:   type =  COLUMN;  break;
+      case BOOLEAN:  type = BCOLUMN;  break;
+      case STRING:   type = SCOLUMN;  break;
+      case BITSTR:   type =  BITCOL;  break;
+      default:
+	 type = pERROR;
+	 gParse.status = PARSE_SYNTAX_ERR;
+	 strcpy (errMsg,"Bad datatype for data: ");
+	 strncat(errMsg, varName, MAXVARNAME);
+	 ffpmsg (errMsg);
+	 break;
+      }
+      thelval->lng = varNum;
+   }
+   return( type );
+}
+
+static int find_variable(char *varName)
+{
+   int i;
+ 
+   if( gParse.nCols )
+      for( i=0; i<gParse.nCols; i++ ) {
+         if( ! strncasecmp(gParse.varData[i].name,varName,MAXVARNAME) ) {
+            return( i );
+         }
+      }
+   return( -1 );
+}
+
+#if defined(vms) || defined(__vms) || defined(WIN32) || defined(__WIN32__) || defined(macintosh)
+
+/* ================================================================== */
+/* A hack for nonunix machines, which lack strcasecmp and strncasecmp */
+/* ================================================================== */
+
+int strcasecmp(const char *s1, const char *s2)
+{
+   char c1, c2;
+
+   for (;;) {
+      c1 = toupper( *s1 );
+      c2 = toupper( *s2 );
+
+      if (c1 < c2) return(-1);
+      if (c1 > c2) return(1);
+      if (c1 == 0) return(0);
+      s1++;
+      s2++;
+   }
+}
+
+int strncasecmp(const char *s1, const char *s2, size_t n)
+{
+   char c1, c2;
+
+   for (; n-- ;) {
+      c1 = toupper( *s1 );
+      c2 = toupper( *s2 );
+
+      if (c1 < c2) return(-1);
+      if (c1 > c2) return(1);
+      if (c1 == 0) return(0);
+      s1++;
+      s2++;
+   }
+   return(0);
+}
+
+#endif
diff --git a/src/plugins/cfitsio/eval_tab.h b/src/plugins/cfitsio/eval_tab.h
new file mode 100644
index 0000000..aed4459
--- /dev/null
+++ b/src/plugins/cfitsio/eval_tab.h
@@ -0,0 +1,42 @@
+typedef union {
+    int    Node;        /* Index of Node */
+    double dbl;         /* real value    */
+    long   lng;         /* integer value */
+    char   log;         /* logical value */
+    char   str[MAX_STRLEN];    /* string value  */
+} FFSTYPE;
+#define	BOOLEAN	258
+#define	LONG	259
+#define	DOUBLE	260
+#define	STRING	261
+#define	BITSTR	262
+#define	FUNCTION	263
+#define	BFUNCTION	264
+#define	IFUNCTION	265
+#define	GTIFILTER	266
+#define	REGFILTER	267
+#define	COLUMN	268
+#define	BCOLUMN	269
+#define	SCOLUMN	270
+#define	BITCOL	271
+#define	ROWREF	272
+#define	NULLREF	273
+#define	SNULLREF	274
+#define	OR	275
+#define	AND	276
+#define	EQ	277
+#define	NE	278
+#define	GT	279
+#define	LT	280
+#define	LTE	281
+#define	GTE	282
+#define	POWER	283
+#define	NOT	284
+#define	INTCAST	285
+#define	FLTCAST	286
+#define	UMINUS	287
+#define	ACCUM	288
+#define	DIFF	289
+
+
+extern FFSTYPE fflval;
diff --git a/src/plugins/cfitsio/eval_y.c b/src/plugins/cfitsio/eval_y.c
new file mode 100644
index 0000000..e18cf11
--- /dev/null
+++ b/src/plugins/cfitsio/eval_y.c
@@ -0,0 +1,7333 @@
+
+/*  A Bison parser, made from eval.y
+ by  GNU Bison version 1.25
+  */
+
+#define FFBISON 1  /* Identify Bison output.  */
+
+#define	BOOLEAN	258
+#define	LONG	259
+#define	DOUBLE	260
+#define	STRING	261
+#define	BITSTR	262
+#define	FUNCTION	263
+#define	BFUNCTION	264
+#define	IFUNCTION	265
+#define	GTIFILTER	266
+#define	REGFILTER	267
+#define	COLUMN	268
+#define	BCOLUMN	269
+#define	SCOLUMN	270
+#define	BITCOL	271
+#define	ROWREF	272
+#define	NULLREF	273
+#define	SNULLREF	274
+#define	OR	275
+#define	AND	276
+#define	EQ	277
+#define	NE	278
+#define	GT	279
+#define	LT	280
+#define	LTE	281
+#define	GTE	282
+#define	POWER	283
+#define	NOT	284
+#define	INTCAST	285
+#define	FLTCAST	286
+#define	UMINUS	287
+#define	ACCUM	288
+#define	DIFF	289
+
+#line 1 "eval.y"
+
+/************************************************************************/
+/*                                                                      */
+/*                       CFITSIO Lexical Parser                         */
+/*                                                                      */
+/* This file is one of 3 files containing code which parses an          */
+/* arithmetic expression and evaluates it in the context of an input    */
+/* FITS file table extension.  The CFITSIO lexical parser is divided    */
+/* into the following 3 parts/files: the CFITSIO "front-end",           */
+/* eval_f.c, contains the interface between the user/CFITSIO and the    */
+/* real core of the parser; the FLEX interpreter, eval_l.c, takes the   */
+/* input string and parses it into tokens and identifies the FITS       */
+/* information required to evaluate the expression (ie, keywords and    */
+/* columns); and, the BISON grammar and evaluation routines, eval_y.c,  */
+/* receives the FLEX output and determines and performs the actual      */
+/* operations.  The files eval_l.c and eval_y.c are produced from       */
+/* running flex and bison on the files eval.l and eval.y, respectively. */
+/* (flex and bison are available from any GNU archive: see www.gnu.org) */
+/*                                                                      */
+/* The grammar rules, rather than evaluating the expression in situ,    */
+/* builds a tree, or Nodal, structure mapping out the order of          */
+/* operations and expression dependencies.  This "compilation" process  */
+/* allows for much faster processing of multiple rows.  This technique  */
+/* was developed by Uwe Lammers of the XMM Science Analysis System,     */
+/* although the CFITSIO implementation is entirely code original.       */
+/*                                                                      */
+/*                                                                      */
+/* Modification History:                                                */
+/*                                                                      */
+/*   Kent Blackburn      c1992  Original parser code developed for the  */
+/*                              FTOOLS software package, in particular, */
+/*                              the fselect task.                       */
+/*   Kent Blackburn      c1995  BIT column support added                */
+/*   Peter D Wilson   Feb 1998  Vector column support added             */
+/*   Peter D Wilson   May 1998  Ported to CFITSIO library.  User        */
+/*                              interface routines written, in essence  */
+/*                              making fselect, fcalc, and maketime     */
+/*                              capabilities available to all tools     */
+/*                              via single function calls.              */
+/*   Peter D Wilson   Jun 1998  Major rewrite of parser core, so as to  */
+/*                              create a run-time evaluation tree,      */
+/*                              inspired by the work of Uwe Lammers,    */
+/*                              resulting in a speed increase of        */
+/*                              10-100 times.                           */
+/*   Peter D Wilson   Jul 1998  gtifilter(a,b,c,d) function added       */
+/*   Peter D Wilson   Aug 1998  regfilter(a,b,c,d) function added       */
+/*   Peter D Wilson   Jul 1999  Make parser fitsfile-independent,       */
+/*                              allowing a purely vector-based usage    */
+/*  Craig B Markwardt Jun 2004  Add MEDIAN() function                   */
+/*  Craig B Markwardt Jun 2004  Add SUM(), and MIN/MAX() for bit arrays */
+/*  Craig B Markwardt Jun 2004  Allow subscripting of nX bit arrays     */
+/*  Craig B Markwardt Jun 2004  Implement statistical functions         */
+/*                              NVALID(), AVERAGE(), and STDDEV()       */
+/*                              for integer and floating point vectors  */
+/*  Craig B Markwardt Jun 2004  Use NULL values for range errors instead*/
+/*                              of throwing a parse error               */
+/*  Craig B Markwardt Oct 2004  Add ACCUM() and SEQDIFF() functions     */
+/*  Craig B Markwardt Feb 2005  Add ANGSEP() function                   */
+/*  Craig B Markwardt Aug 2005  CIRCLE, BOX, ELLIPSE, NEAR and REGFILTER*/
+/*                              functions now accept vector arguments   */
+/*  Craig B Markwardt Sum 2006  Add RANDOMN() and RANDOMP() functions   */
+/*  Craig B Markwardt Mar 2007  Allow arguments to RANDOM and RANDOMN to*/
+/*                              determine the output dimensions         */
+/*  Craig B Markwardt Aug 2009  Add substring STRMID() and string search*/
+/*                              STRSTR() functions; more overflow checks*/
+/*                                                                      */
+/************************************************************************/
+
+#define  APPROX 1.0e-7
+#include "eval_defs.h"
+#include "region.h"
+#include <time.h>
+
+#include <stdlib.h>
+
+#ifndef alloca
+#define alloca malloc
+#endif
+
+   /*  Shrink the initial stack depth to keep local data <32K (mac limit)  */
+   /*  yacc will allocate more space if needed, though.                    */
+#define  FFINITDEPTH   100
+
+/***************************************************************/
+/*  Replace Bison's BACKUP macro with one that fixes a bug --  */
+/*  must update state after popping the stack -- and allows    */
+/*  popping multiple terms at one time.                        */
+/***************************************************************/
+
+#define FFNEWBACKUP(token, value)                               \
+   do								\
+     if (ffchar == FFEMPTY )   					\
+       { ffchar = (token);                                      \
+         memcpy( &fflval, &(value), sizeof(value) );            \
+         ffchar1 = FFTRANSLATE (ffchar);			\
+         while (fflen--) FFPOPSTACK;				\
+         ffstate = *ffssp;					\
+         goto ffbackup;						\
+       }							\
+     else							\
+       { fferror ("syntax error: cannot back up"); FFERROR; }	\
+   while (0)
+
+/***************************************************************/
+/*  Useful macros for accessing/testing Nodes                  */
+/***************************************************************/
+
+#define TEST(a)        if( (a)<0 ) FFERROR
+#define SIZE(a)        gParse.Nodes[ a ].value.nelem
+#define TYPE(a)        gParse.Nodes[ a ].type
+#define OPER(a)        gParse.Nodes[ a ].operation
+#define PROMOTE(a,b)   if( TYPE(a) > TYPE(b) )                  \
+                          b = New_Unary( TYPE(a), 0, b );       \
+                       else if( TYPE(a) < TYPE(b) )             \
+	                  a = New_Unary( TYPE(b), 0, a );
+
+/*****  Internal functions  *****/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static int  Alloc_Node    ( void );
+static void Free_Last_Node( void );
+static void Evaluate_Node ( int thisNode );
+
+static int  New_Const ( int returnType, void *value, long len );
+static int  New_Column( int ColNum );
+static int  New_Offset( int ColNum, int offset );
+static int  New_Unary ( int returnType, int Op, int Node1 );
+static int  New_BinOp ( int returnType, int Node1, int Op, int Node2 );
+static int  New_Func  ( int returnType, funcOp Op, int nNodes,
+			int Node1, int Node2, int Node3, int Node4, 
+			int Node5, int Node6, int Node7 );
+static int  New_FuncSize( int returnType, funcOp Op, int nNodes,
+			int Node1, int Node2, int Node3, int Node4, 
+			  int Node5, int Node6, int Node7, int Size);
+static int  New_Deref ( int Var,  int nDim,
+			int Dim1, int Dim2, int Dim3, int Dim4, int Dim5 );
+static int  New_GTI   ( char *fname, int Node1, char *start, char *stop );
+static int  New_REG   ( char *fname, int NodeX, int NodeY, char *colNames );
+static int  New_Vector( int subNode );
+static int  Close_Vec ( int vecNode );
+static int  Locate_Col( Node *this );
+static int  Test_Dims ( int Node1, int Node2 );
+static void Copy_Dims ( int Node1, int Node2 );
+
+static void Allocate_Ptrs( Node *this );
+static void Do_Unary     ( Node *this );
+static void Do_Offset    ( Node *this );
+static void Do_BinOp_bit ( Node *this );
+static void Do_BinOp_str ( Node *this );
+static void Do_BinOp_log ( Node *this );
+static void Do_BinOp_lng ( Node *this );
+static void Do_BinOp_dbl ( Node *this );
+static void Do_Func      ( Node *this );
+static void Do_Deref     ( Node *this );
+static void Do_GTI       ( Node *this );
+static void Do_REG       ( Node *this );
+static void Do_Vector    ( Node *this );
+
+static long Search_GTI   ( double evtTime, long nGTI, double *start,
+			   double *stop, int ordered );
+
+static char  saobox (double xcen, double ycen, double xwid, double ywid,
+		     double rot,  double xcol, double ycol);
+static char  ellipse(double xcen, double ycen, double xrad, double yrad,
+		     double rot, double xcol, double ycol);
+static char  circle (double xcen, double ycen, double rad,
+		     double xcol, double ycol);
+static char  bnear  (double x, double y, double tolerance);
+static char  bitcmp (char *bitstrm1, char *bitstrm2);
+static char  bitlgte(char *bits1, int oper, char *bits2);
+
+static void  bitand(char *result, char *bitstrm1, char *bitstrm2);
+static void  bitor (char *result, char *bitstrm1, char *bitstrm2);
+static void  bitnot(char *result, char *bits);
+static int cstrmid(char *dest_str, int dest_len,
+		   char *src_str,  int src_len, int pos);
+
+static void  fferror(char *msg);
+
+#ifdef __cplusplus
+    }
+#endif
+
+
+#line 189 "eval.y"
+typedef union {
+    int    Node;        /* Index of Node */
+    double dbl;         /* real value    */
+    long   lng;         /* integer value */
+    char   log;         /* logical value */
+    char   str[MAX_STRLEN];    /* string value  */
+} FFSTYPE;
+#include <stdio.h>
+
+#ifndef __cplusplus
+#ifndef __STDC__
+#define const
+#endif
+#endif
+
+
+
+#define	FFFINAL		290
+#define	FFFLAG		-32768
+#define	FFNTBASE	54
+
+#define FFTRANSLATE(x) ((unsigned)(x) <= 289 ? fftranslate[x] : 62)
+
+static const char fftranslate[] = {     0,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,    50,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,    37,    41,     2,    52,
+    53,    38,    35,    20,    36,     2,    39,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,    22,     2,     2,
+    21,     2,    25,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+    47,     2,    51,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,    23,    40,    24,    30,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     2,     2,     2,     2,     2,
+     2,     2,     2,     2,     2,     1,     2,     3,     4,     5,
+     6,     7,     8,     9,    10,    11,    12,    13,    14,    15,
+    16,    17,    18,    19,    26,    27,    28,    29,    31,    32,
+    33,    34,    42,    43,    44,    45,    46,    48,    49
+};
+
+#if FFDEBUG != 0
+static const short ffprhs[] = {     0,
+     0,     1,     4,     6,     9,    12,    15,    18,    21,    24,
+    28,    31,    35,    39,    43,    46,    49,    51,    53,    58,
+    62,    66,    70,    75,    82,    91,   102,   115,   118,   122,
+   124,   126,   128,   133,   135,   137,   141,   145,   149,   153,
+   157,   161,   164,   167,   171,   175,   179,   185,   191,   197,
+   200,   204,   208,   212,   216,   222,   228,   238,   243,   250,
+   259,   270,   283,   286,   289,   292,   295,   297,   299,   304,
+   308,   312,   316,   320,   324,   328,   332,   336,   340,   344,
+   348,   352,   356,   360,   364,   368,   372,   376,   380,   384,
+   388,   392,   396,   402,   408,   412,   416,   420,   426,   434,
+   446,   462,   465,   469,   475,   485,   489,   497,   507,   512,
+   519,   528,   539,   552,   555,   559,   561,   563,   568,   570,
+   574,   578,   584,   590
+};
+
+static const short ffrhs[] = {    -1,
+    54,    55,     0,    50,     0,    58,    50,     0,    59,    50,
+     0,    61,    50,     0,    60,    50,     0,     1,    50,     0,
+    23,    59,     0,    56,    20,    59,     0,    23,    58,     0,
+    57,    20,    58,     0,    57,    20,    59,     0,    56,    20,
+    58,     0,    57,    24,     0,    56,    24,     0,     7,     0,
+    16,     0,    16,    23,    58,    24,     0,    60,    41,    60,
+     0,    60,    40,    60,     0,    60,    35,    60,     0,    60,
+    47,    58,    51,     0,    60,    47,    58,    20,    58,    51,
+     0,    60,    47,    58,    20,    58,    20,    58,    51,     0,
+    60,    47,    58,    20,    58,    20,    58,    20,    58,    51,
+     0,    60,    47,    58,    20,    58,    20,    58,    20,    58,
+    20,    58,    51,     0,    43,    60,     0,    52,    60,    53,
+     0,     4,     0,     5,     0,    13,     0,    13,    23,    58,
+    24,     0,    17,     0,    18,     0,    58,    37,    58,     0,
+    58,    35,    58,     0,    58,    36,    58,     0,    58,    38,
+    58,     0,    58,    39,    58,     0,    58,    42,    58,     0,
+    35,    58,     0,    36,    58,     0,    52,    58,    53,     0,
+    58,    38,    59,     0,    59,    38,    58,     0,    59,    25,
+    58,    22,    58,     0,    59,    25,    59,    22,    58,     0,
+    59,    25,    58,    22,    59,     0,     8,    53,     0,     8,
+    59,    53,     0,     8,    61,    53,     0,     8,    60,    53,
+     0,     8,    58,    53,     0,    10,    61,    20,    61,    53,
+     0,     8,    58,    20,    58,    53,     0,     8,    58,    20,
+    58,    20,    58,    20,    58,    53,     0,    58,    47,    58,
+    51,     0,    58,    47,    58,    20,    58,    51,     0,    58,
+    47,    58,    20,    58,    20,    58,    51,     0,    58,    47,
+    58,    20,    58,    20,    58,    20,    58,    51,     0,    58,
+    47,    58,    20,    58,    20,    58,    20,    58,    20,    58,
+    51,     0,    44,    58,     0,    44,    59,     0,    45,    58,
+     0,    45,    59,     0,     3,     0,    14,     0,    14,    23,
+    58,    24,     0,    60,    28,    60,     0,    60,    29,    60,
+     0,    60,    32,    60,     0,    60,    33,    60,     0,    60,
+    31,    60,     0,    60,    34,    60,     0,    58,    31,    58,
+     0,    58,    32,    58,     0,    58,    34,    58,     0,    58,
+    33,    58,     0,    58,    30,    58,     0,    58,    28,    58,
+     0,    58,    29,    58,     0,    61,    28,    61,     0,    61,
+    29,    61,     0,    61,    31,    61,     0,    61,    34,    61,
+     0,    61,    32,    61,     0,    61,    33,    61,     0,    59,
+    27,    59,     0,    59,    26,    59,     0,    59,    28,    59,
+     0,    59,    29,    59,     0,    58,    21,    58,    22,    58,
+     0,    59,    25,    59,    22,    59,     0,     9,    58,    53,
+     0,     9,    59,    53,     0,     9,    61,    53,     0,     8,
+    59,    20,    59,    53,     0,     9,    58,    20,    58,    20,
+    58,    53,     0,     9,    58,    20,    58,    20,    58,    20,
+    58,    20,    58,    53,     0,     9,    58,    20,    58,    20,
+    58,    20,    58,    20,    58,    20,    58,    20,    58,    53,
+     0,    11,    53,     0,    11,     6,    53,     0,    11,     6,
+    20,    58,    53,     0,    11,     6,    20,    58,    20,     6,
+    20,     6,    53,     0,    12,     6,    53,     0,    12,     6,
+    20,    58,    20,    58,    53,     0,    12,     6,    20,    58,
+    20,    58,    20,     6,    53,     0,    59,    47,    58,    51,
+     0,    59,    47,    58,    20,    58,    51,     0,    59,    47,
+    58,    20,    58,    20,    58,    51,     0,    59,    47,    58,
+    20,    58,    20,    58,    20,    58,    51,     0,    59,    47,
+    58,    20,    58,    20,    58,    20,    58,    20,    58,    51,
+     0,    43,    59,     0,    52,    59,    53,     0,     6,     0,
+    15,     0,    15,    23,    58,    24,     0,    19,     0,    52,
+    61,    53,     0,    61,    35,    61,     0,    59,    25,    61,
+    22,    61,     0,     8,    61,    20,    61,    53,     0,     8,
+    61,    20,    58,    20,    58,    53,     0
+};
+
+#endif
+
+#if FFDEBUG != 0
+static const short ffrline[] = { 0,
+   241,   242,   245,   246,   252,   258,   264,   270,   273,   275,
+   288,   290,   303,   314,   328,   332,   336,   340,   342,   351,
+   354,   357,   366,   368,   370,   372,   374,   376,   379,   383,
+   385,   387,   389,   398,   400,   402,   405,   408,   411,   414,
+   417,   420,   422,   424,   426,   430,   434,   453,   472,   491,
+   504,   518,   530,   561,   659,   667,   729,   753,   755,   757,
+   759,   761,   763,   765,   767,   769,   773,   775,   777,   786,
+   789,   792,   795,   798,   801,   804,   807,   810,   813,   816,
+   819,   822,   825,   828,   831,   834,   837,   840,   843,   845,
+   847,   849,   852,   859,   876,   889,   902,   913,   929,   953,
+   981,  1018,  1022,  1026,  1029,  1033,  1037,  1040,  1044,  1046,
+  1048,  1050,  1052,  1054,  1056,  1060,  1063,  1065,  1074,  1076,
+  1078,  1087,  1106,  1125
+};
+#endif
+
+
+#if FFDEBUG != 0 || defined (FFERROR_VERBOSE)
+
+static const char * const fftname[] = {   "$","error","$undefined.","BOOLEAN",
+"LONG","DOUBLE","STRING","BITSTR","FUNCTION","BFUNCTION","IFUNCTION","GTIFILTER",
+"REGFILTER","COLUMN","BCOLUMN","SCOLUMN","BITCOL","ROWREF","NULLREF","SNULLREF",
+"','","'='","':'","'{'","'}'","'?'","OR","AND","EQ","NE","'~'","GT","LT","LTE",
+"GTE","'+'","'-'","'%'","'*'","'/'","'|'","'&'","POWER","NOT","INTCAST","FLTCAST",
+"UMINUS","'['","ACCUM","DIFF","'\\n'","']'","'('","')'","lines","line","bvector",
+"vector","expr","bexpr","bits","sexpr", NULL
+};
+#endif
+
+static const short ffr1[] = {     0,
+    54,    54,    55,    55,    55,    55,    55,    55,    56,    56,
+    57,    57,    57,    57,    58,    59,    60,    60,    60,    60,
+    60,    60,    60,    60,    60,    60,    60,    60,    60,    58,
+    58,    58,    58,    58,    58,    58,    58,    58,    58,    58,
+    58,    58,    58,    58,    58,    58,    58,    58,    58,    58,
+    58,    58,    58,    58,    58,    58,    58,    58,    58,    58,
+    58,    58,    58,    58,    58,    58,    59,    59,    59,    59,
+    59,    59,    59,    59,    59,    59,    59,    59,    59,    59,
+    59,    59,    59,    59,    59,    59,    59,    59,    59,    59,
+    59,    59,    59,    59,    59,    59,    59,    59,    59,    59,
+    59,    59,    59,    59,    59,    59,    59,    59,    59,    59,
+    59,    59,    59,    59,    59,    61,    61,    61,    61,    61,
+    61,    61,    61,    61
+};
+
+static const short ffr2[] = {     0,
+     0,     2,     1,     2,     2,     2,     2,     2,     2,     3,
+     2,     3,     3,     3,     2,     2,     1,     1,     4,     3,
+     3,     3,     4,     6,     8,    10,    12,     2,     3,     1,
+     1,     1,     4,     1,     1,     3,     3,     3,     3,     3,
+     3,     2,     2,     3,     3,     3,     5,     5,     5,     2,
+     3,     3,     3,     3,     5,     5,     9,     4,     6,     8,
+    10,    12,     2,     2,     2,     2,     1,     1,     4,     3,
+     3,     3,     3,     3,     3,     3,     3,     3,     3,     3,
+     3,     3,     3,     3,     3,     3,     3,     3,     3,     3,
+     3,     3,     5,     5,     3,     3,     3,     5,     7,    11,
+    15,     2,     3,     5,     9,     3,     7,     9,     4,     6,
+     8,    10,    12,     2,     3,     1,     1,     4,     1,     3,
+     3,     5,     5,     7
+};
+
+static const short ffdefact[] = {     1,
+     0,     0,    67,    30,    31,   116,    17,     0,     0,     0,
+     0,     0,    32,    68,   117,    18,    34,    35,   119,     0,
+     0,     0,     0,     0,     0,     3,     0,     2,     0,     0,
+     0,     0,     0,     0,     8,    50,     0,     0,     0,     0,
+     0,     0,     0,     0,     0,     0,     0,     0,   102,     0,
+     0,     0,     0,     0,    11,     9,     0,    42,    43,   114,
+    28,    63,    64,    65,    66,     0,     0,     0,     0,     0,
+    16,     0,    15,     0,     0,     0,     0,     0,     0,     0,
+     0,     0,     0,     0,     0,     0,     0,     0,     4,     0,
+     0,     0,     0,     0,     0,     0,     5,     0,     0,     0,
+     0,     0,     0,     0,     0,     0,     0,     7,     0,     0,
+     0,     0,     0,     0,     0,     6,     0,    54,     0,    51,
+    53,     0,    52,     0,    95,    96,    97,     0,     0,   103,
+     0,   106,     0,     0,     0,     0,    44,   115,    29,   120,
+    14,    10,    12,    13,     0,    81,    82,    80,    76,    77,
+    79,    78,    37,    38,    36,    39,    45,    40,    41,     0,
+     0,     0,     0,    90,    89,    91,    92,    46,     0,     0,
+     0,    70,    71,    74,    72,    73,    75,    22,    21,    20,
+     0,    83,    84,    85,    87,    88,    86,   121,     0,     0,
+     0,     0,     0,     0,     0,     0,    33,    69,   118,    19,
+     0,     0,    58,     0,     0,     0,     0,   109,    28,     0,
+     0,    23,     0,    56,    98,     0,   123,     0,    55,     0,
+   104,     0,    93,     0,    47,    49,    48,    94,   122,     0,
+     0,     0,     0,     0,     0,     0,     0,    59,     0,   110,
+     0,    24,     0,   124,     0,    99,     0,     0,   107,     0,
+     0,     0,     0,     0,     0,     0,     0,    60,     0,   111,
+     0,    25,    57,     0,   105,   108,     0,     0,     0,     0,
+     0,    61,     0,   112,     0,    26,     0,   100,     0,     0,
+     0,     0,    62,   113,    27,     0,     0,   101,     0,     0
+};
+
+static const short ffdefgoto[] = {     1,
+    28,    29,    30,    45,    46,    43,    57
+};
+
+static const short ffpact[] = {-32768,
+   301,   -41,-32768,-32768,-32768,-32768,-32768,   351,   402,   402,
+    -5,    12,     8,    33,    34,    41,-32768,-32768,-32768,   402,
+   402,   402,   402,   402,   402,-32768,   402,-32768,   -18,     9,
+  1092,   403,  1438,    79,-32768,-32768,   428,   143,   294,    10,
+   456,   224,  1478,   125,  1390,  1436,  1523,    -6,-32768,     2,
+   402,   402,   402,   402,  1390,  1436,  1129,    19,    19,    20,
+    21,    19,    20,    19,    20,   623,   240,   344,  1120,   402,
+-32768,   402,-32768,   402,   402,   402,   402,   402,   402,   402,
+   402,   402,   402,   402,   402,   402,   402,   402,-32768,   402,
+   402,   402,   402,   402,   402,   402,-32768,    -3,    -3,    -3,
+    -3,    -3,    -3,    -3,    -3,    -3,   402,-32768,   402,   402,
+   402,   402,   402,   402,   402,-32768,   402,-32768,   402,-32768,
+-32768,   402,-32768,   402,-32768,-32768,-32768,   402,   402,-32768,
+   402,-32768,  1266,  1286,  1306,  1326,-32768,-32768,-32768,-32768,
+  1390,  1436,  1390,  1436,  1348,  1503,  1503,  1503,    23,    23,
+    23,    23,   160,   160,   160,   -15,    20,   -15,   -15,   732,
+  1370,  1413,  1531,   146,   -13,   -35,   -35,   -15,   756,    -3,
+    -3,   -30,   -30,   -30,   -30,   -30,   -30,    50,    21,    21,
+   780,    67,    67,    11,    11,    11,    11,-32768,   484,  1118,
+  1146,  1415,  1166,  1424,   512,  1186,-32768,-32768,-32768,-32768,
+   402,   402,-32768,   402,   402,   402,   402,-32768,    21,  1480,
+   402,-32768,   402,-32768,-32768,   402,-32768,   402,-32768,    66,
+-32768,   402,  1461,   804,  1461,  1436,  1461,  1436,  1129,   828,
+   852,  1206,   650,   540,    68,   568,   402,-32768,   402,-32768,
+   402,-32768,   402,-32768,   402,-32768,    86,    87,-32768,   876,
+   900,   924,   677,  1226,    52,    56,   402,-32768,   402,-32768,
+   402,-32768,-32768,   402,-32768,-32768,   948,   972,   996,   596,
+   402,-32768,   402,-32768,   402,-32768,   402,-32768,  1020,  1044,
+  1068,  1246,-32768,-32768,-32768,   402,   704,-32768,   126,-32768
+};
+
+static const short ffpgoto[] = {-32768,
+-32768,-32768,-32768,    -1,    95,   124,    27
+};
+
+
+#define	FFLAST		1566
+
+
+static const short fftable[] = {    31,
+    48,    70,    95,     7,   104,    71,    37,    41,    35,   105,
+   106,    96,    16,   129,    93,    94,   107,    50,    55,    58,
+    59,   131,    62,    64,    95,    66,    87,    34,    72,   122,
+    51,    88,    73,    96,    40,    44,    47,   109,   110,   170,
+   111,   112,   113,   114,   115,   115,   130,    49,   171,   133,
+   134,   135,   136,    69,   132,    52,    53,    82,    83,    84,
+    85,    86,   123,    54,    87,    88,    96,   107,   141,    88,
+   143,   235,   145,   146,   147,   148,   149,   150,   151,   152,
+   153,   154,   155,   156,   158,   159,   160,   247,   161,   105,
+   106,   255,   256,   168,   169,    32,   107,   111,   112,   113,
+   114,   115,    38,    42,   265,   181,   109,   110,   266,   111,
+   112,   113,   114,   115,    56,   189,   163,    60,    63,    65,
+   191,    67,   193,     0,    33,   290,     0,   195,   116,   196,
+     0,    39,     0,     0,     0,   182,   183,   184,   185,   186,
+   187,   188,     0,     0,     0,     0,    61,     0,   192,     0,
+    68,     0,   109,   110,   194,   111,   112,   113,   114,   115,
+     0,     0,   119,     0,   142,     0,   144,    90,    91,    92,
+    93,    94,    92,    93,    94,     0,     0,   127,     0,   157,
+    95,     0,     0,    95,   162,   164,   165,   166,   167,    96,
+     0,     0,    96,     0,     0,   120,     0,    85,    86,   223,
+   224,    87,   225,   227,     0,   230,    88,     0,     0,   231,
+     0,   232,     0,   190,   233,     0,   234,     0,     0,     0,
+   236,   172,   173,   174,   175,   176,   177,   178,   179,   180,
+     0,     0,   229,     0,     0,   250,     0,   251,     0,   252,
+     0,   253,     0,   254,     0,     0,     0,     0,    90,    91,
+    92,    93,    94,     0,     0,   267,     0,   268,     0,   269,
+     0,    95,   270,     0,    90,    91,    92,    93,    94,   279,
+    96,   280,     0,   281,     0,   282,   126,    95,     0,     0,
+     0,     0,     0,     0,   287,     0,    96,     0,     0,     0,
+     0,     0,   138,   209,   210,     0,     0,     0,   226,   228,
+   289,     2,     0,     3,     4,     5,     6,     7,     8,     9,
+    10,    11,    12,    13,    14,    15,    16,    17,    18,    19,
+     0,    98,    99,    20,   100,   101,   102,   103,   104,     0,
+     0,     0,     0,   105,   106,    21,    22,     0,     0,     0,
+   107,     0,     0,    23,    24,    25,   121,     0,     0,     0,
+    26,     0,    27,     3,     4,     5,     6,     7,     8,     9,
+    10,    11,    12,    13,    14,    15,    16,    17,    18,    19,
+     0,    98,    99,    20,   100,   101,   102,   103,   104,     0,
+     0,     0,     0,   105,   106,    21,    22,     0,     0,     0,
+   107,     0,     0,    23,    24,    25,   139,     0,     0,     0,
+     0,     0,    27,    36,     3,     4,     5,     6,     7,     8,
+     9,    10,    11,    12,    13,    14,    15,    16,    17,    18,
+    19,     0,     0,     0,    20,     0,     0,    90,    91,    92,
+    93,    94,     0,     0,     0,     0,    21,    22,     0,     0,
+    95,     0,     0,     0,    23,    24,    25,   117,    74,    96,
+     0,     0,    97,    27,     0,    75,    76,    77,    78,    79,
+    80,    81,    82,    83,    84,    85,    86,     0,     0,    87,
+     0,     0,     0,     0,    88,   124,    74,     0,     0,     0,
+   118,     0,     0,    75,    76,    77,    78,    79,    80,    81,
+    82,    83,    84,    85,    86,     0,     0,    87,     0,     0,
+     0,     0,    88,   213,    74,     0,     0,     0,   125,     0,
+     0,    75,    76,    77,    78,    79,    80,    81,    82,    83,
+    84,    85,    86,     0,     0,    87,     0,     0,     0,     0,
+    88,   220,    74,     0,     0,     0,   214,     0,     0,    75,
+    76,    77,    78,    79,    80,    81,    82,    83,    84,    85,
+    86,     0,     0,    87,     0,     0,     0,     0,    88,   245,
+    74,     0,     0,     0,   221,     0,     0,    75,    76,    77,
+    78,    79,    80,    81,    82,    83,    84,    85,    86,     0,
+     0,    87,     0,     0,     0,     0,    88,   248,    74,     0,
+     0,     0,   246,     0,     0,    75,    76,    77,    78,    79,
+    80,    81,    82,    83,    84,    85,    86,     0,     0,    87,
+     0,     0,     0,     0,    88,   277,    74,     0,     0,     0,
+   249,     0,     0,    75,    76,    77,    78,    79,    80,    81,
+    82,    83,    84,    85,    86,     0,     0,    87,     0,     0,
+     0,     0,    88,    74,     0,     0,     0,     0,   278,     0,
+    75,    76,    77,    78,    79,    80,    81,    82,    83,    84,
+    85,    86,     0,     0,    87,     0,     0,     0,     0,    88,
+    74,     0,     0,     0,     0,   137,     0,    75,    76,    77,
+    78,    79,    80,    81,    82,    83,    84,    85,    86,     0,
+     0,    87,     0,     0,     0,     0,    88,    74,     0,     0,
+     0,     0,   244,     0,    75,    76,    77,    78,    79,    80,
+    81,    82,    83,    84,    85,    86,     0,     0,    87,     0,
+     0,     0,     0,    88,    74,     0,     0,     0,     0,   263,
+     0,    75,    76,    77,    78,    79,    80,    81,    82,    83,
+    84,    85,    86,     0,     0,    87,     0,     0,     0,     0,
+    88,   202,    74,     0,     0,     0,   288,     0,     0,    75,
+    76,    77,    78,    79,    80,    81,    82,    83,    84,    85,
+    86,     0,     0,    87,     0,   207,    74,     0,    88,     0,
+     0,     0,   203,    75,    76,    77,    78,    79,    80,    81,
+    82,    83,    84,    85,    86,     0,     0,    87,     0,   211,
+    74,     0,    88,     0,     0,     0,   208,    75,    76,    77,
+    78,    79,    80,    81,    82,    83,    84,    85,    86,     0,
+     0,    87,     0,   237,    74,     0,    88,     0,     0,     0,
+   212,    75,    76,    77,    78,    79,    80,    81,    82,    83,
+    84,    85,    86,     0,     0,    87,     0,   239,    74,     0,
+    88,     0,     0,     0,   238,    75,    76,    77,    78,    79,
+    80,    81,    82,    83,    84,    85,    86,     0,     0,    87,
+     0,   241,    74,     0,    88,     0,     0,     0,   240,    75,
+    76,    77,    78,    79,    80,    81,    82,    83,    84,    85,
+    86,     0,     0,    87,     0,   257,    74,     0,    88,     0,
+     0,     0,   242,    75,    76,    77,    78,    79,    80,    81,
+    82,    83,    84,    85,    86,     0,     0,    87,     0,   259,
+    74,     0,    88,     0,     0,     0,   258,    75,    76,    77,
+    78,    79,    80,    81,    82,    83,    84,    85,    86,     0,
+     0,    87,     0,   261,    74,     0,    88,     0,     0,     0,
+   260,    75,    76,    77,    78,    79,    80,    81,    82,    83,
+    84,    85,    86,     0,     0,    87,     0,   271,    74,     0,
+    88,     0,     0,     0,   262,    75,    76,    77,    78,    79,
+    80,    81,    82,    83,    84,    85,    86,     0,     0,    87,
+     0,   273,    74,     0,    88,     0,     0,     0,   272,    75,
+    76,    77,    78,    79,    80,    81,    82,    83,    84,    85,
+    86,     0,     0,    87,     0,   275,    74,     0,    88,     0,
+     0,     0,   274,    75,    76,    77,    78,    79,    80,    81,
+    82,    83,    84,    85,    86,     0,     0,    87,     0,     0,
+    74,     0,    88,     0,     0,     0,   276,    75,    76,    77,
+    78,    79,    80,    81,    82,    83,    84,    85,    86,     0,
+     0,    87,     0,     0,    74,     0,    88,     0,     0,     0,
+   283,    75,    76,    77,    78,    79,    80,    81,    82,    83,
+    84,    85,    86,     0,     0,    87,     0,     0,    74,     0,
+    88,     0,     0,     0,   284,    75,    76,    77,    78,    79,
+    80,    81,    82,    83,    84,    85,    86,     0,     0,    87,
+     0,     0,    74,     0,    88,     0,     0,     0,   285,    75,
+    76,    77,    78,    79,    80,    81,    82,    83,    84,    85,
+    86,     0,     0,    87,     0,     0,     0,     0,    88,     0,
+     0,    89,    90,    91,    92,    93,    94,   109,   110,     0,
+   111,   112,   113,   114,   115,    95,   109,   110,     0,   111,
+   112,   113,   114,   115,    96,   216,    74,     0,     0,     0,
+   215,     0,   140,    75,    76,    77,    78,    79,    80,    81,
+    82,    83,    84,    85,    86,   218,    74,    87,     0,     0,
+     0,     0,    88,    75,    76,    77,    78,    79,    80,    81,
+    82,    83,    84,    85,    86,   222,    74,    87,     0,     0,
+     0,     0,    88,    75,    76,    77,    78,    79,    80,    81,
+    82,    83,    84,    85,    86,   243,    74,    87,     0,     0,
+     0,     0,    88,    75,    76,    77,    78,    79,    80,    81,
+    82,    83,    84,    85,    86,   264,    74,    87,     0,     0,
+     0,     0,    88,    75,    76,    77,    78,    79,    80,    81,
+    82,    83,    84,    85,    86,   286,    74,    87,     0,     0,
+     0,     0,    88,    75,    76,    77,    78,    79,    80,    81,
+    82,    83,    84,    85,    86,     0,    74,    87,     0,   197,
+     0,     0,    88,    75,    76,    77,    78,    79,    80,    81,
+    82,    83,    84,    85,    86,     0,    74,    87,     0,   198,
+     0,     0,    88,    75,    76,    77,    78,    79,    80,    81,
+    82,    83,    84,    85,    86,     0,    74,    87,     0,   199,
+     0,     0,    88,    75,    76,    77,    78,    79,    80,    81,
+    82,    83,    84,    85,    86,     0,    74,    87,     0,   200,
+     0,     0,    88,    75,    76,    77,    78,    79,    80,    81,
+    82,    83,    84,    85,    86,     0,     0,    87,    74,   201,
+     0,     0,    88,     0,     0,    75,    76,    77,    78,    79,
+    80,    81,    82,    83,    84,    85,    86,     0,     0,    87,
+    74,   204,     0,     0,    88,     0,     0,    75,    76,    77,
+    78,    79,    80,    81,    82,    83,    84,    85,    86,     0,
+    74,    87,     0,     0,     0,     0,    88,    75,    76,    77,
+    78,    79,    80,    81,    82,    83,    84,    85,    86,     0,
+     0,    87,     0,     0,   205,     0,    88,    90,    91,    92,
+    93,    94,   109,   110,     0,   111,   112,   113,   114,   115,
+    95,   109,   110,     0,   111,   112,   113,   114,   115,    96,
+    90,    91,    92,    93,    94,    98,    99,   217,   100,   101,
+   102,   103,   104,    95,     0,     0,   219,   105,   106,     0,
+     0,     0,    96,     0,   107,     0,     0,   108,    75,    76,
+    77,    78,    79,    80,    81,    82,    83,    84,    85,    86,
+     0,     0,    87,     0,     0,    98,    99,    88,   100,   101,
+   102,   103,   104,     0,   104,     0,     0,   105,   106,   105,
+   106,     0,     0,     0,   107,     0,   107,     0,     0,     0,
+     0,     0,   139,    78,    79,    80,    81,    82,    83,    84,
+    85,    86,   128,     0,    87,     0,     0,     0,     0,    88,
+   109,   110,   206,   111,   112,   113,   114,   115,   109,   110,
+     0,   111,   112,   113,   114,   115
+};
+
+static const short ffcheck[] = {     1,
+     6,    20,    38,     7,    35,    24,     8,     9,    50,    40,
+    41,    47,    16,    20,    28,    29,    47,     6,    20,    21,
+    22,    20,    24,    25,    38,    27,    42,     1,    20,    20,
+    23,    47,    24,    47,     8,     9,    10,    28,    29,    43,
+    31,    32,    33,    34,    35,    35,    53,    53,    52,    51,
+    52,    53,    54,    27,    53,    23,    23,    35,    36,    37,
+    38,    39,    53,    23,    42,    47,    47,    47,    70,    47,
+    72,     6,    74,    75,    76,    77,    78,    79,    80,    81,
+    82,    83,    84,    85,    86,    87,    88,    20,    90,    40,
+    41,     6,     6,    95,    96,     1,    47,    31,    32,    33,
+    34,    35,     8,     9,    53,   107,    28,    29,    53,    31,
+    32,    33,    34,    35,    20,   117,    90,    23,    24,    25,
+   122,    27,   124,    -1,     1,     0,    -1,   129,    50,   131,
+    -1,     8,    -1,    -1,    -1,   109,   110,   111,   112,   113,
+   114,   115,    -1,    -1,    -1,    -1,    23,    -1,   122,    -1,
+    27,    -1,    28,    29,   128,    31,    32,    33,    34,    35,
+    -1,    -1,    20,    -1,    70,    -1,    72,    25,    26,    27,
+    28,    29,    27,    28,    29,    -1,    -1,    53,    -1,    85,
+    38,    -1,    -1,    38,    90,    91,    92,    93,    94,    47,
+    -1,    -1,    47,    -1,    -1,    53,    -1,    38,    39,   201,
+   202,    42,   204,   205,    -1,   207,    47,    -1,    -1,   211,
+    -1,   213,    -1,   119,   216,    -1,   218,    -1,    -1,    -1,
+   222,    98,    99,   100,   101,   102,   103,   104,   105,   106,
+    -1,    -1,   206,    -1,    -1,   237,    -1,   239,    -1,   241,
+    -1,   243,    -1,   245,    -1,    -1,    -1,    -1,    25,    26,
+    27,    28,    29,    -1,    -1,   257,    -1,   259,    -1,   261,
+    -1,    38,   264,    -1,    25,    26,    27,    28,    29,   271,
+    47,   273,    -1,   275,    -1,   277,    53,    38,    -1,    -1,
+    -1,    -1,    -1,    -1,   286,    -1,    47,    -1,    -1,    -1,
+    -1,    -1,    53,   170,   171,    -1,    -1,    -1,   204,   205,
+     0,     1,    -1,     3,     4,     5,     6,     7,     8,     9,
+    10,    11,    12,    13,    14,    15,    16,    17,    18,    19,
+    -1,    28,    29,    23,    31,    32,    33,    34,    35,    -1,
+    -1,    -1,    -1,    40,    41,    35,    36,    -1,    -1,    -1,
+    47,    -1,    -1,    43,    44,    45,    53,    -1,    -1,    -1,
+    50,    -1,    52,     3,     4,     5,     6,     7,     8,     9,
+    10,    11,    12,    13,    14,    15,    16,    17,    18,    19,
+    -1,    28,    29,    23,    31,    32,    33,    34,    35,    -1,
+    -1,    -1,    -1,    40,    41,    35,    36,    -1,    -1,    -1,
+    47,    -1,    -1,    43,    44,    45,    53,    -1,    -1,    -1,
+    -1,    -1,    52,    53,     3,     4,     5,     6,     7,     8,
+     9,    10,    11,    12,    13,    14,    15,    16,    17,    18,
+    19,    -1,    -1,    -1,    23,    -1,    -1,    25,    26,    27,
+    28,    29,    -1,    -1,    -1,    -1,    35,    36,    -1,    -1,
+    38,    -1,    -1,    -1,    43,    44,    45,    20,    21,    47,
+    -1,    -1,    50,    52,    -1,    28,    29,    30,    31,    32,
+    33,    34,    35,    36,    37,    38,    39,    -1,    -1,    42,
+    -1,    -1,    -1,    -1,    47,    20,    21,    -1,    -1,    -1,
+    53,    -1,    -1,    28,    29,    30,    31,    32,    33,    34,
+    35,    36,    37,    38,    39,    -1,    -1,    42,    -1,    -1,
+    -1,    -1,    47,    20,    21,    -1,    -1,    -1,    53,    -1,
+    -1,    28,    29,    30,    31,    32,    33,    34,    35,    36,
+    37,    38,    39,    -1,    -1,    42,    -1,    -1,    -1,    -1,
+    47,    20,    21,    -1,    -1,    -1,    53,    -1,    -1,    28,
+    29,    30,    31,    32,    33,    34,    35,    36,    37,    38,
+    39,    -1,    -1,    42,    -1,    -1,    -1,    -1,    47,    20,
+    21,    -1,    -1,    -1,    53,    -1,    -1,    28,    29,    30,
+    31,    32,    33,    34,    35,    36,    37,    38,    39,    -1,
+    -1,    42,    -1,    -1,    -1,    -1,    47,    20,    21,    -1,
+    -1,    -1,    53,    -1,    -1,    28,    29,    30,    31,    32,
+    33,    34,    35,    36,    37,    38,    39,    -1,    -1,    42,
+    -1,    -1,    -1,    -1,    47,    20,    21,    -1,    -1,    -1,
+    53,    -1,    -1,    28,    29,    30,    31,    32,    33,    34,
+    35,    36,    37,    38,    39,    -1,    -1,    42,    -1,    -1,
+    -1,    -1,    47,    21,    -1,    -1,    -1,    -1,    53,    -1,
+    28,    29,    30,    31,    32,    33,    34,    35,    36,    37,
+    38,    39,    -1,    -1,    42,    -1,    -1,    -1,    -1,    47,
+    21,    -1,    -1,    -1,    -1,    53,    -1,    28,    29,    30,
+    31,    32,    33,    34,    35,    36,    37,    38,    39,    -1,
+    -1,    42,    -1,    -1,    -1,    -1,    47,    21,    -1,    -1,
+    -1,    -1,    53,    -1,    28,    29,    30,    31,    32,    33,
+    34,    35,    36,    37,    38,    39,    -1,    -1,    42,    -1,
+    -1,    -1,    -1,    47,    21,    -1,    -1,    -1,    -1,    53,
+    -1,    28,    29,    30,    31,    32,    33,    34,    35,    36,
+    37,    38,    39,    -1,    -1,    42,    -1,    -1,    -1,    -1,
+    47,    20,    21,    -1,    -1,    -1,    53,    -1,    -1,    28,
+    29,    30,    31,    32,    33,    34,    35,    36,    37,    38,
+    39,    -1,    -1,    42,    -1,    20,    21,    -1,    47,    -1,
+    -1,    -1,    51,    28,    29,    30,    31,    32,    33,    34,
+    35,    36,    37,    38,    39,    -1,    -1,    42,    -1,    20,
+    21,    -1,    47,    -1,    -1,    -1,    51,    28,    29,    30,
+    31,    32,    33,    34,    35,    36,    37,    38,    39,    -1,
+    -1,    42,    -1,    20,    21,    -1,    47,    -1,    -1,    -1,
+    51,    28,    29,    30,    31,    32,    33,    34,    35,    36,
+    37,    38,    39,    -1,    -1,    42,    -1,    20,    21,    -1,
+    47,    -1,    -1,    -1,    51,    28,    29,    30,    31,    32,
+    33,    34,    35,    36,    37,    38,    39,    -1,    -1,    42,
+    -1,    20,    21,    -1,    47,    -1,    -1,    -1,    51,    28,
+    29,    30,    31,    32,    33,    34,    35,    36,    37,    38,
+    39,    -1,    -1,    42,    -1,    20,    21,    -1,    47,    -1,
+    -1,    -1,    51,    28,    29,    30,    31,    32,    33,    34,
+    35,    36,    37,    38,    39,    -1,    -1,    42,    -1,    20,
+    21,    -1,    47,    -1,    -1,    -1,    51,    28,    29,    30,
+    31,    32,    33,    34,    35,    36,    37,    38,    39,    -1,
+    -1,    42,    -1,    20,    21,    -1,    47,    -1,    -1,    -1,
+    51,    28,    29,    30,    31,    32,    33,    34,    35,    36,
+    37,    38,    39,    -1,    -1,    42,    -1,    20,    21,    -1,
+    47,    -1,    -1,    -1,    51,    28,    29,    30,    31,    32,
+    33,    34,    35,    36,    37,    38,    39,    -1,    -1,    42,
+    -1,    20,    21,    -1,    47,    -1,    -1,    -1,    51,    28,
+    29,    30,    31,    32,    33,    34,    35,    36,    37,    38,
+    39,    -1,    -1,    42,    -1,    20,    21,    -1,    47,    -1,
+    -1,    -1,    51,    28,    29,    30,    31,    32,    33,    34,
+    35,    36,    37,    38,    39,    -1,    -1,    42,    -1,    -1,
+    21,    -1,    47,    -1,    -1,    -1,    51,    28,    29,    30,
+    31,    32,    33,    34,    35,    36,    37,    38,    39,    -1,
+    -1,    42,    -1,    -1,    21,    -1,    47,    -1,    -1,    -1,
+    51,    28,    29,    30,    31,    32,    33,    34,    35,    36,
+    37,    38,    39,    -1,    -1,    42,    -1,    -1,    21,    -1,
+    47,    -1,    -1,    -1,    51,    28,    29,    30,    31,    32,
+    33,    34,    35,    36,    37,    38,    39,    -1,    -1,    42,
+    -1,    -1,    21,    -1,    47,    -1,    -1,    -1,    51,    28,
+    29,    30,    31,    32,    33,    34,    35,    36,    37,    38,
+    39,    -1,    -1,    42,    -1,    -1,    -1,    -1,    47,    -1,
+    -1,    50,    25,    26,    27,    28,    29,    28,    29,    -1,
+    31,    32,    33,    34,    35,    38,    28,    29,    -1,    31,
+    32,    33,    34,    35,    47,    20,    21,    -1,    -1,    -1,
+    53,    -1,    53,    28,    29,    30,    31,    32,    33,    34,
+    35,    36,    37,    38,    39,    20,    21,    42,    -1,    -1,
+    -1,    -1,    47,    28,    29,    30,    31,    32,    33,    34,
+    35,    36,    37,    38,    39,    20,    21,    42,    -1,    -1,
+    -1,    -1,    47,    28,    29,    30,    31,    32,    33,    34,
+    35,    36,    37,    38,    39,    20,    21,    42,    -1,    -1,
+    -1,    -1,    47,    28,    29,    30,    31,    32,    33,    34,
+    35,    36,    37,    38,    39,    20,    21,    42,    -1,    -1,
+    -1,    -1,    47,    28,    29,    30,    31,    32,    33,    34,
+    35,    36,    37,    38,    39,    20,    21,    42,    -1,    -1,
+    -1,    -1,    47,    28,    29,    30,    31,    32,    33,    34,
+    35,    36,    37,    38,    39,    -1,    21,    42,    -1,    24,
+    -1,    -1,    47,    28,    29,    30,    31,    32,    33,    34,
+    35,    36,    37,    38,    39,    -1,    21,    42,    -1,    24,
+    -1,    -1,    47,    28,    29,    30,    31,    32,    33,    34,
+    35,    36,    37,    38,    39,    -1,    21,    42,    -1,    24,
+    -1,    -1,    47,    28,    29,    30,    31,    32,    33,    34,
+    35,    36,    37,    38,    39,    -1,    21,    42,    -1,    24,
+    -1,    -1,    47,    28,    29,    30,    31,    32,    33,    34,
+    35,    36,    37,    38,    39,    -1,    -1,    42,    21,    22,
+    -1,    -1,    47,    -1,    -1,    28,    29,    30,    31,    32,
+    33,    34,    35,    36,    37,    38,    39,    -1,    -1,    42,
+    21,    22,    -1,    -1,    47,    -1,    -1,    28,    29,    30,
+    31,    32,    33,    34,    35,    36,    37,    38,    39,    -1,
+    21,    42,    -1,    -1,    -1,    -1,    47,    28,    29,    30,
+    31,    32,    33,    34,    35,    36,    37,    38,    39,    -1,
+    -1,    42,    -1,    -1,    22,    -1,    47,    25,    26,    27,
+    28,    29,    28,    29,    -1,    31,    32,    33,    34,    35,
+    38,    28,    29,    -1,    31,    32,    33,    34,    35,    47,
+    25,    26,    27,    28,    29,    28,    29,    53,    31,    32,
+    33,    34,    35,    38,    -1,    -1,    53,    40,    41,    -1,
+    -1,    -1,    47,    -1,    47,    -1,    -1,    50,    28,    29,
+    30,    31,    32,    33,    34,    35,    36,    37,    38,    39,
+    -1,    -1,    42,    -1,    -1,    28,    29,    47,    31,    32,
+    33,    34,    35,    -1,    35,    -1,    -1,    40,    41,    40,
+    41,    -1,    -1,    -1,    47,    -1,    47,    -1,    -1,    -1,
+    -1,    -1,    53,    31,    32,    33,    34,    35,    36,    37,
+    38,    39,    20,    -1,    42,    -1,    -1,    -1,    -1,    47,
+    28,    29,    22,    31,    32,    33,    34,    35,    28,    29,
+    -1,    31,    32,    33,    34,    35
+};
+/* -*-C-*-  Note some compilers choke on comments on `#line' lines.  */
+#line 3 "/usr1/local/share/bison.simple"
+
+/* Skeleton output parser for bison,
+   Copyright (C) 1984, 1989, 1990 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+/* As a special exception, when this file is copied by Bison into a
+   Bison output file, you may use that output file without restriction.
+   This special exception was added by the Free Software Foundation
+   in version 1.24 of Bison.  */
+
+#ifndef alloca
+#ifdef __GNUC__
+#define alloca __builtin_alloca
+#else /* not GNU C.  */
+#if (!defined (__STDC__) && defined (sparc)) || defined (__sparc__) || defined (__sparc) || defined (__sgi)
+#include <alloca.h>
+#else /* not sparc */
+#if defined (MSDOS) && !defined (__TURBOC__)
+#include <malloc.h>
+#else /* not MSDOS, or __TURBOC__ */
+#if defined(_AIX)
+#include <malloc.h>
+ #pragma alloca
+#else /* not MSDOS, __TURBOC__, or _AIX */
+#ifdef __hpux
+#ifdef __cplusplus
+extern "C" {
+void *alloca (unsigned int);
+};
+#else /* not __cplusplus */
+void *alloca ();
+#endif /* not __cplusplus */
+#endif /* __hpux */
+#endif /* not _AIX */
+#endif /* not MSDOS, or __TURBOC__ */
+#endif /* not sparc.  */
+#endif /* not GNU C.  */
+#endif /* alloca not defined.  */
+
+/* This is the parser code that is written into each bison parser
+  when the %semantic_parser declaration is not specified in the grammar.
+  It was written by Richard Stallman by simplifying the hairy parser
+  used when %semantic_parser is specified.  */
+
+/* Note: there must be only one dollar sign in this file.
+   It is replaced by the list of actions, each action
+   as one case of the switch.  */
+
+#define fferrok		(fferrstatus = 0)
+#define ffclearin	(ffchar = FFEMPTY)
+#define FFEMPTY		-2
+#define FFEOF		0
+#define FFACCEPT	return(0)
+#define FFABORT 	return(1)
+#define FFERROR		goto fferrlab1
+/* Like FFERROR except do call fferror.
+   This remains here temporarily to ease the
+   transition to the new meaning of FFERROR, for GCC.
+   Once GCC version 2 has supplanted version 1, this can go.  */
+#define FFFAIL		goto fferrlab
+#define FFRECOVERING()  (!!fferrstatus)
+#define FFBACKUP(token, value) \
+do								\
+  if (ffchar == FFEMPTY && fflen == 1)				\
+    { ffchar = (token), fflval = (value);			\
+      ffchar1 = FFTRANSLATE (ffchar);				\
+      FFPOPSTACK;						\
+      goto ffbackup;						\
+    }								\
+  else								\
+    { fferror ("syntax error: cannot back up"); FFERROR; }	\
+while (0)
+
+#define FFTERROR	1
+#define FFERRCODE	256
+
+#ifndef FFPURE
+#define FFLEX		fflex()
+#endif
+
+#ifdef FFPURE
+#ifdef FFLSP_NEEDED
+#ifdef FFLEX_PARAM
+#define FFLEX		fflex(&fflval, &fflloc, FFLEX_PARAM)
+#else
+#define FFLEX		fflex(&fflval, &fflloc)
+#endif
+#else /* not FFLSP_NEEDED */
+#ifdef FFLEX_PARAM
+#define FFLEX		fflex(&fflval, FFLEX_PARAM)
+#else
+#define FFLEX		fflex(&fflval)
+#endif
+#endif /* not FFLSP_NEEDED */
+#endif
+
+/* If nonreentrant, generate the variables here */
+
+#ifndef FFPURE
+
+int	ffchar;			/*  the lookahead symbol		*/
+FFSTYPE	fflval;			/*  the semantic value of the		*/
+				/*  lookahead symbol			*/
+
+#ifdef FFLSP_NEEDED
+FFLTYPE fflloc;			/*  location data for the lookahead	*/
+				/*  symbol				*/
+#endif
+
+int ffnerrs;			/*  number of parse errors so far       */
+#endif  /* not FFPURE */
+
+#if FFDEBUG != 0
+int ffdebug;			/*  nonzero means print parse trace	*/
+/* Since this is uninitialized, it does not stop multiple parsers
+   from coexisting.  */
+#endif
+
+/*  FFINITDEPTH indicates the initial size of the parser's stacks	*/
+
+#ifndef	FFINITDEPTH
+#define FFINITDEPTH 200
+#endif
+
+/*  FFMAXDEPTH is the maximum size the stacks can grow to
+    (effective only if the built-in stack extension method is used).  */
+
+#if FFMAXDEPTH == 0
+#undef FFMAXDEPTH
+#endif
+
+#ifndef FFMAXDEPTH
+#define FFMAXDEPTH 10000
+#endif
+
+/* Prevent warning if -Wstrict-prototypes.  */
+#ifdef __GNUC__
+int ffparse (void);
+#endif
+
+#if __GNUC__ > 1		/* GNU C and GNU C++ define this.  */
+#define __ff_memcpy(TO,FROM,COUNT)	__builtin_memcpy(TO,FROM,COUNT)
+#else				/* not GNU C or C++ */
+#ifndef __cplusplus
+
+/* This is the most reliable way to avoid incompatibilities
+   in available built-in functions on various systems.  */
+static void
+__ff_memcpy (to, from, count)
+     char *to;
+     char *from;
+     int count;
+{
+  register char *f = from;
+  register char *t = to;
+  register int i = count;
+
+  while (i-- > 0)
+    *t++ = *f++;
+}
+
+#else /* __cplusplus */
+
+/* This is the most reliable way to avoid incompatibilities
+   in available built-in functions on various systems.  */
+static void
+__ff_memcpy (char *to, char *from, int count)
+{
+  register char *f = from;
+  register char *t = to;
+  register int i = count;
+
+  while (i-- > 0)
+    *t++ = *f++;
+}
+
+#endif
+#endif
+
+#line 196 "/usr1/local/share/bison.simple"
+
+/* The user can define FFPARSE_PARAM as the name of an argument to be passed
+   into ffparse.  The argument should have type void *.
+   It should actually point to an object.
+   Grammar actions can access the variable by casting it
+   to the proper pointer type.  */
+
+#ifdef FFPARSE_PARAM
+#ifdef __cplusplus
+#define FFPARSE_PARAM_ARG void *FFPARSE_PARAM
+#define FFPARSE_PARAM_DECL
+#else /* not __cplusplus */
+#define FFPARSE_PARAM_ARG FFPARSE_PARAM
+#define FFPARSE_PARAM_DECL void *FFPARSE_PARAM;
+#endif /* not __cplusplus */
+#else /* not FFPARSE_PARAM */
+#define FFPARSE_PARAM_ARG
+#define FFPARSE_PARAM_DECL
+#endif /* not FFPARSE_PARAM */
+
+int
+ffparse(FFPARSE_PARAM_ARG)
+     FFPARSE_PARAM_DECL
+{
+  register int ffstate;
+  register int ffn;
+  register short *ffssp;
+  register FFSTYPE *ffvsp;
+  int fferrstatus;	/*  number of tokens to shift before error messages enabled */
+  int ffchar1 = 0;		/*  lookahead token as an internal (translated) token number */
+
+  short	ffssa[FFINITDEPTH];	/*  the state stack			*/
+  FFSTYPE ffvsa[FFINITDEPTH];	/*  the semantic value stack		*/
+
+  short *ffss = ffssa;		/*  refer to the stacks thru separate pointers */
+  FFSTYPE *ffvs = ffvsa;	/*  to allow ffoverflow to reallocate them elsewhere */
+
+#ifdef FFLSP_NEEDED
+  FFLTYPE fflsa[FFINITDEPTH];	/*  the location stack			*/
+  FFLTYPE *ffls = fflsa;
+  FFLTYPE *fflsp;
+
+#define FFPOPSTACK   (ffvsp--, ffssp--, fflsp--)
+#else
+#define FFPOPSTACK   (ffvsp--, ffssp--)
+#endif
+
+  int ffstacksize = FFINITDEPTH;
+
+#ifdef FFPURE
+  int ffchar;
+  FFSTYPE fflval;
+  int ffnerrs;
+#ifdef FFLSP_NEEDED
+  FFLTYPE fflloc;
+#endif
+#endif
+
+  FFSTYPE ffval;		/*  the variable used to return		*/
+				/*  semantic values from the action	*/
+				/*  routines				*/
+
+  int fflen;
+
+#if FFDEBUG != 0
+  if (ffdebug)
+    fprintf(stderr, "Starting parse\n");
+#endif
+
+  ffstate = 0;
+  fferrstatus = 0;
+  ffnerrs = 0;
+  ffchar = FFEMPTY;		/* Cause a token to be read.  */
+
+  /* Initialize stack pointers.
+     Waste one element of value and location stack
+     so that they stay on the same level as the state stack.
+     The wasted elements are never initialized.  */
+
+  ffssp = ffss - 1;
+  ffvsp = ffvs;
+#ifdef FFLSP_NEEDED
+  fflsp = ffls;
+#endif
+
+/* Push a new state, which is found in  ffstate  .  */
+/* In all cases, when you get here, the value and location stacks
+   have just been pushed. so pushing a state here evens the stacks.  */
+ffnewstate:
+
+  *++ffssp = ffstate;
+
+  if (ffssp >= ffss + ffstacksize - 1)
+    {
+      /* Give user a chance to reallocate the stack */
+      /* Use copies of these so that the &'s don't force the real ones into memory. */
+      FFSTYPE *ffvs1 = ffvs;
+      short *ffss1 = ffss;
+#ifdef FFLSP_NEEDED
+      FFLTYPE *ffls1 = ffls;
+#endif
+
+      /* Get the current used size of the three stacks, in elements.  */
+      int size = ffssp - ffss + 1;
+
+#ifdef ffoverflow
+      /* Each stack pointer address is followed by the size of
+	 the data in use in that stack, in bytes.  */
+#ifdef FFLSP_NEEDED
+      /* This used to be a conditional around just the two extra args,
+	 but that might be undefined if ffoverflow is a macro.  */
+      ffoverflow("parser stack overflow",
+		 &ffss1, size * sizeof (*ffssp),
+		 &ffvs1, size * sizeof (*ffvsp),
+		 &ffls1, size * sizeof (*fflsp),
+		 &ffstacksize);
+#else
+      ffoverflow("parser stack overflow",
+		 &ffss1, size * sizeof (*ffssp),
+		 &ffvs1, size * sizeof (*ffvsp),
+		 &ffstacksize);
+#endif
+
+      ffss = ffss1; ffvs = ffvs1;
+#ifdef FFLSP_NEEDED
+      ffls = ffls1;
+#endif
+#else /* no ffoverflow */
+      /* Extend the stack our own way.  */
+      if (ffstacksize >= FFMAXDEPTH)
+	{
+	  fferror("parser stack overflow");
+	  return 2;
+	}
+      ffstacksize *= 2;
+      if (ffstacksize > FFMAXDEPTH)
+	ffstacksize = FFMAXDEPTH;
+      ffss = (short *) alloca (ffstacksize * sizeof (*ffssp));
+      __ff_memcpy ((char *)ffss, (char *)ffss1, size * sizeof (*ffssp));
+      ffvs = (FFSTYPE *) alloca (ffstacksize * sizeof (*ffvsp));
+      __ff_memcpy ((char *)ffvs, (char *)ffvs1, size * sizeof (*ffvsp));
+#ifdef FFLSP_NEEDED
+      ffls = (FFLTYPE *) alloca (ffstacksize * sizeof (*fflsp));
+      __ff_memcpy ((char *)ffls, (char *)ffls1, size * sizeof (*fflsp));
+#endif
+#endif /* no ffoverflow */
+
+      ffssp = ffss + size - 1;
+      ffvsp = ffvs + size - 1;
+#ifdef FFLSP_NEEDED
+      fflsp = ffls + size - 1;
+#endif
+
+#if FFDEBUG != 0
+      if (ffdebug)
+	fprintf(stderr, "Stack size increased to %d\n", ffstacksize);
+#endif
+
+      if (ffssp >= ffss + ffstacksize - 1)
+	FFABORT;
+    }
+
+#if FFDEBUG != 0
+  if (ffdebug)
+    fprintf(stderr, "Entering state %d\n", ffstate);
+#endif
+
+  goto ffbackup;
+ ffbackup:
+
+/* Do appropriate processing given the current state.  */
+/* Read a lookahead token if we need one and don't already have one.  */
+/* ffresume: */
+
+  /* First try to decide what to do without reference to lookahead token.  */
+
+  ffn = ffpact[ffstate];
+  if (ffn == FFFLAG)
+    goto ffdefault;
+
+  /* Not known => get a lookahead token if don't already have one.  */
+
+  /* ffchar is either FFEMPTY or FFEOF
+     or a valid token in external form.  */
+
+  if (ffchar == FFEMPTY)
+    {
+#if FFDEBUG != 0
+      if (ffdebug)
+	fprintf(stderr, "Reading a token: ");
+#endif
+      ffchar = FFLEX;
+    }
+
+  /* Convert token to internal form (in ffchar1) for indexing tables with */
+
+  if (ffchar <= 0)		/* This means end of input. */
+    {
+      ffchar1 = 0;
+      ffchar = FFEOF;		/* Don't call FFLEX any more */
+
+#if FFDEBUG != 0
+      if (ffdebug)
+	fprintf(stderr, "Now at end of input.\n");
+#endif
+    }
+  else
+    {
+      ffchar1 = FFTRANSLATE(ffchar);
+
+#if FFDEBUG != 0
+      if (ffdebug)
+	{
+	  fprintf (stderr, "Next token is %d (%s", ffchar, fftname[ffchar1]);
+	  /* Give the individual parser a way to print the precise meaning
+	     of a token, for further debugging info.  */
+#ifdef FFPRINT
+	  FFPRINT (stderr, ffchar, fflval);
+#endif
+	  fprintf (stderr, ")\n");
+	}
+#endif
+    }
+
+  ffn += ffchar1;
+  if (ffn < 0 || ffn > FFLAST || ffcheck[ffn] != ffchar1)
+    goto ffdefault;
+
+  ffn = fftable[ffn];
+
+  /* ffn is what to do for this token type in this state.
+     Negative => reduce, -ffn is rule number.
+     Positive => shift, ffn is new state.
+       New state is final state => don't bother to shift,
+       just return success.
+     0, or most negative number => error.  */
+
+  if (ffn < 0)
+    {
+      if (ffn == FFFLAG)
+	goto fferrlab;
+      ffn = -ffn;
+      goto ffreduce;
+    }
+  else if (ffn == 0)
+    goto fferrlab;
+
+  if (ffn == FFFINAL)
+    FFACCEPT;
+
+  /* Shift the lookahead token.  */
+
+#if FFDEBUG != 0
+  if (ffdebug)
+    fprintf(stderr, "Shifting token %d (%s), ", ffchar, fftname[ffchar1]);
+#endif
+
+  /* Discard the token being shifted unless it is eof.  */
+  if (ffchar != FFEOF)
+    ffchar = FFEMPTY;
+
+  *++ffvsp = fflval;
+#ifdef FFLSP_NEEDED
+  *++fflsp = fflloc;
+#endif
+
+  /* count tokens shifted since error; after three, turn off error status.  */
+  if (fferrstatus) fferrstatus--;
+
+  ffstate = ffn;
+  goto ffnewstate;
+
+/* Do the default action for the current state.  */
+ffdefault:
+
+  ffn = ffdefact[ffstate];
+  if (ffn == 0)
+    goto fferrlab;
+
+/* Do a reduction.  ffn is the number of a rule to reduce with.  */
+ffreduce:
+  fflen = ffr2[ffn];
+  if (fflen > 0)
+    ffval = ffvsp[1-fflen]; /* implement default value of the action */
+
+#if FFDEBUG != 0
+  if (ffdebug)
+    {
+      int i;
+
+      fprintf (stderr, "Reducing via rule %d (line %d), ",
+	       ffn, ffrline[ffn]);
+
+      /* Print the symbols being reduced, and their result.  */
+      for (i = ffprhs[ffn]; ffrhs[i] > 0; i++)
+	fprintf (stderr, "%s ", fftname[ffrhs[i]]);
+      fprintf (stderr, " -> %s\n", fftname[ffr1[ffn]]);
+    }
+#endif
+
+
+  switch (ffn) {
+
+case 3:
+#line 245 "eval.y"
+{;
+    break;}
+case 4:
+#line 247 "eval.y"
+{ if( ffvsp[-1].Node<0 ) {
+		     fferror("Couldn't build node structure: out of memory?");
+		     FFERROR;  }
+                  gParse.resultNode = ffvsp[-1].Node;
+		;
+    break;}
+case 5:
+#line 253 "eval.y"
+{ if( ffvsp[-1].Node<0 ) {
+		     fferror("Couldn't build node structure: out of memory?");
+		     FFERROR;  }
+                  gParse.resultNode = ffvsp[-1].Node;
+		;
+    break;}
+case 6:
+#line 259 "eval.y"
+{ if( ffvsp[-1].Node<0 ) {
+		     fferror("Couldn't build node structure: out of memory?");
+		     FFERROR;  } 
+                  gParse.resultNode = ffvsp[-1].Node;
+		;
+    break;}
+case 7:
+#line 265 "eval.y"
+{ if( ffvsp[-1].Node<0 ) {
+		     fferror("Couldn't build node structure: out of memory?");
+		     FFERROR;  }
+                  gParse.resultNode = ffvsp[-1].Node;
+		;
+    break;}
+case 8:
+#line 270 "eval.y"
+{  fferrok;  ;
+    break;}
+case 9:
+#line 274 "eval.y"
+{ ffval.Node = New_Vector( ffvsp[0].Node ); TEST(ffval.Node); ;
+    break;}
+case 10:
+#line 276 "eval.y"
+{
+                  if( gParse.Nodes[ffvsp[-2].Node].nSubNodes >= MAXSUBS ) {
+		     ffvsp[-2].Node = Close_Vec( ffvsp[-2].Node ); TEST(ffvsp[-2].Node);
+		     ffval.Node = New_Vector( ffvsp[-2].Node ); TEST(ffval.Node);
+                  } else {
+                     ffval.Node = ffvsp[-2].Node;
+                  }
+		  gParse.Nodes[ffval.Node].SubNodes[ gParse.Nodes[ffval.Node].nSubNodes++ ]
+		     = ffvsp[0].Node;
+                ;
+    break;}
+case 11:
+#line 289 "eval.y"
+{ ffval.Node = New_Vector( ffvsp[0].Node ); TEST(ffval.Node); ;
+    break;}
+case 12:
+#line 291 "eval.y"
+{
+                  if( TYPE(ffvsp[-2].Node) < TYPE(ffvsp[0].Node) )
+                     TYPE(ffvsp[-2].Node) = TYPE(ffvsp[0].Node);
+                  if( gParse.Nodes[ffvsp[-2].Node].nSubNodes >= MAXSUBS ) {
+		     ffvsp[-2].Node = Close_Vec( ffvsp[-2].Node ); TEST(ffvsp[-2].Node);
+		     ffval.Node = New_Vector( ffvsp[-2].Node ); TEST(ffval.Node);
+                  } else {
+                     ffval.Node = ffvsp[-2].Node;
+                  }
+		  gParse.Nodes[ffval.Node].SubNodes[ gParse.Nodes[ffval.Node].nSubNodes++ ]
+		     = ffvsp[0].Node;
+                ;
+    break;}
+case 13:
+#line 304 "eval.y"
+{
+                  if( gParse.Nodes[ffvsp[-2].Node].nSubNodes >= MAXSUBS ) {
+		     ffvsp[-2].Node = Close_Vec( ffvsp[-2].Node ); TEST(ffvsp[-2].Node);
+		     ffval.Node = New_Vector( ffvsp[-2].Node ); TEST(ffval.Node);
+                  } else {
+                     ffval.Node = ffvsp[-2].Node;
+                  }
+		  gParse.Nodes[ffval.Node].SubNodes[ gParse.Nodes[ffval.Node].nSubNodes++ ]
+		     = ffvsp[0].Node;
+                ;
+    break;}
+case 14:
+#line 315 "eval.y"
+{
+                  TYPE(ffvsp[-2].Node) = TYPE(ffvsp[0].Node);
+                  if( gParse.Nodes[ffvsp[-2].Node].nSubNodes >= MAXSUBS ) {
+		     ffvsp[-2].Node = Close_Vec( ffvsp[-2].Node ); TEST(ffvsp[-2].Node);
+		     ffval.Node = New_Vector( ffvsp[-2].Node ); TEST(ffval.Node);
+                  } else {
+                     ffval.Node = ffvsp[-2].Node;
+                  }
+		  gParse.Nodes[ffval.Node].SubNodes[ gParse.Nodes[ffval.Node].nSubNodes++ ]
+		     = ffvsp[0].Node;
+                ;
+    break;}
+case 15:
+#line 329 "eval.y"
+{ ffval.Node = Close_Vec( ffvsp[-1].Node ); TEST(ffval.Node); ;
+    break;}
+case 16:
+#line 333 "eval.y"
+{ ffval.Node = Close_Vec( ffvsp[-1].Node ); TEST(ffval.Node); ;
+    break;}
+case 17:
+#line 337 "eval.y"
+{
+                  ffval.Node = New_Const( BITSTR, ffvsp[0].str, strlen(ffvsp[0].str)+1 ); TEST(ffval.Node);
+		  SIZE(ffval.Node) = strlen(ffvsp[0].str); ;
+    break;}
+case 18:
+#line 341 "eval.y"
+{ ffval.Node = New_Column( ffvsp[0].lng ); TEST(ffval.Node); ;
+    break;}
+case 19:
+#line 343 "eval.y"
+{
+                  if( TYPE(ffvsp[-1].Node) != LONG
+		      || OPER(ffvsp[-1].Node) != CONST_OP ) {
+		     fferror("Offset argument must be a constant integer");
+		     FFERROR;
+		  }
+                  ffval.Node = New_Offset( ffvsp[-3].lng, ffvsp[-1].Node ); TEST(ffval.Node);
+                ;
+    break;}
+case 20:
+#line 352 "eval.y"
+{ ffval.Node = New_BinOp( BITSTR, ffvsp[-2].Node, '&', ffvsp[0].Node ); TEST(ffval.Node);
+                  SIZE(ffval.Node) = ( SIZE(ffvsp[-2].Node)>SIZE(ffvsp[0].Node) ? SIZE(ffvsp[-2].Node) : SIZE(ffvsp[0].Node) );  ;
+    break;}
+case 21:
+#line 355 "eval.y"
+{ ffval.Node = New_BinOp( BITSTR, ffvsp[-2].Node, '|', ffvsp[0].Node ); TEST(ffval.Node);
+                  SIZE(ffval.Node) = ( SIZE(ffvsp[-2].Node)>SIZE(ffvsp[0].Node) ? SIZE(ffvsp[-2].Node) : SIZE(ffvsp[0].Node) );  ;
+    break;}
+case 22:
+#line 358 "eval.y"
+{ 
+		  if (SIZE(ffvsp[-2].Node)+SIZE(ffvsp[0].Node) >= MAX_STRLEN) {
+		    fferror("Combined bit string size exceeds " MAX_STRLEN_S " bits");
+		    FFERROR;
+		  }
+		  ffval.Node = New_BinOp( BITSTR, ffvsp[-2].Node, '+', ffvsp[0].Node ); TEST(ffval.Node);
+                  SIZE(ffval.Node) = SIZE(ffvsp[-2].Node) + SIZE(ffvsp[0].Node); 
+		;
+    break;}
+case 23:
+#line 367 "eval.y"
+{ ffval.Node = New_Deref( ffvsp[-3].Node, 1, ffvsp[-1].Node,  0,  0,  0,   0 ); TEST(ffval.Node); ;
+    break;}
+case 24:
+#line 369 "eval.y"
+{ ffval.Node = New_Deref( ffvsp[-5].Node, 2, ffvsp[-3].Node, ffvsp[-1].Node,  0,  0,   0 ); TEST(ffval.Node); ;
+    break;}
+case 25:
+#line 371 "eval.y"
+{ ffval.Node = New_Deref( ffvsp[-7].Node, 3, ffvsp[-5].Node, ffvsp[-3].Node, ffvsp[-1].Node,  0,   0 ); TEST(ffval.Node); ;
+    break;}
+case 26:
+#line 373 "eval.y"
+{ ffval.Node = New_Deref( ffvsp[-9].Node, 4, ffvsp[-7].Node, ffvsp[-5].Node, ffvsp[-3].Node, ffvsp[-1].Node,   0 ); TEST(ffval.Node); ;
+    break;}
+case 27:
+#line 375 "eval.y"
+{ ffval.Node = New_Deref( ffvsp[-11].Node, 5, ffvsp[-9].Node, ffvsp[-7].Node, ffvsp[-5].Node, ffvsp[-3].Node, ffvsp[-1].Node ); TEST(ffval.Node); ;
+    break;}
+case 28:
+#line 377 "eval.y"
+{ ffval.Node = New_Unary( BITSTR, NOT, ffvsp[0].Node ); TEST(ffval.Node);     ;
+    break;}
+case 29:
+#line 380 "eval.y"
+{ ffval.Node = ffvsp[-1].Node; ;
+    break;}
+case 30:
+#line 384 "eval.y"
+{ ffval.Node = New_Const( LONG,   &(ffvsp[0].lng), sizeof(long)   ); TEST(ffval.Node); ;
+    break;}
+case 31:
+#line 386 "eval.y"
+{ ffval.Node = New_Const( DOUBLE, &(ffvsp[0].dbl), sizeof(double) ); TEST(ffval.Node); ;
+    break;}
+case 32:
+#line 388 "eval.y"
+{ ffval.Node = New_Column( ffvsp[0].lng ); TEST(ffval.Node); ;
+    break;}
+case 33:
+#line 390 "eval.y"
+{
+                  if( TYPE(ffvsp[-1].Node) != LONG
+		      || OPER(ffvsp[-1].Node) != CONST_OP ) {
+		     fferror("Offset argument must be a constant integer");
+		     FFERROR;
+		  }
+                  ffval.Node = New_Offset( ffvsp[-3].lng, ffvsp[-1].Node ); TEST(ffval.Node);
+                ;
+    break;}
+case 34:
+#line 399 "eval.y"
+{ ffval.Node = New_Func( LONG, row_fct,  0, 0, 0, 0, 0, 0, 0, 0 ); ;
+    break;}
+case 35:
+#line 401 "eval.y"
+{ ffval.Node = New_Func( LONG, null_fct, 0, 0, 0, 0, 0, 0, 0, 0 ); ;
+    break;}
+case 36:
+#line 403 "eval.y"
+{ PROMOTE(ffvsp[-2].Node,ffvsp[0].Node); ffval.Node = New_BinOp( TYPE(ffvsp[-2].Node), ffvsp[-2].Node, '%', ffvsp[0].Node );
+		  TEST(ffval.Node);                                                ;
+    break;}
+case 37:
+#line 406 "eval.y"
+{ PROMOTE(ffvsp[-2].Node,ffvsp[0].Node); ffval.Node = New_BinOp( TYPE(ffvsp[-2].Node), ffvsp[-2].Node, '+', ffvsp[0].Node );
+		  TEST(ffval.Node);                                                ;
+    break;}
+case 38:
+#line 409 "eval.y"
+{ PROMOTE(ffvsp[-2].Node,ffvsp[0].Node); ffval.Node = New_BinOp( TYPE(ffvsp[-2].Node), ffvsp[-2].Node, '-', ffvsp[0].Node ); 
+		  TEST(ffval.Node);                                                ;
+    break;}
+case 39:
+#line 412 "eval.y"
+{ PROMOTE(ffvsp[-2].Node,ffvsp[0].Node); ffval.Node = New_BinOp( TYPE(ffvsp[-2].Node), ffvsp[-2].Node, '*', ffvsp[0].Node ); 
+		  TEST(ffval.Node);                                                ;
+    break;}
+case 40:
+#line 415 "eval.y"
+{ PROMOTE(ffvsp[-2].Node,ffvsp[0].Node); ffval.Node = New_BinOp( TYPE(ffvsp[-2].Node), ffvsp[-2].Node, '/', ffvsp[0].Node ); 
+		  TEST(ffval.Node);                                                ;
+    break;}
+case 41:
+#line 418 "eval.y"
+{ PROMOTE(ffvsp[-2].Node,ffvsp[0].Node); ffval.Node = New_BinOp( TYPE(ffvsp[-2].Node), ffvsp[-2].Node, POWER, ffvsp[0].Node );
+		  TEST(ffval.Node);                                                ;
+    break;}
+case 42:
+#line 421 "eval.y"
+{ ffval.Node = ffvsp[0].Node; ;
+    break;}
+case 43:
+#line 423 "eval.y"
+{ ffval.Node = New_Unary( TYPE(ffvsp[0].Node), UMINUS, ffvsp[0].Node ); TEST(ffval.Node); ;
+    break;}
+case 44:
+#line 425 "eval.y"
+{ ffval.Node = ffvsp[-1].Node; ;
+    break;}
+case 45:
+#line 427 "eval.y"
+{ ffvsp[0].Node = New_Unary( TYPE(ffvsp[-2].Node), 0, ffvsp[0].Node );
+                  ffval.Node = New_BinOp( TYPE(ffvsp[-2].Node), ffvsp[-2].Node, '*', ffvsp[0].Node ); 
+		  TEST(ffval.Node);                                ;
+    break;}
+case 46:
+#line 431 "eval.y"
+{ ffvsp[-2].Node = New_Unary( TYPE(ffvsp[0].Node), 0, ffvsp[-2].Node );
+                  ffval.Node = New_BinOp( TYPE(ffvsp[0].Node), ffvsp[-2].Node, '*', ffvsp[0].Node );
+                  TEST(ffval.Node);                                ;
+    break;}
+case 47:
+#line 435 "eval.y"
+{
+                  PROMOTE(ffvsp[-2].Node,ffvsp[0].Node);
+                  if( ! Test_Dims(ffvsp[-2].Node,ffvsp[0].Node) ) {
+                     fferror("Incompatible dimensions in '?:' arguments");
+		     FFERROR;
+                  }
+                  ffval.Node = New_Func( 0, ifthenelse_fct, 3, ffvsp[-2].Node, ffvsp[0].Node, ffvsp[-4].Node,
+                                 0, 0, 0, 0 );
+                  TEST(ffval.Node);
+                  if( SIZE(ffvsp[-2].Node)<SIZE(ffvsp[0].Node) )  Copy_Dims(ffval.Node, ffvsp[0].Node);
+                  TYPE(ffvsp[-4].Node) = TYPE(ffvsp[-2].Node);
+                  if( ! Test_Dims(ffvsp[-4].Node,ffval.Node) ) {
+                     fferror("Incompatible dimensions in '?:' condition");
+		     FFERROR;
+                  }
+                  TYPE(ffvsp[-4].Node) = BOOLEAN;
+                  if( SIZE(ffval.Node)<SIZE(ffvsp[-4].Node) )  Copy_Dims(ffval.Node, ffvsp[-4].Node);
+                ;
+    break;}
+case 48:
+#line 454 "eval.y"
+{
+                  PROMOTE(ffvsp[-2].Node,ffvsp[0].Node);
+                  if( ! Test_Dims(ffvsp[-2].Node,ffvsp[0].Node) ) {
+                     fferror("Incompatible dimensions in '?:' arguments");
+		     FFERROR;
+                  }
+                  ffval.Node = New_Func( 0, ifthenelse_fct, 3, ffvsp[-2].Node, ffvsp[0].Node, ffvsp[-4].Node,
+                                 0, 0, 0, 0 );
+                  TEST(ffval.Node);
+                  if( SIZE(ffvsp[-2].Node)<SIZE(ffvsp[0].Node) )  Copy_Dims(ffval.Node, ffvsp[0].Node);
+                  TYPE(ffvsp[-4].Node) = TYPE(ffvsp[-2].Node);
+                  if( ! Test_Dims(ffvsp[-4].Node,ffval.Node) ) {
+                     fferror("Incompatible dimensions in '?:' condition");
+		     FFERROR;
+                  }
+                  TYPE(ffvsp[-4].Node) = BOOLEAN;
+                  if( SIZE(ffval.Node)<SIZE(ffvsp[-4].Node) )  Copy_Dims(ffval.Node, ffvsp[-4].Node);
+                ;
+    break;}
+case 49:
+#line 473 "eval.y"
+{
+                  PROMOTE(ffvsp[-2].Node,ffvsp[0].Node);
+                  if( ! Test_Dims(ffvsp[-2].Node,ffvsp[0].Node) ) {
+                     fferror("Incompatible dimensions in '?:' arguments");
+		     FFERROR;
+                  }
+                  ffval.Node = New_Func( 0, ifthenelse_fct, 3, ffvsp[-2].Node, ffvsp[0].Node, ffvsp[-4].Node,
+                                 0, 0, 0, 0 );
+                  TEST(ffval.Node);
+                  if( SIZE(ffvsp[-2].Node)<SIZE(ffvsp[0].Node) )  Copy_Dims(ffval.Node, ffvsp[0].Node);
+                  TYPE(ffvsp[-4].Node) = TYPE(ffvsp[-2].Node);
+                  if( ! Test_Dims(ffvsp[-4].Node,ffval.Node) ) {
+                     fferror("Incompatible dimensions in '?:' condition");
+		     FFERROR;
+                  }
+                  TYPE(ffvsp[-4].Node) = BOOLEAN;
+                  if( SIZE(ffval.Node)<SIZE(ffvsp[-4].Node) )  Copy_Dims(ffval.Node, ffvsp[-4].Node);
+                ;
+    break;}
+case 50:
+#line 492 "eval.y"
+{ if (FSTRCMP(ffvsp[-1].str,"RANDOM(") == 0) {  /* Scalar RANDOM() */
+                     srand( (unsigned int) time(NULL) );
+                     ffval.Node = New_Func( DOUBLE, rnd_fct, 0, 0, 0, 0, 0, 0, 0, 0 );
+		  } else if (FSTRCMP(ffvsp[-1].str,"RANDOMN(") == 0) {/*Scalar RANDOMN()*/
+		     srand( (unsigned int) time(NULL) );
+		     ffval.Node = New_Func( DOUBLE, gasrnd_fct, 0, 0, 0, 0, 0, 0, 0, 0 );
+                  } else {
+                     fferror("Function() not supported");
+		     FFERROR;
+		  }
+                  TEST(ffval.Node); 
+                ;
+    break;}
+case 51:
+#line 505 "eval.y"
+{ if (FSTRCMP(ffvsp[-2].str,"SUM(") == 0) {
+		     ffval.Node = New_Func( LONG, sum_fct, 1, ffvsp[-1].Node, 0, 0, 0, 0, 0, 0 );
+                  } else if (FSTRCMP(ffvsp[-2].str,"NELEM(") == 0) {
+                     ffval.Node = New_Const( LONG, &( SIZE(ffvsp[-1].Node) ), sizeof(long) );
+                  } else if (FSTRCMP(ffvsp[-2].str,"ACCUM(") == 0) {
+		    long zero = 0;
+		    ffval.Node = New_BinOp( LONG , ffvsp[-1].Node, ACCUM, New_Const( LONG, &zero, sizeof(zero) ));
+		  } else {
+                     fferror("Function(bool) not supported");
+		     FFERROR;
+		  }
+                  TEST(ffval.Node); 
+		;
+    break;}
+case 52:
+#line 519 "eval.y"
+{ if (FSTRCMP(ffvsp[-2].str,"NELEM(") == 0) {
+                     ffval.Node = New_Const( LONG, &( SIZE(ffvsp[-1].Node) ), sizeof(long) );
+		  } else if (FSTRCMP(ffvsp[-2].str,"NVALID(") == 0) {
+		     ffval.Node = New_Func( LONG, nonnull_fct, 1, ffvsp[-1].Node,
+				    0, 0, 0, 0, 0, 0 );
+		  } else {
+                     fferror("Function(str) not supported");
+		     FFERROR;
+		  }
+                  TEST(ffval.Node); 
+		;
+    break;}
+case 53:
+#line 531 "eval.y"
+{ if (FSTRCMP(ffvsp[-2].str,"NELEM(") == 0) {
+                     ffval.Node = New_Const( LONG, &( SIZE(ffvsp[-1].Node) ), sizeof(long) );
+		} else if (FSTRCMP(ffvsp[-2].str,"NVALID(") == 0) { /* Bit arrays do not have NULL */
+                     ffval.Node = New_Const( LONG, &( SIZE(ffvsp[-1].Node) ), sizeof(long) );
+		} else if (FSTRCMP(ffvsp[-2].str,"SUM(") == 0) {
+		     ffval.Node = New_Func( LONG, sum_fct, 1, ffvsp[-1].Node,
+				    0, 0, 0, 0, 0, 0 );
+		} else if (FSTRCMP(ffvsp[-2].str,"MIN(") == 0) {
+		     ffval.Node = New_Func( TYPE(ffvsp[-1].Node),  /* Force 1D result */
+				    min1_fct, 1, ffvsp[-1].Node, 0, 0, 0, 0, 0, 0 );
+		     /* Note: $2 is a vector so the result can never
+		        be a constant.  Therefore it will never be set
+		        inside New_Func(), and it is safe to set SIZE() */
+		     SIZE(ffval.Node) = 1;
+		} else if (FSTRCMP(ffvsp[-2].str,"ACCUM(") == 0) {
+		    long zero = 0;
+		    ffval.Node = New_BinOp( LONG , ffvsp[-1].Node, ACCUM, New_Const( LONG, &zero, sizeof(zero) ));
+		} else if (FSTRCMP(ffvsp[-2].str,"MAX(") == 0) {
+		     ffval.Node = New_Func( TYPE(ffvsp[-1].Node),  /* Force 1D result */
+				    max1_fct, 1, ffvsp[-1].Node, 0, 0, 0, 0, 0, 0 );
+		     /* Note: $2 is a vector so the result can never
+		        be a constant.  Therefore it will never be set
+		        inside New_Func(), and it is safe to set SIZE() */
+		     SIZE(ffval.Node) = 1;
+		} else {
+                     fferror("Function(bits) not supported");
+		     FFERROR;
+		  }
+                  TEST(ffval.Node); 
+		;
+    break;}
+case 54:
+#line 562 "eval.y"
+{ if (FSTRCMP(ffvsp[-2].str,"SUM(") == 0)
+		     ffval.Node = New_Func( TYPE(ffvsp[-1].Node), sum_fct, 1, ffvsp[-1].Node,
+				    0, 0, 0, 0, 0, 0 );
+		  else if (FSTRCMP(ffvsp[-2].str,"AVERAGE(") == 0)
+		     ffval.Node = New_Func( DOUBLE, average_fct, 1, ffvsp[-1].Node,
+				    0, 0, 0, 0, 0, 0 );
+		  else if (FSTRCMP(ffvsp[-2].str,"STDDEV(") == 0)
+		     ffval.Node = New_Func( DOUBLE, stddev_fct, 1, ffvsp[-1].Node,
+				    0, 0, 0, 0, 0, 0 );
+		  else if (FSTRCMP(ffvsp[-2].str,"MEDIAN(") == 0)
+		     ffval.Node = New_Func( TYPE(ffvsp[-1].Node), median_fct, 1, ffvsp[-1].Node,
+				    0, 0, 0, 0, 0, 0 );
+		  else if (FSTRCMP(ffvsp[-2].str,"NELEM(") == 0)
+                     ffval.Node = New_Const( LONG, &( SIZE(ffvsp[-1].Node) ), sizeof(long) );
+		  else if (FSTRCMP(ffvsp[-2].str,"NVALID(") == 0)
+		     ffval.Node = New_Func( LONG, nonnull_fct, 1, ffvsp[-1].Node,
+				    0, 0, 0, 0, 0, 0 );
+		  else if   ((FSTRCMP(ffvsp[-2].str,"ACCUM(") == 0) && (TYPE(ffvsp[-1].Node) == LONG)) {
+		    long zero = 0;
+		    ffval.Node = New_BinOp( LONG ,   ffvsp[-1].Node, ACCUM, New_Const( LONG,   &zero, sizeof(zero) ));
+		  } else if ((FSTRCMP(ffvsp[-2].str,"ACCUM(") == 0) && (TYPE(ffvsp[-1].Node) == DOUBLE)) {
+		    double zero = 0;
+		    ffval.Node = New_BinOp( DOUBLE , ffvsp[-1].Node, ACCUM, New_Const( DOUBLE, &zero, sizeof(zero) ));
+		  } else if ((FSTRCMP(ffvsp[-2].str,"SEQDIFF(") == 0) && (TYPE(ffvsp[-1].Node) == LONG)) {
+		    long zero = 0;
+		    ffval.Node = New_BinOp( LONG ,   ffvsp[-1].Node, DIFF, New_Const( LONG,   &zero, sizeof(zero) ));
+		  } else if ((FSTRCMP(ffvsp[-2].str,"SEQDIFF(") == 0) && (TYPE(ffvsp[-1].Node) == DOUBLE)) {
+		    double zero = 0;
+		    ffval.Node = New_BinOp( DOUBLE , ffvsp[-1].Node, DIFF, New_Const( DOUBLE, &zero, sizeof(zero) ));
+		  } else if (FSTRCMP(ffvsp[-2].str,"ABS(") == 0)
+		     ffval.Node = New_Func( 0, abs_fct, 1, ffvsp[-1].Node, 0, 0, 0, 0, 0, 0 );
+ 		  else if (FSTRCMP(ffvsp[-2].str,"MIN(") == 0)
+		     ffval.Node = New_Func( TYPE(ffvsp[-1].Node),  /* Force 1D result */
+				    min1_fct, 1, ffvsp[-1].Node, 0, 0, 0, 0, 0, 0 );
+		  else if (FSTRCMP(ffvsp[-2].str,"MAX(") == 0)
+		     ffval.Node = New_Func( TYPE(ffvsp[-1].Node),  /* Force 1D result */
+				    max1_fct, 1, ffvsp[-1].Node, 0, 0, 0, 0, 0, 0 );
+		  else if (FSTRCMP(ffvsp[-2].str,"RANDOM(") == 0) { /* Vector RANDOM() */
+                     srand( (unsigned int) time(NULL) );
+                     ffval.Node = New_Func( 0, rnd_fct, 1, ffvsp[-1].Node, 0, 0, 0, 0, 0, 0 );
+		     TEST(ffval.Node);
+		     TYPE(ffval.Node) = DOUBLE;
+		  } else if (FSTRCMP(ffvsp[-2].str,"RANDOMN(") == 0) {
+		     srand( (unsigned int) time(NULL) ); /* Vector RANDOMN() */
+		     ffval.Node = New_Func( 0, gasrnd_fct, 1, ffvsp[-1].Node, 0, 0, 0, 0, 0, 0 );
+		     TEST(ffval.Node);
+		     TYPE(ffval.Node) = DOUBLE;
+                  } 
+  		  else {  /*  These all take DOUBLE arguments  */
+		     if( TYPE(ffvsp[-1].Node) != DOUBLE ) ffvsp[-1].Node = New_Unary( DOUBLE, 0, ffvsp[-1].Node );
+                     if (FSTRCMP(ffvsp[-2].str,"SIN(") == 0)
+			ffval.Node = New_Func( 0, sin_fct,  1, ffvsp[-1].Node, 0, 0, 0, 0, 0, 0 );
+		     else if (FSTRCMP(ffvsp[-2].str,"COS(") == 0)
+			ffval.Node = New_Func( 0, cos_fct,  1, ffvsp[-1].Node, 0, 0, 0, 0, 0, 0 );
+		     else if (FSTRCMP(ffvsp[-2].str,"TAN(") == 0)
+			ffval.Node = New_Func( 0, tan_fct,  1, ffvsp[-1].Node, 0, 0, 0, 0, 0, 0 );
+		     else if (FSTRCMP(ffvsp[-2].str,"ARCSIN(") == 0
+			      || FSTRCMP(ffvsp[-2].str,"ASIN(") == 0)
+			ffval.Node = New_Func( 0, asin_fct, 1, ffvsp[-1].Node, 0, 0, 0, 0, 0, 0 );
+		     else if (FSTRCMP(ffvsp[-2].str,"ARCCOS(") == 0
+			      || FSTRCMP(ffvsp[-2].str,"ACOS(") == 0)
+			ffval.Node = New_Func( 0, acos_fct, 1, ffvsp[-1].Node, 0, 0, 0, 0, 0, 0 );
+		     else if (FSTRCMP(ffvsp[-2].str,"ARCTAN(") == 0
+			      || FSTRCMP(ffvsp[-2].str,"ATAN(") == 0)
+			ffval.Node = New_Func( 0, atan_fct, 1, ffvsp[-1].Node, 0, 0, 0, 0, 0, 0 );
+		     else if (FSTRCMP(ffvsp[-2].str,"SINH(") == 0)
+			ffval.Node = New_Func( 0, sinh_fct,  1, ffvsp[-1].Node, 0, 0, 0, 0, 0, 0 );
+		     else if (FSTRCMP(ffvsp[-2].str,"COSH(") == 0)
+			ffval.Node = New_Func( 0, cosh_fct,  1, ffvsp[-1].Node, 0, 0, 0, 0, 0, 0 );
+		     else if (FSTRCMP(ffvsp[-2].str,"TANH(") == 0)
+			ffval.Node = New_Func( 0, tanh_fct,  1, ffvsp[-1].Node, 0, 0, 0, 0, 0, 0 );
+		     else if (FSTRCMP(ffvsp[-2].str,"EXP(") == 0)
+			ffval.Node = New_Func( 0, exp_fct,  1, ffvsp[-1].Node, 0, 0, 0, 0, 0, 0 );
+		     else if (FSTRCMP(ffvsp[-2].str,"LOG(") == 0)
+			ffval.Node = New_Func( 0, log_fct,  1, ffvsp[-1].Node, 0, 0, 0, 0, 0, 0 );
+		     else if (FSTRCMP(ffvsp[-2].str,"LOG10(") == 0)
+			ffval.Node = New_Func( 0, log10_fct, 1, ffvsp[-1].Node, 0, 0, 0, 0, 0, 0 );
+		     else if (FSTRCMP(ffvsp[-2].str,"SQRT(") == 0)
+			ffval.Node = New_Func( 0, sqrt_fct, 1, ffvsp[-1].Node, 0, 0, 0, 0, 0, 0 );
+		     else if (FSTRCMP(ffvsp[-2].str,"ROUND(") == 0)
+			ffval.Node = New_Func( 0, round_fct, 1, ffvsp[-1].Node, 0, 0, 0, 0, 0, 0 );
+		     else if (FSTRCMP(ffvsp[-2].str,"FLOOR(") == 0)
+			ffval.Node = New_Func( 0, floor_fct, 1, ffvsp[-1].Node, 0, 0, 0, 0, 0, 0 );
+		     else if (FSTRCMP(ffvsp[-2].str,"CEIL(") == 0)
+			ffval.Node = New_Func( 0, ceil_fct, 1, ffvsp[-1].Node, 0, 0, 0, 0, 0, 0 );
+		     else if (FSTRCMP(ffvsp[-2].str,"RANDOMP(") == 0) {
+		       srand( (unsigned int) time(NULL) );
+		       ffval.Node = New_Func( 0, poirnd_fct, 1, ffvsp[-1].Node, 
+				      0, 0, 0, 0, 0, 0 );
+		       TYPE(ffval.Node) = LONG;
+		     } else {
+			fferror("Function(expr) not supported");
+			FFERROR;
+		     }
+		  }
+                  TEST(ffval.Node); 
+                ;
+    break;}
+case 55:
+#line 660 "eval.y"
+{ 
+		  if (FSTRCMP(ffvsp[-4].str,"STRSTR(") == 0) {
+		    ffval.Node = New_Func( LONG, strpos_fct, 2, ffvsp[-3].Node, ffvsp[-1].Node, 0, 
+				   0, 0, 0, 0 );
+		    TEST(ffval.Node);
+		  }
+                ;
+    break;}
+case 56:
+#line 668 "eval.y"
+{ 
+		   if (FSTRCMP(ffvsp[-4].str,"DEFNULL(") == 0) {
+		      if( SIZE(ffvsp[-3].Node)>=SIZE(ffvsp[-1].Node) && Test_Dims( ffvsp[-3].Node, ffvsp[-1].Node ) ) {
+			 PROMOTE(ffvsp[-3].Node,ffvsp[-1].Node);
+			 ffval.Node = New_Func( 0, defnull_fct, 2, ffvsp[-3].Node, ffvsp[-1].Node, 0,
+					0, 0, 0, 0 );
+			 TEST(ffval.Node); 
+		      } else {
+			 fferror("Dimensions of DEFNULL arguments "
+				 "are not compatible");
+			 FFERROR;
+		      }
+		   } else if (FSTRCMP(ffvsp[-4].str,"ARCTAN2(") == 0) {
+		     if( TYPE(ffvsp[-3].Node) != DOUBLE ) ffvsp[-3].Node = New_Unary( DOUBLE, 0, ffvsp[-3].Node );
+		     if( TYPE(ffvsp[-1].Node) != DOUBLE ) ffvsp[-1].Node = New_Unary( DOUBLE, 0, ffvsp[-1].Node );
+		     if( Test_Dims( ffvsp[-3].Node, ffvsp[-1].Node ) ) {
+			ffval.Node = New_Func( 0, atan2_fct, 2, ffvsp[-3].Node, ffvsp[-1].Node, 0, 0, 0, 0, 0 );
+			TEST(ffval.Node); 
+			if( SIZE(ffvsp[-3].Node)<SIZE(ffvsp[-1].Node) ) Copy_Dims(ffval.Node, ffvsp[-1].Node);
+		     } else {
+			fferror("Dimensions of arctan2 arguments "
+				"are not compatible");
+			FFERROR;
+		     }
+		   } else if (FSTRCMP(ffvsp[-4].str,"MIN(") == 0) {
+		      PROMOTE( ffvsp[-3].Node, ffvsp[-1].Node );
+		      if( Test_Dims( ffvsp[-3].Node, ffvsp[-1].Node ) ) {
+			ffval.Node = New_Func( 0, min2_fct, 2, ffvsp[-3].Node, ffvsp[-1].Node, 0, 0, 0, 0, 0 );
+			TEST(ffval.Node);
+			if( SIZE(ffvsp[-3].Node)<SIZE(ffvsp[-1].Node) ) Copy_Dims(ffval.Node, ffvsp[-1].Node);
+		      } else {
+			fferror("Dimensions of min(a,b) arguments "
+				"are not compatible");
+			FFERROR;
+		      }
+		   } else if (FSTRCMP(ffvsp[-4].str,"MAX(") == 0) {
+		      PROMOTE( ffvsp[-3].Node, ffvsp[-1].Node );
+		      if( Test_Dims( ffvsp[-3].Node, ffvsp[-1].Node ) ) {
+			ffval.Node = New_Func( 0, max2_fct, 2, ffvsp[-3].Node, ffvsp[-1].Node, 0, 0, 0, 0, 0 );
+			TEST(ffval.Node);
+			if( SIZE(ffvsp[-3].Node)<SIZE(ffvsp[-1].Node) ) Copy_Dims(ffval.Node, ffvsp[-1].Node);
+		      } else {
+			fferror("Dimensions of max(a,b) arguments "
+				"are not compatible");
+			FFERROR;
+		      }
+#if 0
+		   } else if (FSTRCMP(ffvsp[-4].str,"STRSTR(") == 0) {
+		     if( TYPE(ffvsp[-3].Node) != STRING || TYPE(ffvsp[-1].Node) != STRING) {
+		       fferror("Arguments to strstr(s,r) must be strings");
+		       FFERROR;
+		     }
+		     ffval.Node = New_Func( LONG, strpos_fct, 2, ffvsp[-3].Node, ffvsp[-1].Node, 0, 
+				    0, 0, 0, 0 );
+		     TEST(ffval.Node);
+#endif
+		   } else {
+		      fferror("Function(expr,expr) not supported");
+		      FFERROR;
+		   }
+                ;
+    break;}
+case 57:
+#line 730 "eval.y"
+{ 
+		  if (FSTRCMP(ffvsp[-8].str,"ANGSEP(") == 0) {
+		    if( TYPE(ffvsp[-7].Node) != DOUBLE ) ffvsp[-7].Node = New_Unary( DOUBLE, 0, ffvsp[-7].Node );
+		    if( TYPE(ffvsp[-5].Node) != DOUBLE ) ffvsp[-5].Node = New_Unary( DOUBLE, 0, ffvsp[-5].Node );
+		    if( TYPE(ffvsp[-3].Node) != DOUBLE ) ffvsp[-3].Node = New_Unary( DOUBLE, 0, ffvsp[-3].Node );
+		    if( TYPE(ffvsp[-1].Node) != DOUBLE ) ffvsp[-1].Node = New_Unary( DOUBLE, 0, ffvsp[-1].Node );
+		    if( Test_Dims( ffvsp[-7].Node, ffvsp[-5].Node ) && Test_Dims( ffvsp[-5].Node, ffvsp[-3].Node ) && 
+			Test_Dims( ffvsp[-3].Node, ffvsp[-1].Node ) ) {
+		      ffval.Node = New_Func( 0, angsep_fct, 4, ffvsp[-7].Node, ffvsp[-5].Node, ffvsp[-3].Node, ffvsp[-1].Node,0,0,0 );
+		      TEST(ffval.Node); 
+		      if( SIZE(ffvsp[-7].Node)<SIZE(ffvsp[-5].Node) ) Copy_Dims(ffval.Node, ffvsp[-5].Node);
+		      if( SIZE(ffvsp[-5].Node)<SIZE(ffvsp[-3].Node) ) Copy_Dims(ffval.Node, ffvsp[-3].Node);
+		      if( SIZE(ffvsp[-3].Node)<SIZE(ffvsp[-1].Node) ) Copy_Dims(ffval.Node, ffvsp[-1].Node);
+		    } else {
+		      fferror("Dimensions of ANGSEP arguments "
+			      "are not compatible");
+		      FFERROR;
+		    }
+		   } else {
+		      fferror("Function(expr,expr,expr,expr) not supported");
+		      FFERROR;
+		   }
+                ;
+    break;}
+case 58:
+#line 754 "eval.y"
+{ ffval.Node = New_Deref( ffvsp[-3].Node, 1, ffvsp[-1].Node,  0,  0,  0,   0 ); TEST(ffval.Node); ;
+    break;}
+case 59:
+#line 756 "eval.y"
+{ ffval.Node = New_Deref( ffvsp[-5].Node, 2, ffvsp[-3].Node, ffvsp[-1].Node,  0,  0,   0 ); TEST(ffval.Node); ;
+    break;}
+case 60:
+#line 758 "eval.y"
+{ ffval.Node = New_Deref( ffvsp[-7].Node, 3, ffvsp[-5].Node, ffvsp[-3].Node, ffvsp[-1].Node,  0,   0 ); TEST(ffval.Node); ;
+    break;}
+case 61:
+#line 760 "eval.y"
+{ ffval.Node = New_Deref( ffvsp[-9].Node, 4, ffvsp[-7].Node, ffvsp[-5].Node, ffvsp[-3].Node, ffvsp[-1].Node,   0 ); TEST(ffval.Node); ;
+    break;}
+case 62:
+#line 762 "eval.y"
+{ ffval.Node = New_Deref( ffvsp[-11].Node, 5, ffvsp[-9].Node, ffvsp[-7].Node, ffvsp[-5].Node, ffvsp[-3].Node, ffvsp[-1].Node ); TEST(ffval.Node); ;
+    break;}
+case 63:
+#line 764 "eval.y"
+{ ffval.Node = New_Unary( LONG,   INTCAST, ffvsp[0].Node );  TEST(ffval.Node);  ;
+    break;}
+case 64:
+#line 766 "eval.y"
+{ ffval.Node = New_Unary( LONG,   INTCAST, ffvsp[0].Node );  TEST(ffval.Node);  ;
+    break;}
+case 65:
+#line 768 "eval.y"
+{ ffval.Node = New_Unary( DOUBLE, FLTCAST, ffvsp[0].Node );  TEST(ffval.Node);  ;
+    break;}
+case 66:
+#line 770 "eval.y"
+{ ffval.Node = New_Unary( DOUBLE, FLTCAST, ffvsp[0].Node );  TEST(ffval.Node);  ;
+    break;}
+case 67:
+#line 774 "eval.y"
+{ ffval.Node = New_Const( BOOLEAN, &(ffvsp[0].log), sizeof(char) ); TEST(ffval.Node); ;
+    break;}
+case 68:
+#line 776 "eval.y"
+{ ffval.Node = New_Column( ffvsp[0].lng ); TEST(ffval.Node); ;
+    break;}
+case 69:
+#line 778 "eval.y"
+{
+                  if( TYPE(ffvsp[-1].Node) != LONG
+		      || OPER(ffvsp[-1].Node) != CONST_OP ) {
+		     fferror("Offset argument must be a constant integer");
+		     FFERROR;
+		  }
+                  ffval.Node = New_Offset( ffvsp[-3].lng, ffvsp[-1].Node ); TEST(ffval.Node);
+                ;
+    break;}
+case 70:
+#line 787 "eval.y"
+{ ffval.Node = New_BinOp( BOOLEAN, ffvsp[-2].Node, EQ,  ffvsp[0].Node ); TEST(ffval.Node);
+		  SIZE(ffval.Node) = 1;                                     ;
+    break;}
+case 71:
+#line 790 "eval.y"
+{ ffval.Node = New_BinOp( BOOLEAN, ffvsp[-2].Node, NE,  ffvsp[0].Node ); TEST(ffval.Node); 
+		  SIZE(ffval.Node) = 1;                                     ;
+    break;}
+case 72:
+#line 793 "eval.y"
+{ ffval.Node = New_BinOp( BOOLEAN, ffvsp[-2].Node, LT,  ffvsp[0].Node ); TEST(ffval.Node); 
+		  SIZE(ffval.Node) = 1;                                     ;
+    break;}
+case 73:
+#line 796 "eval.y"
+{ ffval.Node = New_BinOp( BOOLEAN, ffvsp[-2].Node, LTE, ffvsp[0].Node ); TEST(ffval.Node); 
+		  SIZE(ffval.Node) = 1;                                     ;
+    break;}
+case 74:
+#line 799 "eval.y"
+{ ffval.Node = New_BinOp( BOOLEAN, ffvsp[-2].Node, GT,  ffvsp[0].Node ); TEST(ffval.Node); 
+		  SIZE(ffval.Node) = 1;                                     ;
+    break;}
+case 75:
+#line 802 "eval.y"
+{ ffval.Node = New_BinOp( BOOLEAN, ffvsp[-2].Node, GTE, ffvsp[0].Node ); TEST(ffval.Node); 
+		  SIZE(ffval.Node) = 1;                                     ;
+    break;}
+case 76:
+#line 805 "eval.y"
+{ PROMOTE(ffvsp[-2].Node,ffvsp[0].Node); ffval.Node = New_BinOp( BOOLEAN, ffvsp[-2].Node, GT,  ffvsp[0].Node );
+                  TEST(ffval.Node);                                               ;
+    break;}
+case 77:
+#line 808 "eval.y"
+{ PROMOTE(ffvsp[-2].Node,ffvsp[0].Node); ffval.Node = New_BinOp( BOOLEAN, ffvsp[-2].Node, LT,  ffvsp[0].Node );
+                  TEST(ffval.Node);                                               ;
+    break;}
+case 78:
+#line 811 "eval.y"
+{ PROMOTE(ffvsp[-2].Node,ffvsp[0].Node); ffval.Node = New_BinOp( BOOLEAN, ffvsp[-2].Node, GTE, ffvsp[0].Node );
+                  TEST(ffval.Node);                                               ;
+    break;}
+case 79:
+#line 814 "eval.y"
+{ PROMOTE(ffvsp[-2].Node,ffvsp[0].Node); ffval.Node = New_BinOp( BOOLEAN, ffvsp[-2].Node, LTE, ffvsp[0].Node );
+                  TEST(ffval.Node);                                               ;
+    break;}
+case 80:
+#line 817 "eval.y"
+{ PROMOTE(ffvsp[-2].Node,ffvsp[0].Node); ffval.Node = New_BinOp( BOOLEAN, ffvsp[-2].Node, '~', ffvsp[0].Node );
+                  TEST(ffval.Node);                                               ;
+    break;}
+case 81:
+#line 820 "eval.y"
+{ PROMOTE(ffvsp[-2].Node,ffvsp[0].Node); ffval.Node = New_BinOp( BOOLEAN, ffvsp[-2].Node, EQ,  ffvsp[0].Node );
+                  TEST(ffval.Node);                                               ;
+    break;}
+case 82:
+#line 823 "eval.y"
+{ PROMOTE(ffvsp[-2].Node,ffvsp[0].Node); ffval.Node = New_BinOp( BOOLEAN, ffvsp[-2].Node, NE,  ffvsp[0].Node );
+                  TEST(ffval.Node);                                               ;
+    break;}
+case 83:
+#line 826 "eval.y"
+{ ffval.Node = New_BinOp( BOOLEAN, ffvsp[-2].Node, EQ,  ffvsp[0].Node ); TEST(ffval.Node);
+                  SIZE(ffval.Node) = 1; ;
+    break;}
+case 84:
+#line 829 "eval.y"
+{ ffval.Node = New_BinOp( BOOLEAN, ffvsp[-2].Node, NE,  ffvsp[0].Node ); TEST(ffval.Node);
+                  SIZE(ffval.Node) = 1; ;
+    break;}
+case 85:
+#line 832 "eval.y"
+{ ffval.Node = New_BinOp( BOOLEAN, ffvsp[-2].Node, GT,  ffvsp[0].Node ); TEST(ffval.Node);
+                  SIZE(ffval.Node) = 1; ;
+    break;}
+case 86:
+#line 835 "eval.y"
+{ ffval.Node = New_BinOp( BOOLEAN, ffvsp[-2].Node, GTE, ffvsp[0].Node ); TEST(ffval.Node);
+                  SIZE(ffval.Node) = 1; ;
+    break;}
+case 87:
+#line 838 "eval.y"
+{ ffval.Node = New_BinOp( BOOLEAN, ffvsp[-2].Node, LT,  ffvsp[0].Node ); TEST(ffval.Node);
+                  SIZE(ffval.Node) = 1; ;
+    break;}
+case 88:
+#line 841 "eval.y"
+{ ffval.Node = New_BinOp( BOOLEAN, ffvsp[-2].Node, LTE, ffvsp[0].Node ); TEST(ffval.Node);
+                  SIZE(ffval.Node) = 1; ;
+    break;}
+case 89:
+#line 844 "eval.y"
+{ ffval.Node = New_BinOp( BOOLEAN, ffvsp[-2].Node, AND, ffvsp[0].Node ); TEST(ffval.Node); ;
+    break;}
+case 90:
+#line 846 "eval.y"
+{ ffval.Node = New_BinOp( BOOLEAN, ffvsp[-2].Node, OR,  ffvsp[0].Node ); TEST(ffval.Node); ;
+    break;}
+case 91:
+#line 848 "eval.y"
+{ ffval.Node = New_BinOp( BOOLEAN, ffvsp[-2].Node, EQ,  ffvsp[0].Node ); TEST(ffval.Node); ;
+    break;}
+case 92:
+#line 850 "eval.y"
+{ ffval.Node = New_BinOp( BOOLEAN, ffvsp[-2].Node, NE,  ffvsp[0].Node ); TEST(ffval.Node); ;
+    break;}
+case 93:
+#line 853 "eval.y"
+{ PROMOTE(ffvsp[-4].Node,ffvsp[-2].Node); PROMOTE(ffvsp[-4].Node,ffvsp[0].Node); PROMOTE(ffvsp[-2].Node,ffvsp[0].Node);
+		  ffvsp[-2].Node = New_BinOp( BOOLEAN, ffvsp[-2].Node, LTE, ffvsp[-4].Node );
+                  ffvsp[0].Node = New_BinOp( BOOLEAN, ffvsp[-4].Node, LTE, ffvsp[0].Node );
+                  ffval.Node = New_BinOp( BOOLEAN, ffvsp[-2].Node, AND, ffvsp[0].Node );
+                  TEST(ffval.Node);                                         ;
+    break;}
+case 94:
+#line 860 "eval.y"
+{
+                  if( ! Test_Dims(ffvsp[-2].Node,ffvsp[0].Node) ) {
+                     fferror("Incompatible dimensions in '?:' arguments");
+		     FFERROR;
+                  }
+                  ffval.Node = New_Func( 0, ifthenelse_fct, 3, ffvsp[-2].Node, ffvsp[0].Node, ffvsp[-4].Node,
+                                 0, 0, 0, 0 );
+                  TEST(ffval.Node);
+                  if( SIZE(ffvsp[-2].Node)<SIZE(ffvsp[0].Node) )  Copy_Dims(ffval.Node, ffvsp[0].Node);
+                  if( ! Test_Dims(ffvsp[-4].Node,ffval.Node) ) {
+                     fferror("Incompatible dimensions in '?:' condition");
+		     FFERROR;
+                  }
+                  if( SIZE(ffval.Node)<SIZE(ffvsp[-4].Node) )  Copy_Dims(ffval.Node, ffvsp[-4].Node);
+                ;
+    break;}
+case 95:
+#line 877 "eval.y"
+{
+		   if (FSTRCMP(ffvsp[-2].str,"ISNULL(") == 0) {
+		      ffval.Node = New_Func( 0, isnull_fct, 1, ffvsp[-1].Node, 0, 0,
+				     0, 0, 0, 0 );
+		      TEST(ffval.Node); 
+                      /* Use expression's size, but return BOOLEAN */
+		      TYPE(ffval.Node) = BOOLEAN;
+		   } else {
+		      fferror("Boolean Function(expr) not supported");
+		      FFERROR;
+		   }
+		;
+    break;}
+case 96:
+#line 890 "eval.y"
+{
+		   if (FSTRCMP(ffvsp[-2].str,"ISNULL(") == 0) {
+		      ffval.Node = New_Func( 0, isnull_fct, 1, ffvsp[-1].Node, 0, 0,
+				     0, 0, 0, 0 );
+		      TEST(ffval.Node); 
+                      /* Use expression's size, but return BOOLEAN */
+		      TYPE(ffval.Node) = BOOLEAN;
+		   } else {
+		      fferror("Boolean Function(expr) not supported");
+		      FFERROR;
+		   }
+		;
+    break;}
+case 97:
+#line 903 "eval.y"
+{
+		   if (FSTRCMP(ffvsp[-2].str,"ISNULL(") == 0) {
+		      ffval.Node = New_Func( BOOLEAN, isnull_fct, 1, ffvsp[-1].Node, 0, 0,
+				     0, 0, 0, 0 );
+		      TEST(ffval.Node); 
+		   } else {
+		      fferror("Boolean Function(expr) not supported");
+		      FFERROR;
+		   }
+		;
+    break;}
+case 98:
+#line 914 "eval.y"
+{
+		   if (FSTRCMP(ffvsp[-4].str,"DEFNULL(") == 0) {
+		      if( SIZE(ffvsp[-3].Node)>=SIZE(ffvsp[-1].Node) && Test_Dims( ffvsp[-3].Node, ffvsp[-1].Node ) ) {
+			 ffval.Node = New_Func( 0, defnull_fct, 2, ffvsp[-3].Node, ffvsp[-1].Node, 0,
+					0, 0, 0, 0 );
+			 TEST(ffval.Node); 
+		      } else {
+			 fferror("Dimensions of DEFNULL arguments are not compatible");
+			 FFERROR;
+		      }
+		   } else {
+		      fferror("Boolean Function(expr,expr) not supported");
+		      FFERROR;
+		   }
+		;
+    break;}
+case 99:
+#line 930 "eval.y"
+{
+		   if( TYPE(ffvsp[-5].Node) != DOUBLE ) ffvsp[-5].Node = New_Unary( DOUBLE, 0, ffvsp[-5].Node );
+		   if( TYPE(ffvsp[-3].Node) != DOUBLE ) ffvsp[-3].Node = New_Unary( DOUBLE, 0, ffvsp[-3].Node );
+		   if( TYPE(ffvsp[-1].Node) != DOUBLE ) ffvsp[-1].Node = New_Unary( DOUBLE, 0, ffvsp[-1].Node );
+		   if( ! (Test_Dims( ffvsp[-5].Node, ffvsp[-3].Node ) && Test_Dims( ffvsp[-3].Node, ffvsp[-1].Node ) ) ) {
+		       fferror("Dimensions of NEAR arguments "
+			       "are not compatible");
+		       FFERROR;
+		   } else {
+		     if (FSTRCMP(ffvsp[-6].str,"NEAR(") == 0) {
+		       ffval.Node = New_Func( BOOLEAN, near_fct, 3, ffvsp[-5].Node, ffvsp[-3].Node, ffvsp[-1].Node,
+				      0, 0, 0, 0 );
+		     } else {
+		       fferror("Boolean Function not supported");
+		       FFERROR;
+		     }
+		     TEST(ffval.Node); 
+
+		     if( SIZE(ffval.Node)<SIZE(ffvsp[-5].Node) )  Copy_Dims(ffval.Node, ffvsp[-5].Node);
+		     if( SIZE(ffvsp[-5].Node)<SIZE(ffvsp[-3].Node) )  Copy_Dims(ffval.Node, ffvsp[-3].Node);
+		     if( SIZE(ffvsp[-3].Node)<SIZE(ffvsp[-1].Node) )  Copy_Dims(ffval.Node, ffvsp[-1].Node);
+		   }
+		;
+    break;}
+case 100:
+#line 954 "eval.y"
+{
+		   if( TYPE(ffvsp[-9].Node) != DOUBLE ) ffvsp[-9].Node = New_Unary( DOUBLE, 0, ffvsp[-9].Node );
+		   if( TYPE(ffvsp[-7].Node) != DOUBLE ) ffvsp[-7].Node = New_Unary( DOUBLE, 0, ffvsp[-7].Node );
+		   if( TYPE(ffvsp[-5].Node) != DOUBLE ) ffvsp[-5].Node = New_Unary( DOUBLE, 0, ffvsp[-5].Node );
+		   if( TYPE(ffvsp[-3].Node) != DOUBLE ) ffvsp[-3].Node = New_Unary( DOUBLE, 0, ffvsp[-3].Node );
+		   if( TYPE(ffvsp[-1].Node)!= DOUBLE ) ffvsp[-1].Node= New_Unary( DOUBLE, 0, ffvsp[-1].Node);
+		   if( ! (Test_Dims( ffvsp[-9].Node, ffvsp[-7].Node ) && Test_Dims( ffvsp[-7].Node, ffvsp[-5].Node ) && 
+			  Test_Dims( ffvsp[-5].Node, ffvsp[-3].Node ) && Test_Dims( ffvsp[-3].Node, ffvsp[-1].Node )) ) {
+		     fferror("Dimensions of CIRCLE arguments "
+			     "are not compatible");
+		     FFERROR;
+		   } else {
+		     if (FSTRCMP(ffvsp[-10].str,"CIRCLE(") == 0) {
+		       ffval.Node = New_Func( BOOLEAN, circle_fct, 5, ffvsp[-9].Node, ffvsp[-7].Node, ffvsp[-5].Node, ffvsp[-3].Node,
+				      ffvsp[-1].Node, 0, 0 );
+		     } else {
+		       fferror("Boolean Function not supported");
+		       FFERROR;
+		     }
+		     TEST(ffval.Node); 
+		     if( SIZE(ffval.Node)<SIZE(ffvsp[-9].Node) )  Copy_Dims(ffval.Node, ffvsp[-9].Node);
+		     if( SIZE(ffvsp[-9].Node)<SIZE(ffvsp[-7].Node) )  Copy_Dims(ffval.Node, ffvsp[-7].Node);
+		     if( SIZE(ffvsp[-7].Node)<SIZE(ffvsp[-5].Node) )  Copy_Dims(ffval.Node, ffvsp[-5].Node);
+		     if( SIZE(ffvsp[-5].Node)<SIZE(ffvsp[-3].Node) )  Copy_Dims(ffval.Node, ffvsp[-3].Node);
+		     if( SIZE(ffvsp[-3].Node)<SIZE(ffvsp[-1].Node) ) Copy_Dims(ffval.Node, ffvsp[-1].Node);
+		   }
+		;
+    break;}
+case 101:
+#line 982 "eval.y"
+{
+		   if( TYPE(ffvsp[-13].Node) != DOUBLE ) ffvsp[-13].Node = New_Unary( DOUBLE, 0, ffvsp[-13].Node );
+		   if( TYPE(ffvsp[-11].Node) != DOUBLE ) ffvsp[-11].Node = New_Unary( DOUBLE, 0, ffvsp[-11].Node );
+		   if( TYPE(ffvsp[-9].Node) != DOUBLE ) ffvsp[-9].Node = New_Unary( DOUBLE, 0, ffvsp[-9].Node );
+		   if( TYPE(ffvsp[-7].Node) != DOUBLE ) ffvsp[-7].Node = New_Unary( DOUBLE, 0, ffvsp[-7].Node );
+		   if( TYPE(ffvsp[-5].Node)!= DOUBLE ) ffvsp[-5].Node= New_Unary( DOUBLE, 0, ffvsp[-5].Node);
+		   if( TYPE(ffvsp[-3].Node)!= DOUBLE ) ffvsp[-3].Node= New_Unary( DOUBLE, 0, ffvsp[-3].Node);
+		   if( TYPE(ffvsp[-1].Node)!= DOUBLE ) ffvsp[-1].Node= New_Unary( DOUBLE, 0, ffvsp[-1].Node);
+		   if( ! (Test_Dims( ffvsp[-13].Node, ffvsp[-11].Node ) && Test_Dims( ffvsp[-11].Node, ffvsp[-9].Node ) && 
+			  Test_Dims( ffvsp[-9].Node, ffvsp[-7].Node ) && Test_Dims( ffvsp[-7].Node, ffvsp[-5].Node ) &&
+			  Test_Dims(ffvsp[-5].Node,ffvsp[-3].Node ) && Test_Dims(ffvsp[-3].Node, ffvsp[-1].Node ) ) ) {
+		     fferror("Dimensions of BOX or ELLIPSE arguments "
+			     "are not compatible");
+		     FFERROR;
+		   } else {
+		     if (FSTRCMP(ffvsp[-14].str,"BOX(") == 0) {
+		       ffval.Node = New_Func( BOOLEAN, box_fct, 7, ffvsp[-13].Node, ffvsp[-11].Node, ffvsp[-9].Node, ffvsp[-7].Node,
+				      ffvsp[-5].Node, ffvsp[-3].Node, ffvsp[-1].Node );
+		     } else if (FSTRCMP(ffvsp[-14].str,"ELLIPSE(") == 0) {
+		       ffval.Node = New_Func( BOOLEAN, elps_fct, 7, ffvsp[-13].Node, ffvsp[-11].Node, ffvsp[-9].Node, ffvsp[-7].Node,
+				      ffvsp[-5].Node, ffvsp[-3].Node, ffvsp[-1].Node );
+		     } else {
+		       fferror("SAO Image Function not supported");
+		       FFERROR;
+		     }
+		     TEST(ffval.Node); 
+		     if( SIZE(ffval.Node)<SIZE(ffvsp[-13].Node) )  Copy_Dims(ffval.Node, ffvsp[-13].Node);
+		     if( SIZE(ffvsp[-13].Node)<SIZE(ffvsp[-11].Node) )  Copy_Dims(ffval.Node, ffvsp[-11].Node);
+		     if( SIZE(ffvsp[-11].Node)<SIZE(ffvsp[-9].Node) )  Copy_Dims(ffval.Node, ffvsp[-9].Node);
+		     if( SIZE(ffvsp[-9].Node)<SIZE(ffvsp[-7].Node) )  Copy_Dims(ffval.Node, ffvsp[-7].Node);
+		     if( SIZE(ffvsp[-7].Node)<SIZE(ffvsp[-5].Node) ) Copy_Dims(ffval.Node, ffvsp[-5].Node);
+		     if( SIZE(ffvsp[-5].Node)<SIZE(ffvsp[-3].Node) ) Copy_Dims(ffval.Node, ffvsp[-3].Node);
+		     if( SIZE(ffvsp[-3].Node)<SIZE(ffvsp[-1].Node) ) Copy_Dims(ffval.Node, ffvsp[-1].Node);
+		   }
+		;
+    break;}
+case 102:
+#line 1019 "eval.y"
+{ /* Use defaults for all elements */
+                   ffval.Node = New_GTI( "", -99, "*START*", "*STOP*" );
+                   TEST(ffval.Node);                                        ;
+    break;}
+case 103:
+#line 1023 "eval.y"
+{ /* Use defaults for all except filename */
+                   ffval.Node = New_GTI( ffvsp[-1].str, -99, "*START*", "*STOP*" );
+                   TEST(ffval.Node);                                        ;
+    break;}
+case 104:
+#line 1027 "eval.y"
+{  ffval.Node = New_GTI( ffvsp[-3].str, ffvsp[-1].Node, "*START*", "*STOP*" );
+                   TEST(ffval.Node);                                        ;
+    break;}
+case 105:
+#line 1030 "eval.y"
+{  ffval.Node = New_GTI( ffvsp[-7].str, ffvsp[-5].Node, ffvsp[-3].str, ffvsp[-1].str );
+                   TEST(ffval.Node);                                        ;
+    break;}
+case 106:
+#line 1034 "eval.y"
+{ /* Use defaults for all except filename */
+                   ffval.Node = New_REG( ffvsp[-1].str, -99, -99, "" );
+                   TEST(ffval.Node);                                        ;
+    break;}
+case 107:
+#line 1038 "eval.y"
+{  ffval.Node = New_REG( ffvsp[-5].str, ffvsp[-3].Node, ffvsp[-1].Node, "" );
+                   TEST(ffval.Node);                                        ;
+    break;}
+case 108:
+#line 1041 "eval.y"
+{  ffval.Node = New_REG( ffvsp[-7].str, ffvsp[-5].Node, ffvsp[-3].Node, ffvsp[-1].str );
+                   TEST(ffval.Node);                                        ;
+    break;}
+case 109:
+#line 1045 "eval.y"
+{ ffval.Node = New_Deref( ffvsp[-3].Node, 1, ffvsp[-1].Node,  0,  0,  0,   0 ); TEST(ffval.Node); ;
+    break;}
+case 110:
+#line 1047 "eval.y"
+{ ffval.Node = New_Deref( ffvsp[-5].Node, 2, ffvsp[-3].Node, ffvsp[-1].Node,  0,  0,   0 ); TEST(ffval.Node); ;
+    break;}
+case 111:
+#line 1049 "eval.y"
+{ ffval.Node = New_Deref( ffvsp[-7].Node, 3, ffvsp[-5].Node, ffvsp[-3].Node, ffvsp[-1].Node,  0,   0 ); TEST(ffval.Node); ;
+    break;}
+case 112:
+#line 1051 "eval.y"
+{ ffval.Node = New_Deref( ffvsp[-9].Node, 4, ffvsp[-7].Node, ffvsp[-5].Node, ffvsp[-3].Node, ffvsp[-1].Node,   0 ); TEST(ffval.Node); ;
+    break;}
+case 113:
+#line 1053 "eval.y"
+{ ffval.Node = New_Deref( ffvsp[-11].Node, 5, ffvsp[-9].Node, ffvsp[-7].Node, ffvsp[-5].Node, ffvsp[-3].Node, ffvsp[-1].Node ); TEST(ffval.Node); ;
+    break;}
+case 114:
+#line 1055 "eval.y"
+{ ffval.Node = New_Unary( BOOLEAN, NOT, ffvsp[0].Node ); TEST(ffval.Node); ;
+    break;}
+case 115:
+#line 1057 "eval.y"
+{ ffval.Node = ffvsp[-1].Node; ;
+    break;}
+case 116:
+#line 1061 "eval.y"
+{ ffval.Node = New_Const( STRING, ffvsp[0].str, strlen(ffvsp[0].str)+1 ); TEST(ffval.Node);
+                  SIZE(ffval.Node) = strlen(ffvsp[0].str); ;
+    break;}
+case 117:
+#line 1064 "eval.y"
+{ ffval.Node = New_Column( ffvsp[0].lng ); TEST(ffval.Node); ;
+    break;}
+case 118:
+#line 1066 "eval.y"
+{
+                  if( TYPE(ffvsp[-1].Node) != LONG
+		      || OPER(ffvsp[-1].Node) != CONST_OP ) {
+		     fferror("Offset argument must be a constant integer");
+		     FFERROR;
+		  }
+                  ffval.Node = New_Offset( ffvsp[-3].lng, ffvsp[-1].Node ); TEST(ffval.Node);
+                ;
+    break;}
+case 119:
+#line 1075 "eval.y"
+{ ffval.Node = New_Func( STRING, null_fct, 0, 0, 0, 0, 0, 0, 0, 0 ); ;
+    break;}
+case 120:
+#line 1077 "eval.y"
+{ ffval.Node = ffvsp[-1].Node; ;
+    break;}
+case 121:
+#line 1079 "eval.y"
+{ 
+		  if (SIZE(ffvsp[-2].Node)+SIZE(ffvsp[0].Node) >= MAX_STRLEN) {
+		    fferror("Combined string size exceeds " MAX_STRLEN_S " characters");
+		    FFERROR;
+		  }
+		  ffval.Node = New_BinOp( STRING, ffvsp[-2].Node, '+', ffvsp[0].Node );  TEST(ffval.Node);
+		  SIZE(ffval.Node) = SIZE(ffvsp[-2].Node) + SIZE(ffvsp[0].Node);
+		;
+    break;}
+case 122:
+#line 1088 "eval.y"
+{
+		  int outSize;
+                  if( SIZE(ffvsp[-4].Node)!=1 ) {
+                     fferror("Cannot have a vector string column");
+		     FFERROR;
+                  }
+		  /* Since the output can be calculated now, as a constant
+		     scalar, we must precalculate the output size, in
+		     order to avoid an overflow. */
+		  outSize = SIZE(ffvsp[-2].Node);
+		  if (SIZE(ffvsp[0].Node) > outSize) outSize = SIZE(ffvsp[0].Node);
+                  ffval.Node = New_FuncSize( 0, ifthenelse_fct, 3, ffvsp[-2].Node, ffvsp[0].Node, ffvsp[-4].Node,
+				     0, 0, 0, 0, outSize);
+		  
+                  TEST(ffval.Node);
+                  if( SIZE(ffvsp[-2].Node)<SIZE(ffvsp[0].Node) )  Copy_Dims(ffval.Node, ffvsp[0].Node);
+                ;
+    break;}
+case 123:
+#line 1107 "eval.y"
+{ 
+		  if (FSTRCMP(ffvsp[-4].str,"DEFNULL(") == 0) {
+		     int outSize;
+		     /* Since the output can be calculated now, as a constant
+			scalar, we must precalculate the output size, in
+			order to avoid an overflow. */
+		     outSize = SIZE(ffvsp[-3].Node);
+		     if (SIZE(ffvsp[-1].Node) > outSize) outSize = SIZE(ffvsp[-1].Node);
+		     
+		     ffval.Node = New_FuncSize( 0, defnull_fct, 2, ffvsp[-3].Node, ffvsp[-1].Node, 0,
+					0, 0, 0, 0, outSize );
+		     TEST(ffval.Node); 
+		     if( SIZE(ffvsp[-1].Node)>SIZE(ffvsp[-3].Node) ) SIZE(ffval.Node) = SIZE(ffvsp[-1].Node);
+		  } else {
+		     fferror("Function(string,string) not supported");
+		     FFERROR;
+		  }
+		;
+    break;}
+case 124:
+#line 1126 "eval.y"
+{ 
+		  if (FSTRCMP(ffvsp[-6].str,"STRMID(") == 0) {
+		    int len;
+		    if( TYPE(ffvsp[-3].Node) != LONG || SIZE(ffvsp[-3].Node) != 1 ||
+			TYPE(ffvsp[-1].Node) != LONG || SIZE(ffvsp[-1].Node) != 1) {
+		      fferror("When using STRMID(S,P,N), P and N must be integers (and not vector columns)");
+		      FFERROR;
+		    }
+		    if (OPER(ffvsp[-1].Node) == CONST_OP) {
+		      /* Constant value: use that directly */
+		      len = (gParse.Nodes[ffvsp[-1].Node].value.data.lng);
+		    } else {
+		      /* Variable value: use the maximum possible (from $2) */
+		      len = SIZE(ffvsp[-5].Node);
+		    }
+		    if (len <= 0 || len >= MAX_STRLEN) {
+		      fferror("STRMID(S,P,N), N must be 1-" MAX_STRLEN_S);
+		      FFERROR;
+		    }
+		    ffval.Node = New_FuncSize( 0, strmid_fct, 3, ffvsp[-5].Node, ffvsp[-3].Node,ffvsp[-1].Node,0,0,0,0,len);
+		    TEST(ffval.Node);
+		  } else {
+		     fferror("Function(string,expr,expr) not supported");
+		     FFERROR;
+		  }
+		;
+    break;}
+}
+   /* the action file gets copied in in place of this dollarsign */
+#line 498 "/usr1/local/share/bison.simple"
+
+  ffvsp -= fflen;
+  ffssp -= fflen;
+#ifdef FFLSP_NEEDED
+  fflsp -= fflen;
+#endif
+
+#if FFDEBUG != 0
+  if (ffdebug)
+    {
+      short *ssp1 = ffss - 1;
+      fprintf (stderr, "state stack now");
+      while (ssp1 != ffssp)
+	fprintf (stderr, " %d", *++ssp1);
+      fprintf (stderr, "\n");
+    }
+#endif
+
+  *++ffvsp = ffval;
+
+#ifdef FFLSP_NEEDED
+  fflsp++;
+  if (fflen == 0)
+    {
+      fflsp->first_line = fflloc.first_line;
+      fflsp->first_column = fflloc.first_column;
+      fflsp->last_line = (fflsp-1)->last_line;
+      fflsp->last_column = (fflsp-1)->last_column;
+      fflsp->text = 0;
+    }
+  else
+    {
+      fflsp->last_line = (fflsp+fflen-1)->last_line;
+      fflsp->last_column = (fflsp+fflen-1)->last_column;
+    }
+#endif
+
+  /* Now "shift" the result of the reduction.
+     Determine what state that goes to,
+     based on the state we popped back to
+     and the rule number reduced by.  */
+
+  ffn = ffr1[ffn];
+
+  ffstate = ffpgoto[ffn - FFNTBASE] + *ffssp;
+  if (ffstate >= 0 && ffstate <= FFLAST && ffcheck[ffstate] == *ffssp)
+    ffstate = fftable[ffstate];
+  else
+    ffstate = ffdefgoto[ffn - FFNTBASE];
+
+  goto ffnewstate;
+
+fferrlab:   /* here on detecting error */
+
+  if (! fferrstatus)
+    /* If not already recovering from an error, report this error.  */
+    {
+      ++ffnerrs;
+
+#ifdef FFERROR_VERBOSE
+      ffn = ffpact[ffstate];
+
+      if (ffn > FFFLAG && ffn < FFLAST)
+	{
+	  int size = 0;
+	  char *msg;
+	  int x, count;
+
+	  count = 0;
+	  /* Start X at -ffn if nec to avoid negative indexes in ffcheck.  */
+	  for (x = (ffn < 0 ? -ffn : 0);
+	       x < (sizeof(fftname) / sizeof(char *)); x++)
+	    if (ffcheck[x + ffn] == x)
+	      size += strlen(fftname[x]) + 15, count++;
+	  msg = (char *) malloc(size + 15);
+	  if (msg != 0)
+	    {
+	      strcpy(msg, "parse error");
+
+	      if (count < 5)
+		{
+		  count = 0;
+		  for (x = (ffn < 0 ? -ffn : 0);
+		       x < (sizeof(fftname) / sizeof(char *)); x++)
+		    if (ffcheck[x + ffn] == x)
+		      {
+			strcat(msg, count == 0 ? ", expecting `" : " or `");
+			strcat(msg, fftname[x]);
+			strcat(msg, "'");
+			count++;
+		      }
+		}
+	      fferror(msg);
+	      free(msg);
+	    }
+	  else
+	    fferror ("parse error; also virtual memory exceeded");
+	}
+      else
+#endif /* FFERROR_VERBOSE */
+	fferror("parse error");
+    }
+
+  goto fferrlab1;
+fferrlab1:   /* here on error raised explicitly by an action */
+
+  if (fferrstatus == 3)
+    {
+      /* if just tried and failed to reuse lookahead token after an error, discard it.  */
+
+      /* return failure if at end of input */
+      if (ffchar == FFEOF)
+	FFABORT;
+
+#if FFDEBUG != 0
+      if (ffdebug)
+	fprintf(stderr, "Discarding token %d (%s).\n", ffchar, fftname[ffchar1]);
+#endif
+
+      ffchar = FFEMPTY;
+    }
+
+  /* Else will try to reuse lookahead token
+     after shifting the error token.  */
+
+  fferrstatus = 3;		/* Each real token shifted decrements this */
+
+  goto fferrhandle;
+
+fferrdefault:  /* current state does not do anything special for the error token. */
+
+#if 0
+  /* This is wrong; only states that explicitly want error tokens
+     should shift them.  */
+  ffn = ffdefact[ffstate];  /* If its default is to accept any token, ok.  Otherwise pop it.*/
+  if (ffn) goto ffdefault;
+#endif
+
+fferrpop:   /* pop the current state because it cannot handle the error token */
+
+  if (ffssp == ffss) FFABORT;
+  ffvsp--;
+  ffstate = *--ffssp;
+#ifdef FFLSP_NEEDED
+  fflsp--;
+#endif
+
+#if FFDEBUG != 0
+  if (ffdebug)
+    {
+      short *ssp1 = ffss - 1;
+      fprintf (stderr, "Error: state stack now");
+      while (ssp1 != ffssp)
+	fprintf (stderr, " %d", *++ssp1);
+      fprintf (stderr, "\n");
+    }
+#endif
+
+fferrhandle:
+
+  ffn = ffpact[ffstate];
+  if (ffn == FFFLAG)
+    goto fferrdefault;
+
+  ffn += FFTERROR;
+  if (ffn < 0 || ffn > FFLAST || ffcheck[ffn] != FFTERROR)
+    goto fferrdefault;
+
+  ffn = fftable[ffn];
+  if (ffn < 0)
+    {
+      if (ffn == FFFLAG)
+	goto fferrpop;
+      ffn = -ffn;
+      goto ffreduce;
+    }
+  else if (ffn == 0)
+    goto fferrpop;
+
+  if (ffn == FFFINAL)
+    FFACCEPT;
+
+#if FFDEBUG != 0
+  if (ffdebug)
+    fprintf(stderr, "Shifting error token, ");
+#endif
+
+  *++ffvsp = fflval;
+#ifdef FFLSP_NEEDED
+  *++fflsp = fflloc;
+#endif
+
+  ffstate = ffn;
+  goto ffnewstate;
+}
+#line 1155 "eval.y"
+
+
+/*************************************************************************/
+/*  Start of "New" routines which build the expression Nodal structure   */
+/*************************************************************************/
+
+static int Alloc_Node( void )
+{
+                      /* Use this for allocation to guarantee *Nodes */
+   Node *newNodePtr;  /* survives on failure, making it still valid  */
+                      /* while working our way out of this error     */
+
+   if( gParse.nNodes == gParse.nNodesAlloc ) {
+      if( gParse.Nodes ) {
+	 gParse.nNodesAlloc += gParse.nNodesAlloc;
+	 newNodePtr = (Node *)realloc( gParse.Nodes,
+				       sizeof(Node)*gParse.nNodesAlloc );
+      } else {
+	 gParse.nNodesAlloc = 100;
+	 newNodePtr = (Node *)malloc ( sizeof(Node)*gParse.nNodesAlloc );
+      }	 
+
+      if( newNodePtr ) {
+	 gParse.Nodes = newNodePtr;
+      } else {
+	 gParse.status = MEMORY_ALLOCATION;
+	 return( -1 );
+      }
+   }
+
+   return ( gParse.nNodes++ );
+}
+
+static void Free_Last_Node( void )
+{
+   if( gParse.nNodes ) gParse.nNodes--;
+}
+
+static int New_Const( int returnType, void *value, long len )
+{
+   Node *this;
+   int n;
+
+   n = Alloc_Node();
+   if( n>=0 ) {
+      this             = gParse.Nodes + n;
+      this->operation  = CONST_OP;             /* Flag a constant */
+      this->DoOp       = NULL;
+      this->nSubNodes  = 0;
+      this->type       = returnType;
+      memcpy( &(this->value.data), value, len );
+      this->value.undef = NULL;
+      this->value.nelem = 1;
+      this->value.naxis = 1;
+      this->value.naxes[0] = 1;
+   }
+   return(n);
+}
+
+static int New_Column( int ColNum )
+{
+   Node *this;
+   int  n, i;
+
+   n = Alloc_Node();
+   if( n>=0 ) {
+      this              = gParse.Nodes + n;
+      this->operation   = -ColNum;
+      this->DoOp        = NULL;
+      this->nSubNodes   = 0;
+      this->type        = gParse.varData[ColNum].type;
+      this->value.nelem = gParse.varData[ColNum].nelem;
+      this->value.naxis = gParse.varData[ColNum].naxis;
+      for( i=0; i<gParse.varData[ColNum].naxis; i++ )
+	 this->value.naxes[i] = gParse.varData[ColNum].naxes[i];
+   }
+   return(n);
+}
+
+static int New_Offset( int ColNum, int offsetNode )
+{
+   Node *this;
+   int  n, i, colNode;
+
+   colNode = New_Column( ColNum );
+   if( colNode<0 ) return(-1);
+
+   n = Alloc_Node();
+   if( n>=0 ) {
+      this              = gParse.Nodes + n;
+      this->operation   = '{';
+      this->DoOp        = Do_Offset;
+      this->nSubNodes   = 2;
+      this->SubNodes[0] = colNode;
+      this->SubNodes[1] = offsetNode;
+      this->type        = gParse.varData[ColNum].type;
+      this->value.nelem = gParse.varData[ColNum].nelem;
+      this->value.naxis = gParse.varData[ColNum].naxis;
+      for( i=0; i<gParse.varData[ColNum].naxis; i++ )
+	 this->value.naxes[i] = gParse.varData[ColNum].naxes[i];
+   }
+   return(n);
+}
+
+static int New_Unary( int returnType, int Op, int Node1 )
+{
+   Node *this, *that;
+   int  i,n;
+
+   if( Node1<0 ) return(-1);
+   that = gParse.Nodes + Node1;
+
+   if( !Op ) Op = returnType;
+
+   if( (Op==DOUBLE || Op==FLTCAST) && that->type==DOUBLE  ) return( Node1 );
+   if( (Op==LONG   || Op==INTCAST) && that->type==LONG    ) return( Node1 );
+   if( (Op==BOOLEAN              ) && that->type==BOOLEAN ) return( Node1 );
+   
+   n = Alloc_Node();
+   if( n>=0 ) {
+      this              = gParse.Nodes + n;
+      this->operation   = Op;
+      this->DoOp        = Do_Unary;
+      this->nSubNodes   = 1;
+      this->SubNodes[0] = Node1;
+      this->type        = returnType;
+
+      that              = gParse.Nodes + Node1; /* Reset in case .Nodes mv'd */
+      this->value.nelem = that->value.nelem;
+      this->value.naxis = that->value.naxis;
+      for( i=0; i<that->value.naxis; i++ )
+	 this->value.naxes[i] = that->value.naxes[i];
+
+      if( that->operation==CONST_OP ) this->DoOp( this );
+   }
+   return( n );
+}
+
+static int New_BinOp( int returnType, int Node1, int Op, int Node2 )
+{
+   Node *this,*that1,*that2;
+   int  n,i,constant;
+
+   if( Node1<0 || Node2<0 ) return(-1);
+
+   n = Alloc_Node();
+   if( n>=0 ) {
+      this             = gParse.Nodes + n;
+      this->operation  = Op;
+      this->nSubNodes  = 2;
+      this->SubNodes[0]= Node1;
+      this->SubNodes[1]= Node2;
+      this->type       = returnType;
+
+      that1            = gParse.Nodes + Node1;
+      that2            = gParse.Nodes + Node2;
+      constant         = (that1->operation==CONST_OP
+                          && that2->operation==CONST_OP);
+      if( that1->type!=STRING && that1->type!=BITSTR )
+	 if( !Test_Dims( Node1, Node2 ) ) {
+	    Free_Last_Node();
+	    fferror("Array sizes/dims do not match for binary operator");
+	    return(-1);
+	 }
+      if( that1->value.nelem == 1 ) that1 = that2;
+
+      this->value.nelem = that1->value.nelem;
+      this->value.naxis = that1->value.naxis;
+      for( i=0; i<that1->value.naxis; i++ )
+	 this->value.naxes[i] = that1->value.naxes[i];
+
+      if ( Op == ACCUM && that1->type == BITSTR ) {
+	/* ACCUM is rank-reducing on bit strings */
+	this->value.nelem = 1;
+	this->value.naxis = 1;
+	this->value.naxes[0] = 1;
+      }
+
+      /*  Both subnodes should be of same time  */
+      switch( that1->type ) {
+      case BITSTR:  this->DoOp = Do_BinOp_bit;  break;
+      case STRING:  this->DoOp = Do_BinOp_str;  break;
+      case BOOLEAN: this->DoOp = Do_BinOp_log;  break;
+      case LONG:    this->DoOp = Do_BinOp_lng;  break;
+      case DOUBLE:  this->DoOp = Do_BinOp_dbl;  break;
+      }
+      if( constant ) this->DoOp( this );
+   }
+   return( n );
+}
+
+static int New_Func( int returnType, funcOp Op, int nNodes,
+		     int Node1, int Node2, int Node3, int Node4, 
+		     int Node5, int Node6, int Node7 )
+{
+  return New_FuncSize(returnType, Op, nNodes,
+		      Node1, Node2, Node3, Node4, 
+		      Node5, Node6, Node7, 0);
+}
+
+static int New_FuncSize( int returnType, funcOp Op, int nNodes,
+		     int Node1, int Node2, int Node3, int Node4, 
+			 int Node5, int Node6, int Node7, int Size )
+/* If returnType==0 , use Node1's type and vector sizes as returnType, */
+/* else return a single value of type returnType                       */
+{
+   Node *this, *that;
+   int  i,n,constant;
+
+   if( Node1<0 || Node2<0 || Node3<0 || Node4<0 || 
+       Node5<0 || Node6<0 || Node7<0 ) return(-1);
+
+   n = Alloc_Node();
+   if( n>=0 ) {
+      this              = gParse.Nodes + n;
+      this->operation   = (int)Op;
+      this->DoOp        = Do_Func;
+      this->nSubNodes   = nNodes;
+      this->SubNodes[0] = Node1;
+      this->SubNodes[1] = Node2;
+      this->SubNodes[2] = Node3;
+      this->SubNodes[3] = Node4;
+      this->SubNodes[4] = Node5;
+      this->SubNodes[5] = Node6;
+      this->SubNodes[6] = Node7;
+      i = constant = nNodes;    /* Functions with zero params are not const */
+      if (Op == poirnd_fct) constant = 0; /* Nor is Poisson deviate */
+
+      while( i-- )
+	constant = ( constant && OPER(this->SubNodes[i]) == CONST_OP );
+      
+      if( returnType ) {
+	 this->type           = returnType;
+	 this->value.nelem    = 1;
+	 this->value.naxis    = 1;
+	 this->value.naxes[0] = 1;
+      } else {
+	 that              = gParse.Nodes + Node1;
+	 this->type        = that->type;
+	 this->value.nelem = that->value.nelem;
+	 this->value.naxis = that->value.naxis;
+	 for( i=0; i<that->value.naxis; i++ )
+	    this->value.naxes[i] = that->value.naxes[i];
+      }
+      /* Force explicit size before evaluating */
+      if (Size > 0) this->value.nelem = Size;
+
+      if( constant ) this->DoOp( this );
+   }
+   return( n );
+}
+
+static int New_Deref( int Var,  int nDim,
+		      int Dim1, int Dim2, int Dim3, int Dim4, int Dim5 )
+{
+   int n, idx, constant;
+   long elem=0;
+   Node *this, *theVar, *theDim[MAXDIMS];
+
+   if( Var<0 || Dim1<0 || Dim2<0 || Dim3<0 || Dim4<0 || Dim5<0 ) return(-1);
+
+   theVar = gParse.Nodes + Var;
+   if( theVar->operation==CONST_OP || theVar->value.nelem==1 ) {
+      fferror("Cannot index a scalar value");
+      return(-1);
+   }
+
+   n = Alloc_Node();
+   if( n>=0 ) {
+      this              = gParse.Nodes + n;
+      this->nSubNodes   = nDim+1;
+      theVar            = gParse.Nodes + (this->SubNodes[0]=Var);
+      theDim[0]         = gParse.Nodes + (this->SubNodes[1]=Dim1);
+      theDim[1]         = gParse.Nodes + (this->SubNodes[2]=Dim2);
+      theDim[2]         = gParse.Nodes + (this->SubNodes[3]=Dim3);
+      theDim[3]         = gParse.Nodes + (this->SubNodes[4]=Dim4);
+      theDim[4]         = gParse.Nodes + (this->SubNodes[5]=Dim5);
+      constant          = theVar->operation==CONST_OP;
+      for( idx=0; idx<nDim; idx++ )
+	 constant = (constant && theDim[idx]->operation==CONST_OP);
+
+      for( idx=0; idx<nDim; idx++ )
+	 if( theDim[idx]->value.nelem>1 ) {
+	    Free_Last_Node();
+	    fferror("Cannot use an array as an index value");
+	    return(-1);
+	 } else if( theDim[idx]->type!=LONG ) {
+	    Free_Last_Node();
+	    fferror("Index value must be an integer type");
+	    return(-1);
+	 }
+
+      this->operation   = '[';
+      this->DoOp        = Do_Deref;
+      this->type        = theVar->type;
+
+      if( theVar->value.naxis == nDim ) { /* All dimensions specified */
+	 this->value.nelem    = 1;
+	 this->value.naxis    = 1;
+	 this->value.naxes[0] = 1;
+      } else if( nDim==1 ) { /* Dereference only one dimension */
+	 elem=1;
+	 this->value.naxis = theVar->value.naxis-1;
+	 for( idx=0; idx<this->value.naxis; idx++ ) {
+	    elem *= ( this->value.naxes[idx] = theVar->value.naxes[idx] );
+	 }
+	 this->value.nelem = elem;
+      } else {
+	 Free_Last_Node();
+	 fferror("Must specify just one or all indices for vector");
+	 return(-1);
+      }
+      if( constant ) this->DoOp( this );
+   }
+   return(n);
+}
+
+extern int ffGetVariable( char *varName, FFSTYPE *varVal );
+
+static int New_GTI( char *fname, int Node1, char *start, char *stop )
+{
+   fitsfile *fptr;
+   Node *this, *that0, *that1;
+   int  type,i,n, startCol, stopCol, Node0;
+   int  hdutype, hdunum, evthdu, samefile, extvers, movetotype, tstat;
+   char extname[100];
+   long nrows;
+   double timeZeroI[2], timeZeroF[2], dt, timeSpan;
+   char xcol[20], xexpr[20];
+   FFSTYPE colVal;
+
+   if( Node1==-99 ) {
+      type = ffGetVariable( "TIME", &colVal );
+      if( type==COLUMN ) {
+	 Node1 = New_Column( (int)colVal.lng );
+      } else {
+	 fferror("Could not build TIME column for GTIFILTER");
+	 return(-1);
+      }
+   }
+   Node1 = New_Unary( DOUBLE, 0, Node1 );
+   Node0 = Alloc_Node(); /* This will hold the START/STOP times */
+   if( Node1<0 || Node0<0 ) return(-1);
+
+   /*  Record current HDU number in case we need to move within this file  */
+
+   fptr = gParse.def_fptr;
+   ffghdn( fptr, &evthdu );
+
+   /*  Look for TIMEZERO keywords in current extension  */
+
+   tstat = 0;
+   if( ffgkyd( fptr, "TIMEZERO", timeZeroI, NULL, &tstat ) ) {
+      tstat = 0;
+      if( ffgkyd( fptr, "TIMEZERI", timeZeroI, NULL, &tstat ) ) {
+	 timeZeroI[0] = timeZeroF[0] = 0.0;
+      } else if( ffgkyd( fptr, "TIMEZERF", timeZeroF, NULL, &tstat ) ) {
+	 timeZeroF[0] = 0.0;
+      }
+   } else {
+      timeZeroF[0] = 0.0;
+   }
+
+   /*  Resolve filename parameter  */
+
+   switch( fname[0] ) {
+   case '\0':
+      samefile = 1;
+      hdunum = 1;
+      break;
+   case '[':
+      samefile = 1;
+      i = 1;
+      while( fname[i] != '\0' && fname[i] != ']' ) i++;
+      if( fname[i] ) {
+	 fname[i] = '\0';
+	 fname++;
+	 ffexts( fname, &hdunum, extname, &extvers, &movetotype,
+		 xcol, xexpr, &gParse.status );
+         if( *extname ) {
+	    ffmnhd( fptr, movetotype, extname, extvers, &gParse.status );
+	    ffghdn( fptr, &hdunum );
+	 } else if( hdunum ) {
+	    ffmahd( fptr, ++hdunum, &hdutype, &gParse.status );
+	 } else if( !gParse.status ) {
+	    fferror("Cannot use primary array for GTI filter");
+	    return( -1 );
+	 }
+      } else {
+	 fferror("File extension specifier lacks closing ']'");
+	 return( -1 );
+      }
+      break;
+   case '+':
+      samefile = 1;
+      hdunum = atoi( fname ) + 1;
+      if( hdunum>1 )
+	 ffmahd( fptr, hdunum, &hdutype, &gParse.status );
+      else {
+	 fferror("Cannot use primary array for GTI filter");
+	 return( -1 );
+      }
+      break;
+   default:
+      samefile = 0;
+      if( ! ffopen( &fptr, fname, READONLY, &gParse.status ) )
+	 ffghdn( fptr, &hdunum );
+      break;
+   }
+   if( gParse.status ) return(-1);
+
+   /*  If at primary, search for GTI extension  */
+
+   if( hdunum==1 ) {
+      while( 1 ) {
+	 hdunum++;
+	 if( ffmahd( fptr, hdunum, &hdutype, &gParse.status ) ) break;
+	 if( hdutype==IMAGE_HDU ) continue;
+	 tstat = 0;
+	 if( ffgkys( fptr, "EXTNAME", extname, NULL, &tstat ) ) continue;
+	 ffupch( extname );
+	 if( strstr( extname, "GTI" ) ) break;
+      }
+      if( gParse.status ) {
+	 if( gParse.status==END_OF_FILE )
+	    fferror("GTI extension not found in this file");
+	 return(-1);
+      }
+   }
+
+   /*  Locate START/STOP Columns  */
+
+   ffgcno( fptr, CASEINSEN, start, &startCol, &gParse.status );
+   ffgcno( fptr, CASEINSEN, stop,  &stopCol,  &gParse.status );
+   if( gParse.status ) return(-1);
+
+   /*  Look for TIMEZERO keywords in GTI extension  */
+
+   tstat = 0;
+   if( ffgkyd( fptr, "TIMEZERO", timeZeroI+1, NULL, &tstat ) ) {
+      tstat = 0;
+      if( ffgkyd( fptr, "TIMEZERI", timeZeroI+1, NULL, &tstat ) ) {
+	 timeZeroI[1] = timeZeroF[1] = 0.0;
+      } else if( ffgkyd( fptr, "TIMEZERF", timeZeroF+1, NULL, &tstat ) ) {
+	 timeZeroF[1] = 0.0;
+      }
+   } else {
+      timeZeroF[1] = 0.0;
+   }
+
+   n = Alloc_Node();
+   if( n >= 0 ) {
+      this                 = gParse.Nodes + n;
+      this->nSubNodes      = 2;
+      this->SubNodes[1]    = Node1;
+      this->operation      = (int)gtifilt_fct;
+      this->DoOp           = Do_GTI;
+      this->type           = BOOLEAN;
+      that1                = gParse.Nodes + Node1;
+      this->value.nelem    = that1->value.nelem;
+      this->value.naxis    = that1->value.naxis;
+      for( i=0; i < that1->value.naxis; i++ )
+	 this->value.naxes[i] = that1->value.naxes[i];
+
+      /* Init START/STOP node to be treated as a "constant" */
+
+      this->SubNodes[0]    = Node0;
+      that0                = gParse.Nodes + Node0;
+      that0->operation     = CONST_OP;
+      that0->DoOp          = NULL;
+      that0->value.data.ptr= NULL;
+
+      /*  Read in START/STOP times  */
+
+      if( ffgkyj( fptr, "NAXIS2", &nrows, NULL, &gParse.status ) )
+	 return(-1);
+      that0->value.nelem = nrows;
+      if( nrows ) {
+
+	 that0->value.data.dblptr = (double*)malloc( 2*nrows*sizeof(double) );
+	 if( !that0->value.data.dblptr ) {
+	    gParse.status = MEMORY_ALLOCATION;
+	    return(-1);
+	 }
+	 
+	 ffgcvd( fptr, startCol, 1L, 1L, nrows, 0.0,
+		 that0->value.data.dblptr, &i, &gParse.status );
+	 ffgcvd( fptr, stopCol, 1L, 1L, nrows, 0.0,
+		 that0->value.data.dblptr+nrows, &i, &gParse.status );
+	 if( gParse.status ) {
+	    free( that0->value.data.dblptr );
+	    return(-1);
+	 }
+
+	 /*  Test for fully time-ordered GTI... both START && STOP  */
+
+	 that0->type = 1; /*  Assume yes  */
+	 i = nrows;
+	 while( --i )
+	    if(    that0->value.data.dblptr[i-1]
+                   >= that0->value.data.dblptr[i]
+		|| that0->value.data.dblptr[i-1+nrows]
+		   >= that0->value.data.dblptr[i+nrows] ) {
+	       that0->type = 0;
+	       break;
+	    }
+	 
+	 /*  Handle TIMEZERO offset, if any  */
+	 
+	 dt = (timeZeroI[1] - timeZeroI[0]) + (timeZeroF[1] - timeZeroF[0]);
+	 timeSpan = that0->value.data.dblptr[nrows+nrows-1]
+	    - that0->value.data.dblptr[0];
+	 
+	 if( fabs( dt / timeSpan ) > 1e-12 ) {
+	    for( i=0; i<(nrows+nrows); i++ )
+	       that0->value.data.dblptr[i] += dt;
+	 }
+      }
+      if( OPER(Node1)==CONST_OP )
+	 this->DoOp( this );
+   }
+
+   if( samefile )
+      ffmahd( fptr, evthdu, &hdutype, &gParse.status );
+   else
+      ffclos( fptr, &gParse.status );
+
+   return( n );
+}
+
+static int New_REG( char *fname, int NodeX, int NodeY, char *colNames )
+{
+   Node *this, *that0;
+   int  type, n, Node0;
+   int  Xcol, Ycol, tstat;
+   WCSdata wcs;
+   SAORegion *Rgn;
+   char *cX, *cY;
+   FFSTYPE colVal;
+
+   if( NodeX==-99 ) {
+      type = ffGetVariable( "X", &colVal );
+      if( type==COLUMN ) {
+	 NodeX = New_Column( (int)colVal.lng );
+      } else {
+	 fferror("Could not build X column for REGFILTER");
+	 return(-1);
+      }
+   }
+   if( NodeY==-99 ) {
+      type = ffGetVariable( "Y", &colVal );
+      if( type==COLUMN ) {
+	 NodeY = New_Column( (int)colVal.lng );
+      } else {
+	 fferror("Could not build Y column for REGFILTER");
+	 return(-1);
+      }
+   }
+   NodeX = New_Unary( DOUBLE, 0, NodeX );
+   NodeY = New_Unary( DOUBLE, 0, NodeY );
+   Node0 = Alloc_Node(); /* This will hold the Region Data */
+   if( NodeX<0 || NodeY<0 || Node0<0 ) return(-1);
+
+   if( ! (Test_Dims( NodeX, NodeY ) ) ) {
+     fferror("Dimensions of REGFILTER arguments are not compatible");
+     return (-1);
+   }
+
+   n = Alloc_Node();
+   if( n >= 0 ) {
+      this                 = gParse.Nodes + n;
+      this->nSubNodes      = 3;
+      this->SubNodes[0]    = Node0;
+      this->SubNodes[1]    = NodeX;
+      this->SubNodes[2]    = NodeY;
+      this->operation      = (int)regfilt_fct;
+      this->DoOp           = Do_REG;
+      this->type           = BOOLEAN;
+      this->value.nelem    = 1;
+      this->value.naxis    = 1;
+      this->value.naxes[0] = 1;
+      
+      Copy_Dims(n, NodeX);
+      if( SIZE(NodeX)<SIZE(NodeY) )  Copy_Dims(n, NodeY);
+
+      /* Init Region node to be treated as a "constant" */
+
+      that0                = gParse.Nodes + Node0;
+      that0->operation     = CONST_OP;
+      that0->DoOp          = NULL;
+
+      /*  Identify what columns to use for WCS information  */
+
+      Xcol = Ycol = 0;
+      if( *colNames ) {
+	 /*  Use the column names in this string for WCS info  */
+	 while( *colNames==' ' ) colNames++;
+	 cX = cY = colNames;
+	 while( *cY && *cY!=' ' && *cY!=',' ) cY++;
+	 if( *cY )
+	    *(cY++) = '\0';
+	 while( *cY==' ' ) cY++;
+	 if( !*cY ) {
+	    fferror("Could not extract valid pair of column names from REGFILTER");
+	    Free_Last_Node();
+	    return( -1 );
+	 }
+	 fits_get_colnum( gParse.def_fptr, CASEINSEN, cX, &Xcol,
+			  &gParse.status );
+	 fits_get_colnum( gParse.def_fptr, CASEINSEN, cY, &Ycol,
+			  &gParse.status );
+	 if( gParse.status ) {
+	    fferror("Could not locate columns indicated for WCS info");
+	    Free_Last_Node();
+	    return( -1 );
+	 }
+
+      } else {
+	 /*  Try to find columns used in X/Y expressions  */
+	 Xcol = Locate_Col( gParse.Nodes + NodeX );
+	 Ycol = Locate_Col( gParse.Nodes + NodeY );
+	 if( Xcol<0 || Ycol<0 ) {
+	    fferror("Found multiple X/Y column references in REGFILTER");
+	    Free_Last_Node();
+	    return( -1 );
+	 }
+      }
+
+      /*  Now, get the WCS info, if it exists, from the indicated columns  */
+      wcs.exists = 0;
+      if( Xcol>0 && Ycol>0 ) {
+	 tstat = 0;
+	 ffgtcs( gParse.def_fptr, Xcol, Ycol,
+		 &wcs.xrefval, &wcs.yrefval,
+		 &wcs.xrefpix, &wcs.yrefpix,
+		 &wcs.xinc,    &wcs.yinc,
+		 &wcs.rot,      wcs.type,
+		 &tstat );
+	 if( tstat==NO_WCS_KEY ) {
+	    wcs.exists = 0;
+	 } else if( tstat ) {
+	    gParse.status = tstat;
+	    Free_Last_Node();
+	    return( -1 );
+	 } else {
+	    wcs.exists = 1;
+	 }
+      }
+
+      /*  Read in Region file  */
+
+      fits_read_rgnfile( fname, &wcs, &Rgn, &gParse.status );
+      if( gParse.status ) {
+	 Free_Last_Node();
+	 return( -1 );
+      }
+
+      that0->value.data.ptr = Rgn;
+
+      if( OPER(NodeX)==CONST_OP && OPER(NodeY)==CONST_OP )
+	 this->DoOp( this );
+   }
+
+   return( n );
+}
+
+static int New_Vector( int subNode )
+{
+   Node *this, *that;
+   int n;
+
+   n = Alloc_Node();
+   if( n >= 0 ) {
+      this              = gParse.Nodes + n;
+      that              = gParse.Nodes + subNode;
+      this->type        = that->type;
+      this->nSubNodes   = 1;
+      this->SubNodes[0] = subNode;
+      this->operation   = '{';
+      this->DoOp        = Do_Vector;
+   }
+
+   return( n );
+}
+
+static int Close_Vec( int vecNode )
+{
+   Node *this;
+   int n, nelem=0;
+
+   this = gParse.Nodes + vecNode;
+   for( n=0; n < this->nSubNodes; n++ ) {
+      if( TYPE( this->SubNodes[n] ) != this->type ) {
+	 this->SubNodes[n] = New_Unary( this->type, 0, this->SubNodes[n] );
+	 if( this->SubNodes[n]<0 ) return(-1);
+      }
+      nelem += SIZE(this->SubNodes[n]);
+   }
+   this->value.naxis    = 1;
+   this->value.nelem    = nelem;
+   this->value.naxes[0] = nelem;
+
+   return( vecNode );
+}
+
+static int Locate_Col( Node *this )
+/*  Locate the TABLE column number of any columns in "this" calculation.  */
+/*  Return ZERO if none found, or negative if more than 1 found.          */
+{
+   Node *that;
+   int  i, col=0, newCol, nfound=0;
+   
+   if( this->nSubNodes==0
+       && this->operation<=0 && this->operation!=CONST_OP )
+      return gParse.colData[ - this->operation].colnum;
+
+   for( i=0; i<this->nSubNodes; i++ ) {
+      that = gParse.Nodes + this->SubNodes[i];
+      if( that->operation>0 ) {
+	 newCol = Locate_Col( that );
+	 if( newCol<=0 ) {
+	    nfound += -newCol;
+	 } else {
+	    if( !nfound ) {
+	       col = newCol;
+	       nfound++;
+	    } else if( col != newCol ) {
+	       nfound++;
+	    }
+	 }
+      } else if( that->operation!=CONST_OP ) {
+	 /*  Found a Column  */
+	 newCol = gParse.colData[- that->operation].colnum;
+	 if( !nfound ) {
+	    col = newCol;
+	    nfound++;
+	 } else if( col != newCol ) {
+	    nfound++;
+	 }
+      }
+   }
+   if( nfound!=1 )
+      return( - nfound );
+   else
+      return( col );
+}
+
+static int Test_Dims( int Node1, int Node2 )
+{
+   Node *that1, *that2;
+   int valid, i;
+
+   if( Node1<0 || Node2<0 ) return(0);
+
+   that1 = gParse.Nodes + Node1;
+   that2 = gParse.Nodes + Node2;
+
+   if( that1->value.nelem==1 || that2->value.nelem==1 )
+      valid = 1;
+   else if( that1->type==that2->type
+	    && that1->value.nelem==that2->value.nelem
+	    && that1->value.naxis==that2->value.naxis ) {
+      valid = 1;
+      for( i=0; i<that1->value.naxis; i++ ) {
+	 if( that1->value.naxes[i]!=that2->value.naxes[i] )
+	    valid = 0;
+      }
+   } else
+      valid = 0;
+   return( valid );
+}   
+
+static void Copy_Dims( int Node1, int Node2 )
+{
+   Node *that1, *that2;
+   int i;
+
+   if( Node1<0 || Node2<0 ) return;
+
+   that1 = gParse.Nodes + Node1;
+   that2 = gParse.Nodes + Node2;
+
+   that1->value.nelem = that2->value.nelem;
+   that1->value.naxis = that2->value.naxis;
+   for( i=0; i<that2->value.naxis; i++ )
+      that1->value.naxes[i] = that2->value.naxes[i];
+}
+
+/********************************************************************/
+/*    Routines for actually evaluating the expression start here    */
+/********************************************************************/
+
+void Evaluate_Parser( long firstRow, long nRows )
+    /***********************************************************************/
+    /*  Reset the parser for processing another batch of data...           */
+    /*    firstRow:  Row number of the first element to evaluate           */
+    /*    nRows:     Number of rows to be processed                        */
+    /*  Initialize each COLUMN node so that its UNDEF and DATA pointers    */
+    /*  point to the appropriate column arrays.                            */
+    /*  Finally, call Evaluate_Node for final node.                        */
+    /***********************************************************************/
+{
+   int     i, column;
+   long    offset, rowOffset;
+
+   gParse.firstRow = firstRow;
+   gParse.nRows    = nRows;
+
+   /*  Reset Column Nodes' pointers to point to right data and UNDEF arrays  */
+
+   rowOffset = firstRow - gParse.firstDataRow;
+   for( i=0; i<gParse.nNodes; i++ ) {
+     if(    OPER(i) >  0 || OPER(i) == CONST_OP ) continue;
+
+      column = -OPER(i);
+      offset = gParse.varData[column].nelem * rowOffset;
+
+      gParse.Nodes[i].value.undef = gParse.varData[column].undef + offset;
+
+      switch( gParse.Nodes[i].type ) {
+      case BITSTR:
+	 gParse.Nodes[i].value.data.strptr =
+	    (char**)gParse.varData[column].data + rowOffset;
+	 gParse.Nodes[i].value.undef       = NULL;
+	 break;
+      case STRING:
+	 gParse.Nodes[i].value.data.strptr = 
+	    (char**)gParse.varData[column].data + rowOffset;
+	 gParse.Nodes[i].value.undef = gParse.varData[column].undef + rowOffset;
+	 break;
+      case BOOLEAN:
+	 gParse.Nodes[i].value.data.logptr = 
+	    (char*)gParse.varData[column].data + offset;
+	 break;
+      case LONG:
+	 gParse.Nodes[i].value.data.lngptr = 
+	    (long*)gParse.varData[column].data + offset;
+	 break;
+      case DOUBLE:
+	 gParse.Nodes[i].value.data.dblptr = 
+	    (double*)gParse.varData[column].data + offset;
+	 break;
+      }
+   }
+
+   Evaluate_Node( gParse.resultNode );
+}
+
+static void Evaluate_Node( int thisNode )
+    /**********************************************************************/
+    /*  Recursively evaluate thisNode's subNodes, then call one of the    */
+    /*  Do_<Action> functions pointed to by thisNode's DoOp element.      */
+    /**********************************************************************/
+{
+   Node *this;
+   int i;
+   
+   if( gParse.status ) return;
+
+   this = gParse.Nodes + thisNode;
+   if( this->operation>0 ) {  /* <=0 indicate constants and columns */
+      i = this->nSubNodes;
+      while( i-- ) {
+	 Evaluate_Node( this->SubNodes[i] );
+	 if( gParse.status ) return;
+      }
+      this->DoOp( this );
+   }
+}
+
+static void Allocate_Ptrs( Node *this )
+{
+   long elem, row, size;
+
+   if( this->type==BITSTR || this->type==STRING ) {
+
+      this->value.data.strptr = (char**)malloc( gParse.nRows
+						* sizeof(char*) );
+      if( this->value.data.strptr ) {
+	 this->value.data.strptr[0] = (char*)malloc( gParse.nRows
+						     * (this->value.nelem+2)
+						     * sizeof(char) );
+	 if( this->value.data.strptr[0] ) {
+	    row = 0;
+	    while( (++row)<gParse.nRows ) {
+	       this->value.data.strptr[row] =
+		  this->value.data.strptr[row-1] + this->value.nelem+1;
+	    }
+	    if( this->type==STRING ) {
+	       this->value.undef = this->value.data.strptr[row-1]
+                                   + this->value.nelem+1;
+	    } else {
+	       this->value.undef = NULL;  /* BITSTRs don't use undef array */
+	    }
+	 } else {
+	    gParse.status = MEMORY_ALLOCATION;
+	    free( this->value.data.strptr );
+	 }
+      } else {
+	 gParse.status = MEMORY_ALLOCATION;
+      }
+
+   } else {
+
+      elem = this->value.nelem * gParse.nRows;
+      switch( this->type ) {
+      case DOUBLE:  size = sizeof( double ); break;
+      case LONG:    size = sizeof( long   ); break;
+      case BOOLEAN: size = sizeof( char   ); break;
+      default:      size = 1;                break;
+      }
+
+      this->value.data.ptr = calloc(size+1, elem);
+
+      if( this->value.data.ptr==NULL ) {
+	 gParse.status = MEMORY_ALLOCATION;
+      } else {
+	 this->value.undef = (char *)this->value.data.ptr + elem*size;
+      }
+   }
+}
+
+static void Do_Unary( Node *this )
+{
+   Node *that;
+   long elem;
+
+   that = gParse.Nodes + this->SubNodes[0];
+
+   if( that->operation==CONST_OP ) {  /* Operating on a constant! */
+      switch( this->operation ) {
+      case DOUBLE:
+      case FLTCAST:
+	 if( that->type==LONG )
+	    this->value.data.dbl = (double)that->value.data.lng;
+	 else if( that->type==BOOLEAN )
+	    this->value.data.dbl = ( that->value.data.log ? 1.0 : 0.0 );
+	 break;
+      case LONG:
+      case INTCAST:
+	 if( that->type==DOUBLE )
+	    this->value.data.lng = (long)that->value.data.dbl;
+	 else if( that->type==BOOLEAN )
+	    this->value.data.lng = ( that->value.data.log ? 1L : 0L );
+	 break;
+      case BOOLEAN:
+	 if( that->type==DOUBLE )
+	    this->value.data.log = ( that->value.data.dbl != 0.0 );
+	 else if( that->type==LONG )
+	    this->value.data.log = ( that->value.data.lng != 0L );
+	 break;
+      case UMINUS:
+	 if( that->type==DOUBLE )
+	    this->value.data.dbl = - that->value.data.dbl;
+	 else if( that->type==LONG )
+	    this->value.data.lng = - that->value.data.lng;
+	 break;
+      case NOT:
+	 if( that->type==BOOLEAN )
+	    this->value.data.log = ( ! that->value.data.log );
+	 else if( that->type==BITSTR )
+	    bitnot( this->value.data.str, that->value.data.str );
+	 break;
+      }
+      this->operation = CONST_OP;
+
+   } else {
+
+      Allocate_Ptrs( this );
+
+      if( !gParse.status ) {
+
+	 if( this->type!=BITSTR ) {
+	    elem = gParse.nRows;
+	    if( this->type!=STRING )
+	       elem *= this->value.nelem;
+	    while( elem-- )
+	       this->value.undef[elem] = that->value.undef[elem];
+	 }
+
+	 elem = gParse.nRows * this->value.nelem;
+
+	 switch( this->operation ) {
+
+	 case BOOLEAN:
+	    if( that->type==DOUBLE )
+	       while( elem-- )
+		  this->value.data.logptr[elem] =
+		     ( that->value.data.dblptr[elem] != 0.0 );
+	    else if( that->type==LONG )
+	       while( elem-- )
+		  this->value.data.logptr[elem] =
+		     ( that->value.data.lngptr[elem] != 0L );
+	    break;
+
+	 case DOUBLE:
+	 case FLTCAST:
+	    if( that->type==LONG )
+	       while( elem-- )
+		  this->value.data.dblptr[elem] =
+		     (double)that->value.data.lngptr[elem];
+	    else if( that->type==BOOLEAN )
+	       while( elem-- )
+		  this->value.data.dblptr[elem] =
+		     ( that->value.data.logptr[elem] ? 1.0 : 0.0 );
+	    break;
+
+	 case LONG:
+	 case INTCAST:
+	    if( that->type==DOUBLE )
+	       while( elem-- )
+		  this->value.data.lngptr[elem] =
+		     (long)that->value.data.dblptr[elem];
+	    else if( that->type==BOOLEAN )
+	       while( elem-- )
+		  this->value.data.lngptr[elem] =
+		     ( that->value.data.logptr[elem] ? 1L : 0L );
+	    break;
+
+	 case UMINUS:
+	    if( that->type==DOUBLE ) {
+	       while( elem-- )
+		  this->value.data.dblptr[elem] =
+		     - that->value.data.dblptr[elem];
+	    } else if( that->type==LONG ) {
+	       while( elem-- )
+		  this->value.data.lngptr[elem] =
+		     - that->value.data.lngptr[elem];
+	    }
+	    break;
+
+	 case NOT:
+	    if( that->type==BOOLEAN ) {
+	       while( elem-- )
+		  this->value.data.logptr[elem] =
+		     ( ! that->value.data.logptr[elem] );
+	    } else if( that->type==BITSTR ) {
+	       elem = gParse.nRows;
+	       while( elem-- )
+		  bitnot( this->value.data.strptr[elem],
+			  that->value.data.strptr[elem] );
+	    }
+	    break;
+	 }
+      }
+   }
+
+   if( that->operation>0 ) {
+      free( that->value.data.ptr );
+   }
+}
+
+static void Do_Offset( Node *this )
+{
+   Node *col;
+   long fRow, nRowOverlap, nRowReload, rowOffset;
+   long nelem, elem, offset, nRealElem;
+   int status;
+
+   col       = gParse.Nodes + this->SubNodes[0];
+   rowOffset = gParse.Nodes[  this->SubNodes[1] ].value.data.lng;
+
+   Allocate_Ptrs( this );
+
+   fRow   = gParse.firstRow + rowOffset;
+   if( this->type==STRING || this->type==BITSTR )
+      nRealElem = 1;
+   else
+      nRealElem = this->value.nelem;
+
+   nelem = nRealElem;
+
+   if( fRow < gParse.firstDataRow ) {
+
+      /* Must fill in data at start of array */
+
+      nRowReload = gParse.firstDataRow - fRow;
+      if( nRowReload > gParse.nRows ) nRowReload = gParse.nRows;
+      nRowOverlap = gParse.nRows - nRowReload;
+
+      offset = 0;
+
+      /*  NULLify any values falling out of bounds  */
+
+      while( fRow<1 && nRowReload>0 ) {
+	 if( this->type == BITSTR ) {
+	    nelem = this->value.nelem;
+	    this->value.data.strptr[offset][ nelem ] = '\0';
+	    while( nelem-- ) this->value.data.strptr[offset][nelem] = '0';
+	    offset++;
+	 } else {
+	    while( nelem-- )
+	       this->value.undef[offset++] = 1;
+	 }
+	 nelem = nRealElem;
+	 fRow++;
+	 nRowReload--;
+      }
+
+   } else if( fRow + gParse.nRows > gParse.firstDataRow + gParse.nDataRows ) {
+
+      /* Must fill in data at end of array */
+
+      nRowReload = (fRow+gParse.nRows) - (gParse.firstDataRow+gParse.nDataRows);
+      if( nRowReload>gParse.nRows ) {
+	 nRowReload = gParse.nRows;
+      } else {
+	 fRow = gParse.firstDataRow + gParse.nDataRows;
+      }
+      nRowOverlap = gParse.nRows - nRowReload;
+
+      offset = nRowOverlap * nelem;
+
+      /*  NULLify any values falling out of bounds  */
+
+      elem = gParse.nRows * nelem;
+      while( fRow+nRowReload>gParse.totalRows && nRowReload>0 ) {
+	 if( this->type == BITSTR ) {
+	    nelem = this->value.nelem;
+	    elem--;
+	    this->value.data.strptr[elem][ nelem ] = '\0';
+	    while( nelem-- ) this->value.data.strptr[elem][nelem] = '0';
+	 } else {
+	    while( nelem-- )
+	       this->value.undef[--elem] = 1;
+	 }
+	 nelem = nRealElem;
+	 nRowReload--;
+      }
+
+   } else {
+
+      nRowReload  = 0;
+      nRowOverlap = gParse.nRows;
+      offset      = 0;
+
+   }
+
+   if( nRowReload>0 ) {
+      switch( this->type ) {
+      case BITSTR:
+      case STRING:
+	 status = (*gParse.loadData)( -col->operation, fRow, nRowReload,
+				      this->value.data.strptr+offset,
+				      this->value.undef+offset );
+	 break;
+      case BOOLEAN:
+	 status = (*gParse.loadData)( -col->operation, fRow, nRowReload,
+				      this->value.data.logptr+offset,
+				      this->value.undef+offset );
+	 break;
+      case LONG:
+	 status = (*gParse.loadData)( -col->operation, fRow, nRowReload,
+				      this->value.data.lngptr+offset,
+				      this->value.undef+offset );
+	 break;
+      case DOUBLE:
+	 status = (*gParse.loadData)( -col->operation, fRow, nRowReload,
+				      this->value.data.dblptr+offset,
+				      this->value.undef+offset );
+	 break;
+      }
+   }
+
+   /*  Now copy over the overlapping region, if any  */
+
+   if( nRowOverlap <= 0 ) return;
+
+   if( rowOffset>0 )
+      elem = nRowOverlap * nelem;
+   else
+      elem = gParse.nRows * nelem;
+
+   offset = nelem * rowOffset;
+   while( nRowOverlap-- && !gParse.status ) {
+      while( nelem-- && !gParse.status ) {
+	 elem--;
+	 if( this->type != BITSTR )
+	    this->value.undef[elem] = col->value.undef[elem+offset];
+	 switch( this->type ) {
+	 case BITSTR:
+	    strcpy( this->value.data.strptr[elem       ],
+                     col->value.data.strptr[elem+offset] );
+	    break;
+	 case STRING:
+	    strcpy( this->value.data.strptr[elem       ],
+                     col->value.data.strptr[elem+offset] );
+	    break;
+	 case BOOLEAN:
+	    this->value.data.logptr[elem] = col->value.data.logptr[elem+offset];
+	    break;
+	 case LONG:
+	    this->value.data.lngptr[elem] = col->value.data.lngptr[elem+offset];
+	    break;
+	 case DOUBLE:
+	    this->value.data.dblptr[elem] = col->value.data.dblptr[elem+offset];
+	    break;
+	 }
+      }
+      nelem = nRealElem;
+   }
+}
+
+static void Do_BinOp_bit( Node *this )
+{
+   Node *that1, *that2;
+   char *sptr1=NULL, *sptr2=NULL;
+   int  const1, const2;
+   long rows;
+
+   that1 = gParse.Nodes + this->SubNodes[0];
+   that2 = gParse.Nodes + this->SubNodes[1];
+
+   const1 = ( that1->operation==CONST_OP );
+   const2 = ( that2->operation==CONST_OP );
+   sptr1  = ( const1 ? that1->value.data.str : NULL );
+   sptr2  = ( const2 ? that2->value.data.str : NULL );
+
+   if( const1 && const2 ) {
+      switch( this->operation ) {
+      case NE:
+	 this->value.data.log = !bitcmp( sptr1, sptr2 );
+	 break;
+      case EQ:
+	 this->value.data.log =  bitcmp( sptr1, sptr2 );
+	 break;
+      case GT:
+      case LT:
+      case LTE:
+      case GTE:
+	 this->value.data.log = bitlgte( sptr1, this->operation, sptr2 );
+	 break;
+      case '|': 
+	 bitor( this->value.data.str, sptr1, sptr2 );
+	 break;
+      case '&': 
+	 bitand( this->value.data.str, sptr1, sptr2 );
+	 break;
+      case '+':
+	 strcpy( this->value.data.str, sptr1 );
+	 strcat( this->value.data.str, sptr2 );
+	 break;
+      case ACCUM:
+	this->value.data.lng = 0;
+	while( *sptr1 ) {
+	  if ( *sptr1 == '1' ) this->value.data.lng ++;
+	  sptr1 ++;
+	}
+	break;
+	
+      }
+      this->operation = CONST_OP;
+
+   } else {
+
+      Allocate_Ptrs( this );
+
+      if( !gParse.status ) {
+	 rows  = gParse.nRows;
+	 switch( this->operation ) {
+
+	    /*  BITSTR comparisons  */
+
+	 case NE:
+	 case EQ:
+	 case GT:
+	 case LT:
+	 case LTE:
+	 case GTE:
+	    while( rows-- ) {
+	       if( !const1 )
+		  sptr1 = that1->value.data.strptr[rows];
+	       if( !const2 )
+		  sptr2 = that2->value.data.strptr[rows];
+	       switch( this->operation ) {
+	       case NE:  this->value.data.logptr[rows] = 
+                                                      !bitcmp( sptr1, sptr2 );
+                         break;
+	       case EQ:  this->value.data.logptr[rows] = 
+                                                       bitcmp( sptr1, sptr2 );
+                         break;
+	       case GT:
+	       case LT:
+	       case LTE:
+	       case GTE: this->value.data.logptr[rows] = 
+                                     bitlgte( sptr1, this->operation, sptr2 );
+	                 break;
+	       }
+	       this->value.undef[rows] = 0;
+	    }
+	    break;
+	 
+	    /*  BITSTR AND/ORs ...  no UNDEFS in or out */
+      
+	 case '|': 
+	 case '&': 
+	 case '+':
+	    while( rows-- ) {
+	       if( !const1 )
+		  sptr1 = that1->value.data.strptr[rows];
+	       if( !const2 )
+		  sptr2 = that2->value.data.strptr[rows];
+	       if( this->operation=='|' )
+		  bitor(  this->value.data.strptr[rows], sptr1, sptr2 );
+	       else if( this->operation=='&' )
+		  bitand( this->value.data.strptr[rows], sptr1, sptr2 );
+	       else {
+		  strcpy( this->value.data.strptr[rows], sptr1 );
+		  strcat( this->value.data.strptr[rows], sptr2 );
+	       }
+	    }
+	    break;
+
+	    /* Accumulate 1 bits */
+	 case ACCUM:
+	   { 
+	     long i, previous, curr;
+
+	     previous = that2->value.data.lng;
+	     
+	      /* Cumulative sum of this chunk */
+	     for (i=0; i<rows; i++) {
+	       sptr1 = that1->value.data.strptr[i];
+	       for (curr = 0; *sptr1; sptr1 ++) {
+		 if ( *sptr1 == '1' ) curr ++;
+	       }
+	       previous += curr;
+	       this->value.data.lngptr[i] = previous;
+	       this->value.undef[i] = 0;
+	     }
+	     
+	      /* Store final cumulant for next pass */
+	     that2->value.data.lng = previous;
+	   }
+	 }
+      }
+   }
+
+   if( that1->operation>0 ) {
+      free( that1->value.data.strptr[0] );
+      free( that1->value.data.strptr    );
+   }
+   if( that2->operation>0 ) {
+      free( that2->value.data.strptr[0] );
+      free( that2->value.data.strptr    );
+   }
+}
+
+static void Do_BinOp_str( Node *this )
+{
+   Node *that1, *that2;
+   char *sptr1, *sptr2, null1=0, null2=0;
+   int const1, const2, val;
+   long rows;
+
+   that1 = gParse.Nodes + this->SubNodes[0];
+   that2 = gParse.Nodes + this->SubNodes[1];
+
+   const1 = ( that1->operation==CONST_OP );
+   const2 = ( that2->operation==CONST_OP );
+   sptr1  = ( const1 ? that1->value.data.str : NULL );
+   sptr2  = ( const2 ? that2->value.data.str : NULL );
+
+   if( const1 && const2 ) {  /*  Result is a constant  */
+      switch( this->operation ) {
+
+	 /*  Compare Strings  */
+
+      case NE:
+      case EQ:
+	 val = ( FSTRCMP( sptr1, sptr2 ) == 0 );
+	 this->value.data.log = ( this->operation==EQ ? val : !val );
+	 break;
+      case GT:
+	 this->value.data.log = ( FSTRCMP( sptr1, sptr2 ) > 0 );
+	 break;
+      case LT:
+	 this->value.data.log = ( FSTRCMP( sptr1, sptr2 ) < 0 );
+	 break;
+      case GTE:
+	 this->value.data.log = ( FSTRCMP( sptr1, sptr2 ) >= 0 );
+	 break;
+      case LTE:
+	 this->value.data.log = ( FSTRCMP( sptr1, sptr2 ) <= 0 );
+	 break;
+
+	 /*  Concat Strings  */
+
+      case '+':
+	 strcpy( this->value.data.str, sptr1 );
+	 strcat( this->value.data.str, sptr2 );
+	 break;
+      }
+      this->operation = CONST_OP;
+
+   } else {  /*  Not a constant  */
+
+      Allocate_Ptrs( this );
+
+      if( !gParse.status ) {
+
+	 rows = gParse.nRows;
+	 switch( this->operation ) {
+
+	    /*  Compare Strings  */
+
+	 case NE:
+	 case EQ:
+	    while( rows-- ) {
+	       if( !const1 ) null1 = that1->value.undef[rows];
+	       if( !const2 ) null2 = that2->value.undef[rows];
+	       this->value.undef[rows] = (null1 || null2);
+	       if( ! this->value.undef[rows] ) {
+		  if( !const1 ) sptr1  = that1->value.data.strptr[rows];
+		  if( !const2 ) sptr2  = that2->value.data.strptr[rows];
+		  val = ( FSTRCMP( sptr1, sptr2 ) == 0 );
+		  this->value.data.logptr[rows] =
+		     ( this->operation==EQ ? val : !val );
+	       }
+	    }
+	    break;
+	    
+	 case GT:
+	 case LT:
+	    while( rows-- ) {
+	       if( !const1 ) null1 = that1->value.undef[rows];
+	       if( !const2 ) null2 = that2->value.undef[rows];
+	       this->value.undef[rows] = (null1 || null2);
+	       if( ! this->value.undef[rows] ) {
+		  if( !const1 ) sptr1  = that1->value.data.strptr[rows];
+		  if( !const2 ) sptr2  = that2->value.data.strptr[rows];
+		  val = ( FSTRCMP( sptr1, sptr2 ) );
+		  this->value.data.logptr[rows] =
+		     ( this->operation==GT ? val>0 : val<0 );
+	       }
+	    }
+	    break;
+
+	 case GTE:
+	 case LTE:
+	    while( rows-- ) {
+	       if( !const1 ) null1 = that1->value.undef[rows];
+	       if( !const2 ) null2 = that2->value.undef[rows];
+	       this->value.undef[rows] = (null1 || null2);
+	       if( ! this->value.undef[rows] ) {
+		  if( !const1 ) sptr1  = that1->value.data.strptr[rows];
+		  if( !const2 ) sptr2  = that2->value.data.strptr[rows];
+		  val = ( FSTRCMP( sptr1, sptr2 ) );
+		  this->value.data.logptr[rows] =
+		     ( this->operation==GTE ? val>=0 : val<=0 );
+	       }
+	    }
+	    break;
+
+	    /*  Concat Strings  */
+	    
+	 case '+':
+	    while( rows-- ) {
+	       if( !const1 ) null1 = that1->value.undef[rows];
+	       if( !const2 ) null2 = that2->value.undef[rows];
+	       this->value.undef[rows] = (null1 || null2);
+	       if( ! this->value.undef[rows] ) {
+		  if( !const1 ) sptr1  = that1->value.data.strptr[rows];
+		  if( !const2 ) sptr2  = that2->value.data.strptr[rows];
+		  strcpy( this->value.data.strptr[rows], sptr1 );
+		  strcat( this->value.data.strptr[rows], sptr2 );
+	       }
+	    }
+	    break;
+	 }
+      }
+   }
+
+   if( that1->operation>0 ) {
+      free( that1->value.data.strptr[0] );
+      free( that1->value.data.strptr );
+   }
+   if( that2->operation>0 ) {
+      free( that2->value.data.strptr[0] );
+      free( that2->value.data.strptr );
+   }
+}
+
+static void Do_BinOp_log( Node *this )
+{
+   Node *that1, *that2;
+   int vector1, vector2;
+   char val1=0, val2=0, null1=0, null2=0;
+   long rows, nelem, elem;
+
+   that1 = gParse.Nodes + this->SubNodes[0];
+   that2 = gParse.Nodes + this->SubNodes[1];
+
+   vector1 = ( that1->operation!=CONST_OP );
+   if( vector1 )
+      vector1 = that1->value.nelem;
+   else {
+      val1  = that1->value.data.log;
+   }
+
+   vector2 = ( that2->operation!=CONST_OP );
+   if( vector2 )
+      vector2 = that2->value.nelem;
+   else {
+      val2  = that2->value.data.log;
+   }
+
+   if( !vector1 && !vector2 ) {  /*  Result is a constant  */
+      switch( this->operation ) {
+      case OR:
+	 this->value.data.log = (val1 || val2);
+	 break;
+      case AND:
+	 this->value.data.log = (val1 && val2);
+	 break;
+      case EQ:
+	 this->value.data.log = ( (val1 && val2) || (!val1 && !val2) );
+	 break;
+      case NE:
+	 this->value.data.log = ( (val1 && !val2) || (!val1 && val2) );
+	 break;
+      case ACCUM:
+	 this->value.data.lng = val1;
+	 break;
+      }
+      this->operation=CONST_OP;
+   } else if (this->operation == ACCUM) {
+      long i, previous, curr;
+      rows  = gParse.nRows;
+      nelem = this->value.nelem;
+      elem  = this->value.nelem * rows;
+      
+      Allocate_Ptrs( this );
+      
+      if( !gParse.status ) {
+	previous = that2->value.data.lng;
+	
+	/* Cumulative sum of this chunk */
+	for (i=0; i<elem; i++) {
+	  if (!that1->value.undef[i]) {
+	    curr = that1->value.data.logptr[i];
+	    previous += curr;
+	  }
+	  this->value.data.lngptr[i] = previous;
+	  this->value.undef[i] = 0;
+	}
+	
+	/* Store final cumulant for next pass */
+	that2->value.data.lng = previous;
+      }
+      
+   } else {
+      rows  = gParse.nRows;
+      nelem = this->value.nelem;
+      elem  = this->value.nelem * rows;
+
+      Allocate_Ptrs( this );
+
+      if( !gParse.status ) {
+	
+	 if (this->operation == ACCUM) {
+	   long i, previous, curr;
+	   
+	   previous = that2->value.data.lng;
+	   
+	   /* Cumulative sum of this chunk */
+	   for (i=0; i<elem; i++) {
+	     if (!that1->value.undef[i]) {
+	       curr = that1->value.data.logptr[i];
+	       previous += curr;
+	     }
+	     this->value.data.lngptr[i] = previous;
+	     this->value.undef[i] = 0;
+	   }
+	   
+	   /* Store final cumulant for next pass */
+	   that2->value.data.lng = previous;
+	 }
+	
+	 while( rows-- ) {
+	    while( nelem-- ) {
+	       elem--;
+
+	       if( vector1>1 ) {
+		  val1  = that1->value.data.logptr[elem];
+		  null1 = that1->value.undef[elem];
+	       } else if( vector1 ) {
+		  val1  = that1->value.data.logptr[rows];
+		  null1 = that1->value.undef[rows];
+	       }
+
+	       if( vector2>1 ) {
+		  val2  = that2->value.data.logptr[elem];
+		  null2 = that2->value.undef[elem];
+	       } else if( vector2 ) {
+		  val2  = that2->value.data.logptr[rows];
+		  null2 = that2->value.undef[rows];
+	       }
+
+	       this->value.undef[elem] = (null1 || null2);
+	       switch( this->operation ) {
+
+	       case OR:
+		  /*  This is more complicated than others to suppress UNDEFs */
+		  /*  in those cases where the other argument is DEF && TRUE  */
+
+		  if( !null1 && !null2 ) {
+		     this->value.data.logptr[elem] = (val1 || val2);
+		  } else if( (null1 && !null2 && val2)
+			     || ( !null1 && null2 && val1 ) ) {
+		     this->value.data.logptr[elem] = 1;
+		     this->value.undef[elem] = 0;
+		  }
+		  break;
+
+	       case AND:
+		  /*  This is more complicated than others to suppress UNDEFs */
+		  /*  in those cases where the other argument is DEF && FALSE */
+
+		  if( !null1 && !null2 ) {
+		     this->value.data.logptr[elem] = (val1 && val2);
+		  } else if( (null1 && !null2 && !val2)
+			     || ( !null1 && null2 && !val1 ) ) {
+		     this->value.data.logptr[elem] = 0;
+		     this->value.undef[elem] = 0;
+		  }
+		  break;
+
+	       case EQ:
+		  this->value.data.logptr[elem] = 
+		     ( (val1 && val2) || (!val1 && !val2) );
+		  break;
+
+	       case NE:
+		  this->value.data.logptr[elem] =
+		     ( (val1 && !val2) || (!val1 && val2) );
+		  break;
+	       }
+	    }
+	    nelem = this->value.nelem;
+	 }
+      }
+   }
+
+   if( that1->operation>0 ) {
+      free( that1->value.data.ptr );
+   }
+   if( that2->operation>0 ) {
+      free( that2->value.data.ptr );
+   }
+}
+
+static void Do_BinOp_lng( Node *this )
+{
+   Node *that1, *that2;
+   int  vector1, vector2;
+   long val1=0, val2=0;
+   char null1=0, null2=0;
+   long rows, nelem, elem;
+
+   that1 = gParse.Nodes + this->SubNodes[0];
+   that2 = gParse.Nodes + this->SubNodes[1];
+
+   vector1 = ( that1->operation!=CONST_OP );
+   if( vector1 )
+      vector1 = that1->value.nelem;
+   else {
+      val1  = that1->value.data.lng;
+   }
+
+   vector2 = ( that2->operation!=CONST_OP );
+   if( vector2 )
+      vector2 = that2->value.nelem;
+   else {
+      val2  = that2->value.data.lng;
+   }
+
+   if( !vector1 && !vector2 ) {  /*  Result is a constant  */
+
+      switch( this->operation ) {
+      case '~':   /* Treat as == for LONGS */
+      case EQ:    this->value.data.log = (val1 == val2);   break;
+      case NE:    this->value.data.log = (val1 != val2);   break;
+      case GT:    this->value.data.log = (val1 >  val2);   break;
+      case LT:    this->value.data.log = (val1 <  val2);   break;
+      case LTE:   this->value.data.log = (val1 <= val2);   break;
+      case GTE:   this->value.data.log = (val1 >= val2);   break;
+
+      case '+':   this->value.data.lng = (val1  + val2);   break;
+      case '-':   this->value.data.lng = (val1  - val2);   break;
+      case '*':   this->value.data.lng = (val1  * val2);   break;
+
+      case '%':
+	 if( val2 ) this->value.data.lng = (val1 % val2);
+	 else       fferror("Divide by Zero");
+	 break;
+      case '/': 
+	 if( val2 ) this->value.data.lng = (val1 / val2); 
+	 else       fferror("Divide by Zero");
+	 break;
+      case POWER:
+	 this->value.data.lng = (long)pow((double)val1,(double)val2);
+	 break;
+      case ACCUM:
+	 this->value.data.lng = val1;
+	 break;
+      case DIFF:
+	 this->value.data.lng = 0;
+	 break;
+      }
+      this->operation=CONST_OP;
+
+   } else if ((this->operation == ACCUM) || (this->operation == DIFF)) {
+      long i, previous, curr;
+      long undef;
+      rows  = gParse.nRows;
+      nelem = this->value.nelem;
+      elem  = this->value.nelem * rows;
+      
+      Allocate_Ptrs( this );
+      
+      if( !gParse.status ) {
+	previous = that2->value.data.lng;
+	undef    = (long) that2->value.undef;
+	
+	if (this->operation == ACCUM) {
+	  /* Cumulative sum of this chunk */
+	  for (i=0; i<elem; i++) {
+	    if (!that1->value.undef[i]) {
+	      curr = that1->value.data.lngptr[i];
+	      previous += curr;
+	    }
+	    this->value.data.lngptr[i] = previous;
+	    this->value.undef[i] = 0;
+	  }
+	} else {
+	  /* Sequential difference for this chunk */
+	  for (i=0; i<elem; i++) {
+	    curr = that1->value.data.lngptr[i];
+	    if (that1->value.undef[i] || undef) {
+	      /* Either this, or previous, value was undefined */
+	      this->value.data.lngptr[i] = 0;
+	      this->value.undef[i] = 1;
+	    } else {
+	      /* Both defined, we are okay! */
+	      this->value.data.lngptr[i] = curr - previous;
+	      this->value.undef[i] = 0;
+	    }
+
+	    previous = curr;
+	    undef = that1->value.undef[i];
+	  }
+	}	  
+	
+	/* Store final cumulant for next pass */
+	that2->value.data.lng = previous;
+	that2->value.undef    = (char *) undef; /* XXX evil, but no harm here */
+      }
+      
+   } else {
+
+      rows  = gParse.nRows;
+      nelem = this->value.nelem;
+      elem  = this->value.nelem * rows;
+
+      Allocate_Ptrs( this );
+
+      while( rows-- && !gParse.status ) {
+	 while( nelem-- && !gParse.status ) {
+	    elem--;
+
+	    if( vector1>1 ) {
+	       val1  = that1->value.data.lngptr[elem];
+	       null1 = that1->value.undef[elem];
+	    } else if( vector1 ) {
+	       val1  = that1->value.data.lngptr[rows];
+	       null1 = that1->value.undef[rows];
+	    }
+
+	    if( vector2>1 ) {
+	       val2  = that2->value.data.lngptr[elem];
+	       null2 = that2->value.undef[elem];
+	    } else if( vector2 ) {
+	       val2  = that2->value.data.lngptr[rows];
+	       null2 = that2->value.undef[rows];
+	    }
+
+	    this->value.undef[elem] = (null1 || null2);
+	    switch( this->operation ) {
+	    case '~':   /* Treat as == for LONGS */
+	    case EQ:   this->value.data.logptr[elem] = (val1 == val2);   break;
+	    case NE:   this->value.data.logptr[elem] = (val1 != val2);   break;
+	    case GT:   this->value.data.logptr[elem] = (val1 >  val2);   break;
+	    case LT:   this->value.data.logptr[elem] = (val1 <  val2);   break;
+	    case LTE:  this->value.data.logptr[elem] = (val1 <= val2);   break;
+	    case GTE:  this->value.data.logptr[elem] = (val1 >= val2);   break;
+	       
+	    case '+':  this->value.data.lngptr[elem] = (val1  + val2);   break;
+	    case '-':  this->value.data.lngptr[elem] = (val1  - val2);   break;
+	    case '*':  this->value.data.lngptr[elem] = (val1  * val2);   break;
+
+	    case '%':   
+	       if( val2 ) this->value.data.lngptr[elem] = (val1 % val2);
+	       else {
+		 this->value.data.lngptr[elem] = 0;
+		 this->value.undef[elem] = 1;
+	       }
+	       break;
+	    case '/': 
+	       if( val2 ) this->value.data.lngptr[elem] = (val1 / val2); 
+	       else {
+		 this->value.data.lngptr[elem] = 0;
+		 this->value.undef[elem] = 1;
+	       }
+	       break;
+	    case POWER:
+	       this->value.data.lngptr[elem] = (long)pow((double)val1,(double)val2);
+	       break;
+	    }
+	 }
+	 nelem = this->value.nelem;
+      }
+   }
+
+   if( that1->operation>0 ) {
+      free( that1->value.data.ptr );
+   }
+   if( that2->operation>0 ) {
+      free( that2->value.data.ptr );
+   }
+}
+
+static void Do_BinOp_dbl( Node *this )
+{
+   Node   *that1, *that2;
+   int    vector1, vector2;
+   double val1=0.0, val2=0.0;
+   char   null1=0, null2=0;
+   long   rows, nelem, elem;
+
+   that1 = gParse.Nodes + this->SubNodes[0];
+   that2 = gParse.Nodes + this->SubNodes[1];
+
+   vector1 = ( that1->operation!=CONST_OP );
+   if( vector1 )
+      vector1 = that1->value.nelem;
+   else {
+      val1  = that1->value.data.dbl;
+   }
+
+   vector2 = ( that2->operation!=CONST_OP );
+   if( vector2 )
+      vector2 = that2->value.nelem;
+   else {
+      val2  = that2->value.data.dbl;
+   } 
+
+   if( !vector1 && !vector2 ) {  /*  Result is a constant  */
+
+      switch( this->operation ) {
+      case '~':   this->value.data.log = ( fabs(val1-val2) < APPROX );   break;
+      case EQ:    this->value.data.log = (val1 == val2);   break;
+      case NE:    this->value.data.log = (val1 != val2);   break;
+      case GT:    this->value.data.log = (val1 >  val2);   break;
+      case LT:    this->value.data.log = (val1 <  val2);   break;
+      case LTE:   this->value.data.log = (val1 <= val2);   break;
+      case GTE:   this->value.data.log = (val1 >= val2);   break;
+
+      case '+':   this->value.data.dbl = (val1  + val2);   break;
+      case '-':   this->value.data.dbl = (val1  - val2);   break;
+      case '*':   this->value.data.dbl = (val1  * val2);   break;
+
+      case '%':
+	 if( val2 ) this->value.data.dbl = val1 - val2*((int)(val1/val2));
+	 else       fferror("Divide by Zero");
+	 break;
+      case '/': 
+	 if( val2 ) this->value.data.dbl = (val1 / val2); 
+	 else       fferror("Divide by Zero");
+	 break;
+      case POWER:
+	 this->value.data.dbl = (double)pow(val1,val2);
+	 break;
+      case ACCUM:
+	 this->value.data.dbl = val1;
+	 break;
+      case DIFF:
+	this->value.data.dbl = 0;
+	 break;
+      }
+      this->operation=CONST_OP;
+
+   } else if ((this->operation == ACCUM) || (this->operation == DIFF)) {
+      long i;
+      long undef;
+      double previous, curr;
+      rows  = gParse.nRows;
+      nelem = this->value.nelem;
+      elem  = this->value.nelem * rows;
+      
+      Allocate_Ptrs( this );
+      
+      if( !gParse.status ) {
+	previous = that2->value.data.dbl;
+	undef    = (long) that2->value.undef;
+	
+	if (this->operation == ACCUM) {
+	  /* Cumulative sum of this chunk */
+	  for (i=0; i<elem; i++) {
+	    if (!that1->value.undef[i]) {
+	      curr = that1->value.data.dblptr[i];
+	      previous += curr;
+	    }
+	    this->value.data.dblptr[i] = previous;
+	    this->value.undef[i] = 0;
+	  }
+	} else {
+	  /* Sequential difference for this chunk */
+	  for (i=0; i<elem; i++) {
+	    curr = that1->value.data.dblptr[i];
+	    if (that1->value.undef[i] || undef) {
+	      /* Either this, or previous, value was undefined */
+	      this->value.data.dblptr[i] = 0;
+	      this->value.undef[i] = 1;
+	    } else {
+	      /* Both defined, we are okay! */
+	      this->value.data.dblptr[i] = curr - previous;
+	      this->value.undef[i] = 0;
+	    }
+
+	    previous = curr;
+	    undef = that1->value.undef[i];
+	  }
+	}	  
+	
+	/* Store final cumulant for next pass */
+	that2->value.data.dbl = previous;
+	that2->value.undef    = (char *) undef; /* XXX evil, but no harm here */
+      }
+      
+   } else {
+
+      rows  = gParse.nRows;
+      nelem = this->value.nelem;
+      elem  = this->value.nelem * rows;
+
+      Allocate_Ptrs( this );
+
+      while( rows-- && !gParse.status ) {
+	 while( nelem-- && !gParse.status ) {
+	    elem--;
+
+	    if( vector1>1 ) {
+	       val1  = that1->value.data.dblptr[elem];
+	       null1 = that1->value.undef[elem];
+	    } else if( vector1 ) {
+	       val1  = that1->value.data.dblptr[rows];
+	       null1 = that1->value.undef[rows];
+	    }
+
+	    if( vector2>1 ) {
+	       val2  = that2->value.data.dblptr[elem];
+	       null2 = that2->value.undef[elem];
+	    } else if( vector2 ) {
+	       val2  = that2->value.data.dblptr[rows];
+	       null2 = that2->value.undef[rows];
+	    }
+
+	    this->value.undef[elem] = (null1 || null2);
+	    switch( this->operation ) {
+	    case '~':   this->value.data.logptr[elem] =
+                                          ( fabs(val1-val2) < APPROX );   break;
+	    case EQ:    this->value.data.logptr[elem] = (val1 == val2);   break;
+	    case NE:    this->value.data.logptr[elem] = (val1 != val2);   break;
+	    case GT:    this->value.data.logptr[elem] = (val1 >  val2);   break;
+	    case LT:    this->value.data.logptr[elem] = (val1 <  val2);   break;
+	    case LTE:   this->value.data.logptr[elem] = (val1 <= val2);   break;
+	    case GTE:   this->value.data.logptr[elem] = (val1 >= val2);   break;
+	       
+	    case '+':   this->value.data.dblptr[elem] = (val1  + val2);   break;
+	    case '-':   this->value.data.dblptr[elem] = (val1  - val2);   break;
+	    case '*':   this->value.data.dblptr[elem] = (val1  * val2);   break;
+
+	    case '%':
+	       if( val2 ) this->value.data.dblptr[elem] =
+                                val1 - val2*((int)(val1/val2));
+	       else {
+		 this->value.data.dblptr[elem] = 0.0;
+		 this->value.undef[elem] = 1;
+	       }
+	       break;
+	    case '/': 
+	       if( val2 ) this->value.data.dblptr[elem] = (val1 / val2); 
+	       else {
+		 this->value.data.dblptr[elem] = 0.0;
+		 this->value.undef[elem] = 1;
+	       }
+	       break;
+	    case POWER:
+	       this->value.data.dblptr[elem] = (double)pow(val1,val2);
+	       break;
+	    }
+	 }
+	 nelem = this->value.nelem;
+      }
+   }
+
+   if( that1->operation>0 ) {
+      free( that1->value.data.ptr );
+   }
+   if( that2->operation>0 ) {
+      free( that2->value.data.ptr );
+   }
+}
+
+/*
+ *  This Quickselect routine is based on the algorithm described in
+ *  "Numerical recipes in C", Second Edition,
+ *  Cambridge University Press, 1992, Section 8.5, ISBN 0-521-43108-5
+ *  This code by Nicolas Devillard - 1998. Public domain.
+ * http://ndevilla.free.fr/median/median/src/quickselect.c
+ */
+
+#define ELEM_SWAP(a,b) { register long t=(a);(a)=(b);(b)=t; }
+
+/* 
+ * qselect_median_lng - select the median value of a long array
+ *
+ * This routine selects the median value of the long integer array
+ * arr[].  If there are an even number of elements, the "lower median"
+ * is selected.
+ *
+ * The array arr[] is scrambled, so users must operate on a scratch
+ * array if they wish the values to be preserved.
+ *
+ * long arr[] - array of values
+ * int n - number of elements in arr
+ *
+ * RETURNS: the lower median value of arr[]
+ *
+ */
+long qselect_median_lng(long arr[], int n)
+{
+    int low, high ;
+    int median;
+    int middle, ll, hh;
+
+    low = 0 ; high = n-1 ; median = (low + high) / 2;
+    for (;;) {
+
+        if (high <= low) { /* One element only */
+	  return arr[median];	  
+	}
+
+        if (high == low + 1) {  /* Two elements only */
+            if (arr[low] > arr[high])
+                ELEM_SWAP(arr[low], arr[high]) ;
+	    return arr[median];
+        }
+
+    /* Find median of low, middle and high items; swap into position low */
+    middle = (low + high) / 2;
+    if (arr[middle] > arr[high])    ELEM_SWAP(arr[middle], arr[high]) ;
+    if (arr[low] > arr[high])       ELEM_SWAP(arr[low], arr[high]) ;
+    if (arr[middle] > arr[low])     ELEM_SWAP(arr[middle], arr[low]) ;
+
+    /* Swap low item (now in position middle) into position (low+1) */
+    ELEM_SWAP(arr[middle], arr[low+1]) ;
+
+    /* Nibble from each end towards middle, swapping items when stuck */
+    ll = low + 1;
+    hh = high;
+    for (;;) {
+        do ll++; while (arr[low] > arr[ll]) ;
+        do hh--; while (arr[hh]  > arr[low]) ;
+
+        if (hh < ll)
+        break;
+
+        ELEM_SWAP(arr[ll], arr[hh]) ;
+    }
+
+    /* Swap middle item (in position low) back into correct position */
+    ELEM_SWAP(arr[low], arr[hh]) ;
+
+    /* Re-set active partition */
+    if (hh <= median)
+        low = ll;
+        if (hh >= median)
+        high = hh - 1;
+    }
+}
+
+#undef ELEM_SWAP
+
+#define ELEM_SWAP(a,b) { register double t=(a);(a)=(b);(b)=t; }
+
+/* 
+ * qselect_median_dbl - select the median value of a double array
+ *
+ * This routine selects the median value of the double array
+ * arr[].  If there are an even number of elements, the "lower median"
+ * is selected.
+ *
+ * The array arr[] is scrambled, so users must operate on a scratch
+ * array if they wish the values to be preserved.
+ *
+ * double arr[] - array of values
+ * int n - number of elements in arr
+ *
+ * RETURNS: the lower median value of arr[]
+ *
+ */
+double qselect_median_dbl(double arr[], int n)
+{
+    int low, high ;
+    int median;
+    int middle, ll, hh;
+
+    low = 0 ; high = n-1 ; median = (low + high) / 2;
+    for (;;) {
+        if (high <= low) { /* One element only */
+            return arr[median] ;
+	}
+
+        if (high == low + 1) {  /* Two elements only */
+            if (arr[low] > arr[high])
+                ELEM_SWAP(arr[low], arr[high]) ;
+            return arr[median] ;
+        }
+
+    /* Find median of low, middle and high items; swap into position low */
+    middle = (low + high) / 2;
+    if (arr[middle] > arr[high])    ELEM_SWAP(arr[middle], arr[high]) ;
+    if (arr[low] > arr[high])       ELEM_SWAP(arr[low], arr[high]) ;
+    if (arr[middle] > arr[low])     ELEM_SWAP(arr[middle], arr[low]) ;
+
+    /* Swap low item (now in position middle) into position (low+1) */
+    ELEM_SWAP(arr[middle], arr[low+1]) ;
+
+    /* Nibble from each end towards middle, swapping items when stuck */
+    ll = low + 1;
+    hh = high;
+    for (;;) {
+        do ll++; while (arr[low] > arr[ll]) ;
+        do hh--; while (arr[hh]  > arr[low]) ;
+
+        if (hh < ll)
+        break;
+
+        ELEM_SWAP(arr[ll], arr[hh]) ;
+    }
+
+    /* Swap middle item (in position low) back into correct position */
+    ELEM_SWAP(arr[low], arr[hh]) ;
+
+    /* Re-set active partition */
+    if (hh <= median)
+        low = ll;
+        if (hh >= median)
+        high = hh - 1;
+    }
+}
+
+#undef ELEM_SWAP
+
+/*
+ * angsep_calc - compute angular separation between celestial coordinates
+ *   
+ * This routine computes the angular separation between to coordinates
+ * on the celestial sphere (i.e. RA and Dec).  Note that all units are
+ * in DEGREES, unlike the other trig functions in the calculator.
+ *
+ * double ra1, dec1 - RA and Dec of the first position in degrees
+ * double ra2, dec2 - RA and Dec of the second position in degrees
+ * 
+ * RETURNS: (double) angular separation in degrees
+ *
+ */
+double angsep_calc(double ra1, double dec1, double ra2, double dec2)
+{
+  double cd;
+  static double deg = 0;
+  double a, sdec, sra;
+  
+  if (deg == 0) deg = ((double)4)*atan((double)1)/((double)180);
+  /* deg = 1.0; **** UNCOMMENT IF YOU WANT RADIANS */
+
+
+  
+/*
+This (commented out) algorithm uses the Low of Cosines, which becomes
+ unstable for angles less than 0.1 arcsec. 
+ 
+  cd = sin(dec1*deg)*sin(dec2*deg) 
+    + cos(dec1*deg)*cos(dec2*deg)*cos((ra1-ra2)*deg);
+  if (cd < (-1)) cd = -1;
+  if (cd > (+1)) cd = +1;
+  return acos(cd)/deg;
+*/
+
+  /* The algorithm is the law of Haversines.  This algorithm is
+     stable even when the points are close together.  The normal
+     Law of Cosines fails for angles around 0.1 arcsec. */
+
+  sra  = sin( (ra2 - ra1)*deg / 2 );
+  sdec = sin( (dec2 - dec1)*deg / 2);
+  a = sdec*sdec + cos(dec1*deg)*cos(dec2*deg)*sra*sra;
+
+  /* Sanity checking to avoid a range error in the sqrt()'s below */
+  if (a < 0) { a = 0; }
+  if (a > 1) { a = 1; }
+
+  return 2.0*atan2(sqrt(a), sqrt(1.0 - a)) / deg;
+}
+
+
+
+
+
+
+static double ran1()
+{
+  static double dval = 0.0;
+  double rndVal;
+
+  if (dval == 0.0) {
+    if( rand()<32768 && rand()<32768 )
+      dval =      32768.0;
+    else
+      dval = 2147483648.0;
+  }
+
+  rndVal = (double)rand();
+  while( rndVal > dval ) dval *= 2.0;
+  return rndVal/dval;
+}
+
+/* Gaussian deviate routine from Numerical Recipes */
+static double gasdev()
+{
+  static int iset = 0;
+  static double gset;
+  double fac, rsq, v1, v2;
+
+  if (iset == 0) {
+    do {
+      v1 = 2.0*ran1()-1.0;
+      v2 = 2.0*ran1()-1.0;
+      rsq = v1*v1 + v2*v2;
+    } while (rsq >= 1.0 || rsq == 0.0);
+    fac = sqrt(-2.0*log(rsq)/rsq);
+    gset = v1*fac;
+    iset = 1;
+    return v2*fac;
+  } else {
+    iset = 0;
+    return gset;
+  }
+
+}
+
+/* lgamma function - from Numerical Recipes */
+
+float gammaln(float xx)
+     /* Returns the value ln Gamma[(xx)] for xx > 0. */
+{
+  /* 
+     Internal arithmetic will be done in double precision, a nicety
+     that you can omit if five-figure accuracy is good enough. */
+  double x,y,tmp,ser;
+  static double cof[6]={76.18009172947146,-86.50532032941677,
+			24.01409824083091,-1.231739572450155,
+			0.1208650973866179e-2,-0.5395239384953e-5};
+  int j;
+  y=x=xx;
+  tmp=x+5.5;
+  tmp -= (x+0.5)*log(tmp);
+  ser=1.000000000190015;
+  for (j=0;j<=5;j++) ser += cof[j]/++y;
+  return (float) -tmp+log(2.5066282746310005*ser/x);
+}
+
+/* Poisson deviate - derived from Numerical Recipes */
+static long poidev(double xm)
+{
+  static double sq, alxm, g, oldm = -1.0;
+  static double pi = 0;
+  double em, t, y;
+
+  if (pi == 0) pi = ((double)4)*atan((double)1);
+
+  if (xm < 20.0) {
+    if (xm != oldm) {
+      oldm = xm;
+      g = exp(-xm);
+    }
+    em = -1;
+    t = 1.0;
+    do {
+      em += 1;
+      t *= ran1();
+    } while (t > g);
+  } else {
+    if (xm != oldm) {
+      oldm = xm;
+      sq = sqrt(2.0*xm);
+      alxm = log(xm);
+      g = xm*alxm-gammaln( (float) (xm+1.0));
+    }
+    do {
+      do {
+	y = tan(pi*ran1());
+	em = sq*y+xm;
+      } while (em < 0.0);
+      em = floor(em);
+      t = 0.9*(1.0+y*y)*exp(em*alxm-gammaln( (float) (em+1.0) )-g);
+    } while (ran1() > t);
+  }
+
+  /* Return integer version */
+  return (long int) floor(em+0.5);
+}
+
+static void Do_Func( Node *this )
+{
+   Node *theParams[MAXSUBS];
+   int  vector[MAXSUBS], allConst;
+   lval pVals[MAXSUBS];
+   char pNull[MAXSUBS];
+   long   ival;
+   double dval;
+   int  i, valInit;
+   long row, elem, nelem;
+
+   i = this->nSubNodes;
+   allConst = 1;
+   while( i-- ) {
+      theParams[i] = gParse.Nodes + this->SubNodes[i];
+      vector[i]   = ( theParams[i]->operation!=CONST_OP );
+      if( vector[i] ) {
+	 allConst = 0;
+	 vector[i] = theParams[i]->value.nelem;
+      } else {
+	 if( theParams[i]->type==DOUBLE ) {
+	    pVals[i].data.dbl = theParams[i]->value.data.dbl;
+	 } else if( theParams[i]->type==LONG ) {
+	    pVals[i].data.lng = theParams[i]->value.data.lng;
+	 } else if( theParams[i]->type==BOOLEAN ) {
+	    pVals[i].data.log = theParams[i]->value.data.log;
+	 } else
+	    strcpy(pVals[i].data.str, theParams[i]->value.data.str);
+	 pNull[i] = 0;
+      }
+   }
+
+   if( this->nSubNodes==0 ) allConst = 0; /* These do produce scalars */
+   /* Random numbers are *never* constant !! */
+   if( this->operation == poirnd_fct ) allConst = 0;
+   if( this->operation == gasrnd_fct ) allConst = 0;
+   if( this->operation == rnd_fct ) allConst = 0;
+
+   if( allConst ) {
+
+      switch( this->operation ) {
+
+	    /* Non-Trig single-argument functions */
+
+	 case sum_fct:
+	    if( theParams[0]->type==BOOLEAN )
+	       this->value.data.lng = ( pVals[0].data.log ? 1 : 0 );
+	    else if( theParams[0]->type==LONG )
+	       this->value.data.lng = pVals[0].data.lng;
+	    else if( theParams[0]->type==DOUBLE )
+	       this->value.data.dbl = pVals[0].data.dbl;
+	    else if( theParams[0]->type==BITSTR )
+	      strcpy(this->value.data.str, pVals[0].data.str);
+	    break;
+         case average_fct:
+	    if( theParams[0]->type==LONG )
+	       this->value.data.dbl = pVals[0].data.lng;
+	    else if( theParams[0]->type==DOUBLE )
+	       this->value.data.dbl = pVals[0].data.dbl;
+	    break;
+         case stddev_fct:
+	    this->value.data.dbl = 0;  /* Standard deviation of a constant = 0 */
+	    break;
+	 case median_fct:
+	    if( theParams[0]->type==BOOLEAN )
+	       this->value.data.lng = ( pVals[0].data.log ? 1 : 0 );
+	    else if( theParams[0]->type==LONG )
+	       this->value.data.lng = pVals[0].data.lng;
+	    else
+	       this->value.data.dbl = pVals[0].data.dbl;
+	    break;
+
+	 case poirnd_fct:
+	    if( theParams[0]->type==DOUBLE )
+	      this->value.data.lng = poidev(pVals[0].data.dbl);
+	    else
+	      this->value.data.lng = poidev(pVals[0].data.lng);
+	    break;
+
+	 case abs_fct:
+	    if( theParams[0]->type==DOUBLE ) {
+	       dval = pVals[0].data.dbl;
+	       this->value.data.dbl = (dval>0.0 ? dval : -dval);
+	    } else {
+	       ival = pVals[0].data.lng;
+	       this->value.data.lng = (ival> 0  ? ival : -ival);
+	    }
+	    break;
+
+            /* Special Null-Handling Functions */
+
+         case nonnull_fct:
+	    this->value.data.lng = 1; /* Constants are always 1-element and defined */
+	    break;
+         case isnull_fct:  /* Constants are always defined */
+	    this->value.data.log = 0;
+	    break;
+         case defnull_fct:
+	    if( this->type==BOOLEAN )
+	       this->value.data.log = pVals[0].data.log;
+            else if( this->type==LONG )
+	       this->value.data.lng = pVals[0].data.lng;
+            else if( this->type==DOUBLE )
+	       this->value.data.dbl = pVals[0].data.dbl;
+            else if( this->type==STRING )
+	       strcpy(this->value.data.str,pVals[0].data.str);
+	    break;
+
+	    /* Math functions with 1 double argument */
+
+	 case sin_fct:
+	    this->value.data.dbl = sin( pVals[0].data.dbl );
+	    break;
+	 case cos_fct:
+	    this->value.data.dbl = cos( pVals[0].data.dbl );
+	    break;
+	 case tan_fct:
+	    this->value.data.dbl = tan( pVals[0].data.dbl );
+	    break;
+	 case asin_fct:
+	    dval = pVals[0].data.dbl;
+	    if( dval<-1.0 || dval>1.0 )
+	       fferror("Out of range argument to arcsin");
+	    else
+	       this->value.data.dbl = asin( dval );
+	    break;
+	 case acos_fct:
+	    dval = pVals[0].data.dbl;
+	    if( dval<-1.0 || dval>1.0 )
+	       fferror("Out of range argument to arccos");
+	    else
+	       this->value.data.dbl = acos( dval );
+	    break;
+	 case atan_fct:
+	    this->value.data.dbl = atan( pVals[0].data.dbl );
+	    break;
+	 case sinh_fct:
+	    this->value.data.dbl = sinh( pVals[0].data.dbl );
+	    break;
+	 case cosh_fct:
+	    this->value.data.dbl = cosh( pVals[0].data.dbl );
+	    break;
+	 case tanh_fct:
+	    this->value.data.dbl = tanh( pVals[0].data.dbl );
+	    break;
+	 case exp_fct:
+	    this->value.data.dbl = exp( pVals[0].data.dbl );
+	    break;
+	 case log_fct:
+	    dval = pVals[0].data.dbl;
+	    if( dval<=0.0 )
+	       fferror("Out of range argument to log");
+	    else
+	       this->value.data.dbl = log( dval );
+	    break;
+	 case log10_fct:
+	    dval = pVals[0].data.dbl;
+	    if( dval<=0.0 )
+	       fferror("Out of range argument to log10");
+	    else
+	       this->value.data.dbl = log10( dval );
+	    break;
+	 case sqrt_fct:
+	    dval = pVals[0].data.dbl;
+	    if( dval<0.0 )
+	       fferror("Out of range argument to sqrt");
+	    else
+	       this->value.data.dbl = sqrt( dval );
+	    break;
+	 case ceil_fct:
+	    this->value.data.dbl = ceil( pVals[0].data.dbl );
+	    break;
+	 case floor_fct:
+	    this->value.data.dbl = floor( pVals[0].data.dbl );
+	    break;
+	 case round_fct:
+	    this->value.data.dbl = floor( pVals[0].data.dbl + 0.5 );
+	    break;
+
+	    /* Two-argument Trig Functions */
+
+	 case atan2_fct:
+	    this->value.data.dbl =
+	       atan2( pVals[0].data.dbl, pVals[1].data.dbl );
+	    break;
+
+	    /* Four-argument ANGSEP function */
+         case angsep_fct:
+	    this->value.data.dbl = 
+	      angsep_calc(pVals[0].data.dbl, pVals[1].data.dbl,
+			  pVals[2].data.dbl, pVals[3].data.dbl);
+
+	    /*  Min/Max functions taking 1 or 2 arguments  */
+
+         case min1_fct:
+	    /* No constant vectors! */
+	    if( this->type == DOUBLE )
+	       this->value.data.dbl = pVals[0].data.dbl;
+	    else if( this->type == LONG )
+	       this->value.data.lng = pVals[0].data.lng;
+	    else if( this->type == BITSTR )
+	      strcpy(this->value.data.str, pVals[0].data.str);
+	    break;
+         case min2_fct:
+	    if( this->type == DOUBLE )
+	       this->value.data.dbl =
+		  minvalue( pVals[0].data.dbl, pVals[1].data.dbl );
+	    else if( this->type == LONG )
+	       this->value.data.lng =
+		  minvalue( pVals[0].data.lng, pVals[1].data.lng );
+	    break;
+         case max1_fct:
+	    /* No constant vectors! */
+	    if( this->type == DOUBLE )
+	       this->value.data.dbl = pVals[0].data.dbl;
+	    else if( this->type == LONG )
+	       this->value.data.lng = pVals[0].data.lng;
+	    else if( this->type == BITSTR )
+	      strcpy(this->value.data.str, pVals[0].data.str);
+	    break;
+         case max2_fct:
+	    if( this->type == DOUBLE )
+	       this->value.data.dbl =
+		  maxvalue( pVals[0].data.dbl, pVals[1].data.dbl );
+	    else if( this->type == LONG )
+	       this->value.data.lng =
+		  maxvalue( pVals[0].data.lng, pVals[1].data.lng );
+	    break;
+
+	    /* Boolean SAO region Functions... scalar or vector dbls */
+
+	 case near_fct:
+	    this->value.data.log = bnear( pVals[0].data.dbl, pVals[1].data.dbl,
+					  pVals[2].data.dbl );
+	    break;
+	 case circle_fct:
+	    this->value.data.log = circle( pVals[0].data.dbl, pVals[1].data.dbl,
+					   pVals[2].data.dbl, pVals[3].data.dbl,
+					   pVals[4].data.dbl );
+	    break;
+	 case box_fct:
+	    this->value.data.log = saobox( pVals[0].data.dbl, pVals[1].data.dbl,
+					   pVals[2].data.dbl, pVals[3].data.dbl,
+					   pVals[4].data.dbl, pVals[5].data.dbl,
+					   pVals[6].data.dbl );
+	    break;
+	 case elps_fct:
+	    this->value.data.log =
+                               ellipse( pVals[0].data.dbl, pVals[1].data.dbl,
+					pVals[2].data.dbl, pVals[3].data.dbl,
+					pVals[4].data.dbl, pVals[5].data.dbl,
+					pVals[6].data.dbl );
+	    break;
+
+            /* C Conditional expression:  bool ? expr : expr */
+
+         case ifthenelse_fct:
+            switch( this->type ) {
+            case BOOLEAN:
+               this->value.data.log = ( pVals[2].data.log ?
+                                        pVals[0].data.log : pVals[1].data.log );
+               break;
+            case LONG:
+               this->value.data.lng = ( pVals[2].data.log ?
+                                        pVals[0].data.lng : pVals[1].data.lng );
+               break;
+            case DOUBLE:
+               this->value.data.dbl = ( pVals[2].data.log ?
+                                        pVals[0].data.dbl : pVals[1].data.dbl );
+               break;
+            case STRING:
+	       strcpy(this->value.data.str, ( pVals[2].data.log ?
+                                              pVals[0].data.str :
+                                              pVals[1].data.str ) );
+               break;
+            }
+            break;
+
+	    /* String functions */
+         case strmid_fct:
+	   cstrmid(this->value.data.str, this->value.nelem, 
+		   pVals[0].data.str,    pVals[0].nelem,
+		   pVals[1].data.lng);
+	   break;
+         case strpos_fct:
+	   {
+	     char *res = strstr(pVals[0].data.str, pVals[1].data.str);
+	     if (res == NULL) {
+	       this->value.data.lng = 0; 
+	     } else {
+	       this->value.data.lng = (res - pVals[0].data.str) + 1;
+	     }
+	     break;
+	   }
+
+      }
+      this->operation = CONST_OP;
+
+   } else {
+
+      Allocate_Ptrs( this );
+
+      row  = gParse.nRows;
+      elem = row * this->value.nelem;
+
+      if( !gParse.status ) {
+	 switch( this->operation ) {
+
+	    /* Special functions with no arguments */
+
+	 case row_fct:
+	    while( row-- ) {
+	       this->value.data.lngptr[row] = gParse.firstRow + row;
+	       this->value.undef[row] = 0;
+	    }
+	    break;
+	 case null_fct:
+            if( this->type==LONG ) {
+               while( row-- ) {
+                  this->value.data.lngptr[row] = 0;
+                  this->value.undef[row] = 1;
+               }
+            } else if( this->type==STRING ) {
+               while( row-- ) {
+                  this->value.data.strptr[row][0] = '\0';
+                  this->value.undef[row] = 1;
+               }
+            }
+	    break;
+	 case rnd_fct:
+	   while( elem-- ) {
+	     this->value.data.dblptr[elem] = ran1();
+	     this->value.undef[elem] = 0;
+	    }
+	    break;
+
+	 case gasrnd_fct:
+	    while( elem-- ) {
+	       this->value.data.dblptr[elem] = gasdev();
+	       this->value.undef[elem] = 0;
+	    }
+	    break;
+
+	 case poirnd_fct:
+	   if( theParams[0]->type==DOUBLE ) {
+	      if (theParams[0]->operation == CONST_OP) {
+		while( elem-- ) {
+		  this->value.undef[elem] = (pVals[0].data.dbl < 0);
+		  if (! this->value.undef[elem]) {
+		    this->value.data.lngptr[elem] = poidev(pVals[0].data.dbl);
+		  }
+		} 
+	      } else {
+		while( elem-- ) {
+		  this->value.undef[elem] = theParams[0]->value.undef[elem];
+		  if (theParams[0]->value.data.dblptr[elem] < 0) 
+		    this->value.undef[elem] = 1;
+		  if (! this->value.undef[elem]) {
+		    this->value.data.lngptr[elem] = 
+		      poidev(theParams[0]->value.data.dblptr[elem]);
+		  }
+		} /* while */
+	      } /* ! CONST_OP */
+	   } else {
+	     /* LONG */
+	      if (theParams[0]->operation == CONST_OP) {
+		while( elem-- ) {
+		  this->value.undef[elem] = (pVals[0].data.lng < 0);
+		  if (! this->value.undef[elem]) {
+		    this->value.data.lngptr[elem] = poidev(pVals[0].data.lng);
+		  }
+		} 
+	      } else {
+		while( elem-- ) {
+		  this->value.undef[elem] = theParams[0]->value.undef[elem];
+		  if (theParams[0]->value.data.lngptr[elem] < 0) 
+		    this->value.undef[elem] = 1;
+		  if (! this->value.undef[elem]) {
+		    this->value.data.lngptr[elem] = 
+		      poidev(theParams[0]->value.data.lngptr[elem]);
+		  }
+		} /* while */
+	      } /* ! CONST_OP */
+	   } /* END LONG */
+	   break;
+
+
+	    /* Non-Trig single-argument functions */
+	    
+	 case sum_fct:
+	    elem = row * theParams[0]->value.nelem;
+	    if( theParams[0]->type==BOOLEAN ) {
+	       while( row-- ) {
+		  this->value.data.lngptr[row] = 0;
+		  /* Default is UNDEF until a defined value is found */
+		  this->value.undef[row] = 1;
+		  nelem = theParams[0]->value.nelem;
+		  while( nelem-- ) {
+		     elem--;
+		     if ( ! theParams[0]->value.undef[elem] ) {
+		       this->value.data.lngptr[row] +=
+			 ( theParams[0]->value.data.logptr[elem] ? 1 : 0 );
+		       this->value.undef[row] = 0;
+		     }
+		  }
+	       }
+	    } else if( theParams[0]->type==LONG ) {
+	       while( row-- ) {
+		  this->value.data.lngptr[row] = 0;
+		  /* Default is UNDEF until a defined value is found */
+		  this->value.undef[row] = 1;
+		  nelem = theParams[0]->value.nelem;
+		  while( nelem-- ) {
+		     elem--;
+		     if ( ! theParams[0]->value.undef[elem] ) {
+		       this->value.data.lngptr[row] +=
+			 theParams[0]->value.data.lngptr[elem];
+		       this->value.undef[row] = 0;
+		     }
+		  }
+	       }		  
+	    } else if( theParams[0]->type==DOUBLE ){
+	       while( row-- ) {
+		  this->value.data.dblptr[row] = 0.0;
+		  /* Default is UNDEF until a defined value is found */
+		  this->value.undef[row] = 1;
+		  nelem = theParams[0]->value.nelem;
+		  while( nelem-- ) {
+		     elem--;
+		     if ( ! theParams[0]->value.undef[elem] ) {
+		       this->value.data.dblptr[row] +=
+			 theParams[0]->value.data.dblptr[elem];
+		       this->value.undef[row] = 0;
+		     }
+		  }
+	       }		  
+	    } else { /* BITSTR */
+	       nelem = theParams[0]->value.nelem;
+	       while( row-- ) {
+		  char *sptr1 = theParams[0]->value.data.strptr[row];
+		  this->value.data.lngptr[row] = 0;
+		  this->value.undef[row] = 0;
+		  while (*sptr1) {
+		    if (*sptr1 == '1') this->value.data.lngptr[row] ++;
+		    sptr1++;
+		  }
+	       }		  
+	    }
+	    break;
+
+	 case average_fct:
+	    elem = row * theParams[0]->value.nelem;
+	    if( theParams[0]->type==LONG ) {
+	       while( row-- ) {
+		  int count = 0;
+		  this->value.data.dblptr[row] = 0;
+		  nelem = theParams[0]->value.nelem;
+		  while( nelem-- ) {
+		     elem--;
+		     if (theParams[0]->value.undef[elem] == 0) {
+		       this->value.data.dblptr[row] +=
+			 theParams[0]->value.data.lngptr[elem];
+		       count ++;
+		     }
+		  }
+		  if (count == 0) {
+		    this->value.undef[row] = 1;
+		  } else {
+		    this->value.undef[row] = 0;
+		    this->value.data.dblptr[row] /= count;
+		  }
+	       }		  
+	    } else if( theParams[0]->type==DOUBLE ){
+	       while( row-- ) {
+		  int count = 0;
+		  this->value.data.dblptr[row] = 0;
+		  nelem = theParams[0]->value.nelem;
+		  while( nelem-- ) {
+		     elem--;
+		     if (theParams[0]->value.undef[elem] == 0) {
+		       this->value.data.dblptr[row] +=
+			 theParams[0]->value.data.dblptr[elem];
+		       count ++;
+		     }
+		  }
+		  if (count == 0) {
+		    this->value.undef[row] = 1;
+		  } else {
+		    this->value.undef[row] = 0;
+		    this->value.data.dblptr[row] /= count;
+		  }
+	       }		  
+	    }
+	    break;
+	 case stddev_fct:
+	    elem = row * theParams[0]->value.nelem;
+	    if( theParams[0]->type==LONG ) {
+
+	       /* Compute the mean value */
+	       while( row-- ) {
+		  int count = 0;
+		  double sum = 0, sum2 = 0;
+
+		  nelem = theParams[0]->value.nelem;
+		  while( nelem-- ) {
+		     elem--;
+		     if (theParams[0]->value.undef[elem] == 0) {
+		       sum += theParams[0]->value.data.lngptr[elem];
+		       count ++;
+		     }
+		  }
+		  if (count > 1) {
+		    sum /= count;
+
+		    /* Compute the sum of squared deviations */
+		    nelem = theParams[0]->value.nelem;
+		    elem += nelem;  /* Reset elem for second pass */
+		    while( nelem-- ) {
+		      elem--;
+		      if (theParams[0]->value.undef[elem] == 0) {
+			double dx = (theParams[0]->value.data.lngptr[elem] - sum);
+			sum2 += (dx*dx);
+		      }
+		    }
+
+		    sum2 /= (double)count-1;
+
+		    this->value.undef[row] = 0;
+		    this->value.data.dblptr[row] = sqrt(sum2);
+		  } else {
+		    this->value.undef[row] = 0;       /* STDDEV => 0 */
+		    this->value.data.dblptr[row] = 0;
+		  }
+	       }
+	    } else if( theParams[0]->type==DOUBLE ){
+
+	       /* Compute the mean value */
+	       while( row-- ) {
+		  int count = 0;
+		  double sum = 0, sum2 = 0;
+
+		  nelem = theParams[0]->value.nelem;
+		  while( nelem-- ) {
+		     elem--;
+		     if (theParams[0]->value.undef[elem] == 0) {
+		       sum += theParams[0]->value.data.dblptr[elem];
+		       count ++;
+		     }
+		  }
+		  if (count > 1) {
+		    sum /= count;
+
+		    /* Compute the sum of squared deviations */
+		    nelem = theParams[0]->value.nelem;
+		    elem += nelem;  /* Reset elem for second pass */
+		    while( nelem-- ) {
+		      elem--;
+		      if (theParams[0]->value.undef[elem] == 0) {
+			double dx = (theParams[0]->value.data.dblptr[elem] - sum);
+			sum2 += (dx*dx);
+		      }
+		    }
+
+		    sum2 /= (double)count-1;
+
+		    this->value.undef[row] = 0;
+		    this->value.data.dblptr[row] = sqrt(sum2);
+		  } else {
+		    this->value.undef[row] = 0;       /* STDDEV => 0 */
+		    this->value.data.dblptr[row] = 0;
+		  }
+	       }
+	    }
+	    break;
+
+	 case median_fct:
+	   elem = row * theParams[0]->value.nelem;
+	   nelem = theParams[0]->value.nelem;
+	   if( theParams[0]->type==LONG ) {
+	       long *dptr = theParams[0]->value.data.lngptr;
+	       char *uptr = theParams[0]->value.undef;
+	       long *mptr = (long *) malloc(sizeof(long)*nelem);
+	       int irow;
+
+	       /* Allocate temporary storage for this row, since the
+                  quickselect function will scramble the contents */
+	       if (mptr == 0) {
+		 fferror("Could not allocate temporary memory in median function");
+		 free( this->value.data.ptr );
+		 break;
+	       }
+
+	       for (irow=0; irow<row; irow++) {
+		  long *p = mptr;
+		  int nelem1 = nelem;
+		  int count = 0;
+
+		  while ( nelem1-- ) { 
+		    if (*uptr == 0) {
+		      *p++ = *dptr;   /* Only advance the dest pointer if we copied */
+		    }
+		    dptr ++;  /* Advance the source pointer ... */
+		    uptr ++;  /* ... and source "undef" pointer */
+		  }
+		  
+		  nelem1 = (p - mptr);  /* Number of accepted data points */
+		  if (nelem1 > 0) {
+		    this->value.undef[irow] = 0;
+		    this->value.data.lngptr[irow] = qselect_median_lng(mptr, nelem1);
+		  } else {
+		    this->value.undef[irow] = 1;
+		    this->value.data.lngptr[irow] = 0;
+		  }
+		    
+	       }		  
+
+	       free(mptr);
+	    } else {
+	       double *dptr = theParams[0]->value.data.dblptr;
+	       char   *uptr = theParams[0]->value.undef;
+	       double *mptr = (double *) malloc(sizeof(double)*nelem);
+	       int irow;
+
+	       /* Allocate temporary storage for this row, since the
+                  quickselect function will scramble the contents */
+	       if (mptr == 0) {
+		 fferror("Could not allocate temporary memory in median function");
+		 free( this->value.data.ptr );
+		 break;
+	       }
+
+	       for (irow=0; irow<row; irow++) {
+		  double *p = mptr;
+		  int nelem1 = nelem;
+
+		  while ( nelem1-- ) { 
+		    if (*uptr == 0) {
+		      *p++ = *dptr;   /* Only advance the dest pointer if we copied */
+		    }
+		    dptr ++;  /* Advance the source pointer ... */
+		    uptr ++;  /* ... and source "undef" pointer */
+		  }
+
+		  nelem1 = (p - mptr);  /* Number of accepted data points */
+		  if (nelem1 > 0) {
+		    this->value.undef[irow] = 0;
+		    this->value.data.dblptr[irow] = qselect_median_dbl(mptr, nelem1);
+		  } else {
+		    this->value.undef[irow] = 1;
+		    this->value.data.dblptr[irow] = 0;
+		  }
+
+	       }
+	       free(mptr);
+	    }
+	    break;
+	 case abs_fct:
+	    if( theParams[0]->type==DOUBLE )
+	       while( elem-- ) {
+		  dval = theParams[0]->value.data.dblptr[elem];
+		  this->value.data.dblptr[elem] = (dval>0.0 ? dval : -dval);
+		  this->value.undef[elem] = theParams[0]->value.undef[elem];
+	       }
+	    else
+	       while( elem-- ) {
+		  ival = theParams[0]->value.data.lngptr[elem];
+		  this->value.data.lngptr[elem] = (ival> 0  ? ival : -ival);
+		  this->value.undef[elem] = theParams[0]->value.undef[elem];
+	       }
+	    break;
+
+            /* Special Null-Handling Functions */
+
+	 case nonnull_fct:
+	   nelem = theParams[0]->value.nelem;
+	   if ( theParams[0]->type==STRING ) nelem = 1;
+	   elem = row * nelem;
+	   while( row-- ) {
+	     int nelem1 = nelem;
+
+	     this->value.undef[row] = 0;        /* Initialize to 0 (defined) */
+	     this->value.data.lngptr[row] = 0;
+	     while( nelem1-- ) {	
+	       elem --;
+	       if ( theParams[0]->value.undef[elem] == 0 ) this->value.data.lngptr[row] ++;
+	     }
+	   }
+	   break;
+	 case isnull_fct:
+	    if( theParams[0]->type==STRING ) elem = row;
+	    while( elem-- ) {
+	       this->value.data.logptr[elem] = theParams[0]->value.undef[elem];
+	       this->value.undef[elem] = 0;
+	    }
+	    break;
+         case defnull_fct:
+	    switch( this->type ) {
+	    case BOOLEAN:
+	       while( row-- ) {
+		  nelem = this->value.nelem;
+		  while( nelem-- ) {
+		     elem--;
+		     i=2; while( i-- )
+			if( vector[i]>1 ) {
+			   pNull[i] = theParams[i]->value.undef[elem];
+			   pVals[i].data.log =
+			      theParams[i]->value.data.logptr[elem];
+			} else if( vector[i] ) {
+			   pNull[i] = theParams[i]->value.undef[row];
+			   pVals[i].data.log =
+			      theParams[i]->value.data.logptr[row];
+			}
+		     if( pNull[0] ) {
+			this->value.undef[elem] = pNull[1];
+			this->value.data.logptr[elem] = pVals[1].data.log;
+		     } else {
+			this->value.undef[elem] = 0;
+			this->value.data.logptr[elem] = pVals[0].data.log;
+		     }
+		  }
+	       }
+	       break;
+	    case LONG:
+	       while( row-- ) {
+		  nelem = this->value.nelem;
+		  while( nelem-- ) {
+		     elem--;
+		     i=2; while( i-- )
+			if( vector[i]>1 ) {
+			   pNull[i] = theParams[i]->value.undef[elem];
+			   pVals[i].data.lng =
+			      theParams[i]->value.data.lngptr[elem];
+			} else if( vector[i] ) {
+			   pNull[i] = theParams[i]->value.undef[row];
+			   pVals[i].data.lng =
+			      theParams[i]->value.data.lngptr[row];
+			}
+		     if( pNull[0] ) {
+			this->value.undef[elem] = pNull[1];
+			this->value.data.lngptr[elem] = pVals[1].data.lng;
+		     } else {
+			this->value.undef[elem] = 0;
+			this->value.data.lngptr[elem] = pVals[0].data.lng;
+		     }
+		  }
+	       }
+	       break;
+	    case DOUBLE:
+	       while( row-- ) {
+		  nelem = this->value.nelem;
+		  while( nelem-- ) {
+		     elem--;
+		     i=2; while( i-- )
+			if( vector[i]>1 ) {
+			   pNull[i] = theParams[i]->value.undef[elem];
+			   pVals[i].data.dbl =
+			      theParams[i]->value.data.dblptr[elem];
+			} else if( vector[i] ) {
+			   pNull[i] = theParams[i]->value.undef[row];
+			   pVals[i].data.dbl =
+			      theParams[i]->value.data.dblptr[row];
+			}
+		     if( pNull[0] ) {
+			this->value.undef[elem] = pNull[1];
+			this->value.data.dblptr[elem] = pVals[1].data.dbl;
+		     } else {
+			this->value.undef[elem] = 0;
+			this->value.data.dblptr[elem] = pVals[0].data.dbl;
+		     }
+		  }
+	       }
+	       break;
+	    case STRING:
+	       while( row-- ) {
+		  i=2; while( i-- )
+		     if( vector[i] ) {
+			pNull[i] = theParams[i]->value.undef[row];
+			strcpy(pVals[i].data.str,
+			       theParams[i]->value.data.strptr[row]);
+		     }
+		  if( pNull[0] ) {
+		     this->value.undef[row] = pNull[1];
+		     strcpy(this->value.data.strptr[row],pVals[1].data.str);
+		  } else {
+		     this->value.undef[elem] = 0;
+		     strcpy(this->value.data.strptr[row],pVals[0].data.str);
+		  }
+	       }
+	    }
+	    break;
+
+	    /* Math functions with 1 double argument */
+
+	 case sin_fct:
+	    while( elem-- )
+	       if( !(this->value.undef[elem] = theParams[0]->value.undef[elem]) ) {
+		  this->value.data.dblptr[elem] = 
+		     sin( theParams[0]->value.data.dblptr[elem] );
+	       }
+	    break;
+	 case cos_fct:
+	    while( elem-- )
+	       if( !(this->value.undef[elem] = theParams[0]->value.undef[elem]) ) {
+		  this->value.data.dblptr[elem] = 
+		     cos( theParams[0]->value.data.dblptr[elem] );
+	       }
+	    break;
+	 case tan_fct:
+	    while( elem-- )
+	       if( !(this->value.undef[elem] = theParams[0]->value.undef[elem]) ) {
+		  this->value.data.dblptr[elem] = 
+		     tan( theParams[0]->value.data.dblptr[elem] );
+	       }
+	    break;
+	 case asin_fct:
+	    while( elem-- )
+	       if( !(this->value.undef[elem] = theParams[0]->value.undef[elem]) ) {
+		  dval = theParams[0]->value.data.dblptr[elem];
+		  if( dval<-1.0 || dval>1.0 ) {
+		     this->value.data.dblptr[elem] = 0.0;
+		     this->value.undef[elem] = 1;
+		  } else
+		     this->value.data.dblptr[elem] = asin( dval );
+	       }
+	    break;
+	 case acos_fct:
+	    while( elem-- )
+	       if( !(this->value.undef[elem] = theParams[0]->value.undef[elem]) ) {
+		  dval = theParams[0]->value.data.dblptr[elem];
+		  if( dval<-1.0 || dval>1.0 ) {
+		     this->value.data.dblptr[elem] = 0.0;
+		     this->value.undef[elem] = 1;
+		  } else
+		     this->value.data.dblptr[elem] = acos( dval );
+	       }
+	    break;
+	 case atan_fct:
+	    while( elem-- )
+	       if( !(this->value.undef[elem] = theParams[0]->value.undef[elem]) ) {
+		  dval = theParams[0]->value.data.dblptr[elem];
+		  this->value.data.dblptr[elem] = atan( dval );
+	       }
+	    break;
+	 case sinh_fct:
+	    while( elem-- )
+	       if( !(this->value.undef[elem] = theParams[0]->value.undef[elem]) ) {
+		  this->value.data.dblptr[elem] = 
+		     sinh( theParams[0]->value.data.dblptr[elem] );
+	       }
+	    break;
+	 case cosh_fct:
+	    while( elem-- )
+	       if( !(this->value.undef[elem] = theParams[0]->value.undef[elem]) ) {
+		  this->value.data.dblptr[elem] = 
+		     cosh( theParams[0]->value.data.dblptr[elem] );
+	       }
+	    break;
+	 case tanh_fct:
+	    while( elem-- )
+	       if( !(this->value.undef[elem] = theParams[0]->value.undef[elem]) ) {
+		  this->value.data.dblptr[elem] = 
+		     tanh( theParams[0]->value.data.dblptr[elem] );
+	       }
+	    break;
+	 case exp_fct:
+	    while( elem-- )
+	       if( !(this->value.undef[elem] = theParams[0]->value.undef[elem]) ) {
+		  dval = theParams[0]->value.data.dblptr[elem];
+		  this->value.data.dblptr[elem] = exp( dval );
+	       }
+	    break;
+	 case log_fct:
+	    while( elem-- )
+	       if( !(this->value.undef[elem] = theParams[0]->value.undef[elem]) ) {
+		  dval = theParams[0]->value.data.dblptr[elem];
+		  if( dval<=0.0 ) {
+		     this->value.data.dblptr[elem] = 0.0;
+		     this->value.undef[elem] = 1;
+		  } else
+		     this->value.data.dblptr[elem] = log( dval );
+	       }
+	    break;
+	 case log10_fct:
+	    while( elem-- )
+	       if( !(this->value.undef[elem] = theParams[0]->value.undef[elem]) ) {
+		  dval = theParams[0]->value.data.dblptr[elem];
+		  if( dval<=0.0 ) {
+		     this->value.data.dblptr[elem] = 0.0;
+		     this->value.undef[elem] = 1;
+		  } else
+		     this->value.data.dblptr[elem] = log10( dval );
+	       }
+	    break;
+	 case sqrt_fct:
+	    while( elem-- )
+	       if( !(this->value.undef[elem] = theParams[0]->value.undef[elem]) ) {
+		  dval = theParams[0]->value.data.dblptr[elem];
+		  if( dval<0.0 ) {
+		     this->value.data.dblptr[elem] = 0.0;
+		     this->value.undef[elem] = 1;
+		  } else
+		     this->value.data.dblptr[elem] = sqrt( dval );
+	       }
+	    break;
+	 case ceil_fct:
+	    while( elem-- )
+	       if( !(this->value.undef[elem] = theParams[0]->value.undef[elem]) ) {
+		  this->value.data.dblptr[elem] = 
+		     ceil( theParams[0]->value.data.dblptr[elem] );
+	       }
+	    break;
+	 case floor_fct:
+	    while( elem-- )
+	       if( !(this->value.undef[elem] = theParams[0]->value.undef[elem]) ) {
+		  this->value.data.dblptr[elem] = 
+		     floor( theParams[0]->value.data.dblptr[elem] );
+	       }
+	    break;
+	 case round_fct:
+	    while( elem-- )
+	       if( !(this->value.undef[elem] = theParams[0]->value.undef[elem]) ) {
+		  this->value.data.dblptr[elem] = 
+		     floor( theParams[0]->value.data.dblptr[elem] + 0.5);
+	       }
+	    break;
+
+	    /* Two-argument Trig Functions */
+	    
+	 case atan2_fct:
+	    while( row-- ) {
+	       nelem = this->value.nelem;
+	       while( nelem-- ) {
+		  elem--;
+		  i=2; while( i-- )
+		     if( vector[i]>1 ) {
+			pVals[i].data.dbl =
+			   theParams[i]->value.data.dblptr[elem];
+			pNull[i] = theParams[i]->value.undef[elem];
+		     } else if( vector[i] ) {
+			pVals[i].data.dbl =
+			   theParams[i]->value.data.dblptr[row];
+			pNull[i] = theParams[i]->value.undef[row];
+		     }
+		  if( !(this->value.undef[elem] = (pNull[0] || pNull[1]) ) )
+		     this->value.data.dblptr[elem] =
+			atan2( pVals[0].data.dbl, pVals[1].data.dbl );
+	       }
+	    }
+	    break;
+
+	    /* Four-argument ANGSEP Function */
+	    
+	 case angsep_fct:
+	    while( row-- ) {
+	       nelem = this->value.nelem;
+	       while( nelem-- ) {
+		  elem--;
+		  i=4; while( i-- )
+		     if( vector[i]>1 ) {
+			pVals[i].data.dbl =
+			   theParams[i]->value.data.dblptr[elem];
+			pNull[i] = theParams[i]->value.undef[elem];
+		     } else if( vector[i] ) {
+			pVals[i].data.dbl =
+			   theParams[i]->value.data.dblptr[row];
+			pNull[i] = theParams[i]->value.undef[row];
+		     }
+		  if( !(this->value.undef[elem] = (pNull[0] || pNull[1] ||
+						   pNull[2] || pNull[3]) ) )
+		     this->value.data.dblptr[elem] =
+		       angsep_calc(pVals[0].data.dbl, pVals[1].data.dbl,
+				   pVals[2].data.dbl, pVals[3].data.dbl);
+	       }
+	    }
+	    break;
+
+
+
+	    /*  Min/Max functions taking 1 or 2 arguments  */
+
+         case min1_fct:
+	    elem = row * theParams[0]->value.nelem;
+	    if( this->type==LONG ) {
+	       long minVal=0;
+	       while( row-- ) {
+		  valInit = 1;
+		  this->value.undef[row] = 1;
+		  nelem = theParams[0]->value.nelem;
+		  while( nelem-- ) {
+		     elem--;
+		     if ( !theParams[0]->value.undef[elem] ) {
+		       if ( valInit ) {
+			 valInit = 0;
+			 minVal  = theParams[0]->value.data.lngptr[elem];
+		       } else {
+			 minVal  = minvalue( minVal,
+					     theParams[0]->value.data.lngptr[elem] );
+		       }
+		       this->value.undef[row] = 0;
+		     }
+		  }  
+		  this->value.data.lngptr[row] = minVal;
+	       }		  
+	    } else if( this->type==DOUBLE ) {
+	       double minVal=0.0;
+	       while( row-- ) {
+		  valInit = 1;
+		  this->value.undef[row] = 1;
+		  nelem = theParams[0]->value.nelem;
+		  while( nelem-- ) {
+		     elem--;
+		     if ( !theParams[0]->value.undef[elem] ) {
+		       if ( valInit ) {
+			 valInit = 0;
+			 minVal  = theParams[0]->value.data.dblptr[elem];
+		       } else {
+			 minVal  = minvalue( minVal,
+					     theParams[0]->value.data.dblptr[elem] );
+		       }
+		       this->value.undef[row] = 0;
+		     }
+		  }  
+		  this->value.data.dblptr[row] = minVal;
+	       }		  
+	    } else if( this->type==BITSTR ) {
+	       char minVal;
+	       while( row-- ) {
+		  char *sptr1 = theParams[0]->value.data.strptr[row];
+		  minVal = '1';
+		  while (*sptr1) {
+		    if (*sptr1 == '0') minVal = '0';
+		    sptr1++;
+		  }
+		  this->value.data.strptr[row][0] = minVal;
+		  this->value.data.strptr[row][1] = 0;     /* Null terminate */
+	       }		  
+	    }
+	    break;
+         case min2_fct:
+	    if( this->type==LONG ) {
+	       while( row-- ) {
+		  nelem = this->value.nelem;
+		  while( nelem-- ) {
+		     elem--;
+		     i=2; while( i-- )
+			if( vector[i]>1 ) {
+			   pVals[i].data.lng =
+			      theParams[i]->value.data.lngptr[elem];
+			   pNull[i] = theParams[i]->value.undef[elem];
+			} else if( vector[i] ) {
+			   pVals[i].data.lng =
+			      theParams[i]->value.data.lngptr[row];
+			   pNull[i] = theParams[i]->value.undef[row];
+			}
+		     if( pNull[0] && pNull[1] ) {
+		       this->value.undef[elem] = 1;
+		       this->value.data.lngptr[elem] = 0;
+		     } else if (pNull[0]) {
+		       this->value.undef[elem] = 0;
+		       this->value.data.lngptr[elem] = pVals[1].data.lng;
+		     } else if (pNull[1]) {
+		       this->value.undef[elem] = 0;
+		       this->value.data.lngptr[elem] = pVals[0].data.lng;
+		     } else {
+		       this->value.undef[elem] = 0;
+		       this->value.data.lngptr[elem] =
+			 minvalue( pVals[0].data.lng, pVals[1].data.lng );
+		     }
+		  }
+	       }
+	    } else if( this->type==DOUBLE ) {
+	       while( row-- ) {
+		  nelem = this->value.nelem;
+		  while( nelem-- ) {
+		     elem--;
+		     i=2; while( i-- )
+			if( vector[i]>1 ) {
+			   pVals[i].data.dbl =
+			      theParams[i]->value.data.dblptr[elem];
+			   pNull[i] = theParams[i]->value.undef[elem];
+			} else if( vector[i] ) {
+			   pVals[i].data.dbl =
+			      theParams[i]->value.data.dblptr[row];
+			   pNull[i] = theParams[i]->value.undef[row];
+			}
+		     if( pNull[0] && pNull[1] ) {
+		       this->value.undef[elem] = 1;
+		       this->value.data.dblptr[elem] = 0;
+		     } else if (pNull[0]) {
+		       this->value.undef[elem] = 0;
+		       this->value.data.dblptr[elem] = pVals[1].data.dbl;
+		     } else if (pNull[1]) {
+		       this->value.undef[elem] = 0;
+		       this->value.data.dblptr[elem] = pVals[0].data.dbl;
+		     } else {
+		       this->value.undef[elem] = 0;
+		       this->value.data.dblptr[elem] =
+			 minvalue( pVals[0].data.dbl, pVals[1].data.dbl );
+		     }
+		  }
+ 	       }
+	    }
+	    break;
+
+         case max1_fct:
+	    elem = row * theParams[0]->value.nelem;
+	    if( this->type==LONG ) {
+	       long maxVal=0;
+	       while( row-- ) {
+		  valInit = 1;
+		  this->value.undef[row] = 1;
+		  nelem = theParams[0]->value.nelem;
+		  while( nelem-- ) {
+		     elem--;
+		     if ( !theParams[0]->value.undef[elem] ) {
+		       if ( valInit ) {
+			 valInit = 0;
+			 maxVal  = theParams[0]->value.data.lngptr[elem];
+		       } else {
+			 maxVal  = maxvalue( maxVal,
+					     theParams[0]->value.data.lngptr[elem] );
+		       }
+		       this->value.undef[row] = 0;
+		     }
+		  }
+		  this->value.data.lngptr[row] = maxVal;
+	       }		  
+	    } else if( this->type==DOUBLE ) {
+	       double maxVal=0.0;
+	       while( row-- ) {
+		  valInit = 1;
+		  this->value.undef[row] = 1;
+		  nelem = theParams[0]->value.nelem;
+		  while( nelem-- ) {
+		     elem--;
+		     if ( !theParams[0]->value.undef[elem] ) {
+		       if ( valInit ) {
+			 valInit = 0;
+			 maxVal  = theParams[0]->value.data.dblptr[elem];
+		       } else {
+			 maxVal  = maxvalue( maxVal,
+					     theParams[0]->value.data.dblptr[elem] );
+		       }
+		       this->value.undef[row] = 0;
+		     }
+		  }
+		  this->value.data.dblptr[row] = maxVal;
+	       }		  
+	    } else if( this->type==BITSTR ) {
+	       char maxVal;
+	       while( row-- ) {
+		  char *sptr1 = theParams[0]->value.data.strptr[row];
+		  maxVal = '0';
+		  while (*sptr1) {
+		    if (*sptr1 == '1') maxVal = '1';
+		    sptr1++;
+		  }
+		  this->value.data.strptr[row][0] = maxVal;
+		  this->value.data.strptr[row][1] = 0;     /* Null terminate */
+	       }		  
+	    }
+	    break;
+         case max2_fct:
+	    if( this->type==LONG ) {
+	       while( row-- ) {
+		  nelem = this->value.nelem;
+		  while( nelem-- ) {
+		     elem--;
+		     i=2; while( i-- )
+			if( vector[i]>1 ) {
+			   pVals[i].data.lng =
+			      theParams[i]->value.data.lngptr[elem];
+			   pNull[i] = theParams[i]->value.undef[elem];
+			} else if( vector[i] ) {
+			   pVals[i].data.lng =
+			      theParams[i]->value.data.lngptr[row];
+			   pNull[i] = theParams[i]->value.undef[row];
+			}
+		     if( pNull[0] && pNull[1] ) {
+		       this->value.undef[elem] = 1;
+		       this->value.data.lngptr[elem] = 0;
+		     } else if (pNull[0]) {
+		       this->value.undef[elem] = 0;
+		       this->value.data.lngptr[elem] = pVals[1].data.lng;
+		     } else if (pNull[1]) {
+		       this->value.undef[elem] = 0;
+		       this->value.data.lngptr[elem] = pVals[0].data.lng;
+		     } else {
+		       this->value.undef[elem] = 0;
+		       this->value.data.lngptr[elem] =
+			 maxvalue( pVals[0].data.lng, pVals[1].data.lng );
+		     }
+		  }
+	       }
+	    } else if( this->type==DOUBLE ) {
+	       while( row-- ) {
+		  nelem = this->value.nelem;
+		  while( nelem-- ) {
+		     elem--;
+		     i=2; while( i-- )
+			if( vector[i]>1 ) {
+			   pVals[i].data.dbl =
+			      theParams[i]->value.data.dblptr[elem];
+			   pNull[i] = theParams[i]->value.undef[elem];
+			} else if( vector[i] ) {
+			   pVals[i].data.dbl =
+			      theParams[i]->value.data.dblptr[row];
+			   pNull[i] = theParams[i]->value.undef[row];
+			}
+		     if( pNull[0] && pNull[1] ) {
+		       this->value.undef[elem] = 1;
+		       this->value.data.dblptr[elem] = 0;
+		     } else if (pNull[0]) {
+		       this->value.undef[elem] = 0;
+		       this->value.data.dblptr[elem] = pVals[1].data.dbl;
+		     } else if (pNull[1]) {
+		       this->value.undef[elem] = 0;
+		       this->value.data.dblptr[elem] = pVals[0].data.dbl;
+		     } else {
+		       this->value.undef[elem] = 0;
+		       this->value.data.dblptr[elem] =
+			 maxvalue( pVals[0].data.dbl, pVals[1].data.dbl );
+		     }
+		  }
+	       }
+	    }
+	    break;
+
+	    /* Boolean SAO region Functions... scalar or vector dbls */
+
+	 case near_fct:
+	    while( row-- ) {
+	       nelem = this->value.nelem;
+	       while( nelem-- ) {
+		  elem--;
+		  i=3; while( i-- )
+		     if( vector[i]>1 ) {
+			pVals[i].data.dbl =
+			   theParams[i]->value.data.dblptr[elem];
+			pNull[i] = theParams[i]->value.undef[elem];
+		     } else if( vector[i] ) {
+			pVals[i].data.dbl =
+			   theParams[i]->value.data.dblptr[row];
+			pNull[i] = theParams[i]->value.undef[row];
+		     }
+		  if( !(this->value.undef[elem] = (pNull[0] || pNull[1] ||
+						   pNull[2]) ) )
+		    this->value.data.logptr[elem] =
+		      bnear( pVals[0].data.dbl, pVals[1].data.dbl,
+			     pVals[2].data.dbl );
+	       }
+	    }
+	    break;
+
+	 case circle_fct:
+	    while( row-- ) {
+	       nelem = this->value.nelem;
+	       while( nelem-- ) {
+		  elem--;
+		  i=5; while( i-- )
+		     if( vector[i]>1 ) {
+			pVals[i].data.dbl =
+			   theParams[i]->value.data.dblptr[elem];
+			pNull[i] = theParams[i]->value.undef[elem];
+		     } else if( vector[i] ) {
+			pVals[i].data.dbl =
+			   theParams[i]->value.data.dblptr[row];
+			pNull[i] = theParams[i]->value.undef[row];
+		     }
+		  if( !(this->value.undef[elem] = (pNull[0] || pNull[1] ||
+						   pNull[2] || pNull[3] ||
+						   pNull[4]) ) )
+		    this->value.data.logptr[elem] =
+		     circle( pVals[0].data.dbl, pVals[1].data.dbl,
+			     pVals[2].data.dbl, pVals[3].data.dbl,
+			     pVals[4].data.dbl );
+	       }
+	    }
+	    break;
+
+	 case box_fct:
+	    while( row-- ) {
+	       nelem = this->value.nelem;
+	       while( nelem-- ) {
+		  elem--;
+		  i=7; while( i-- )
+		     if( vector[i]>1 ) {
+			pVals[i].data.dbl =
+			   theParams[i]->value.data.dblptr[elem];
+			pNull[i] = theParams[i]->value.undef[elem];
+		     } else if( vector[i] ) {
+			pVals[i].data.dbl =
+			   theParams[i]->value.data.dblptr[row];
+			pNull[i] = theParams[i]->value.undef[row];
+		     }
+		  if( !(this->value.undef[elem] = (pNull[0] || pNull[1] ||
+						   pNull[2] || pNull[3] ||
+						   pNull[4] || pNull[5] ||
+						   pNull[6] ) ) )
+		    this->value.data.logptr[elem] =
+		     saobox( pVals[0].data.dbl, pVals[1].data.dbl,
+			     pVals[2].data.dbl, pVals[3].data.dbl,
+			     pVals[4].data.dbl, pVals[5].data.dbl,
+			     pVals[6].data.dbl );	
+	       }
+	    }
+	    break;
+
+	 case elps_fct:
+	    while( row-- ) {
+	       nelem = this->value.nelem;
+	       while( nelem-- ) {
+		  elem--;
+		  i=7; while( i-- )
+		     if( vector[i]>1 ) {
+			pVals[i].data.dbl =
+			   theParams[i]->value.data.dblptr[elem];
+			pNull[i] = theParams[i]->value.undef[elem];
+		     } else if( vector[i] ) {
+			pVals[i].data.dbl =
+			   theParams[i]->value.data.dblptr[row];
+			pNull[i] = theParams[i]->value.undef[row];
+		     }
+		  if( !(this->value.undef[elem] = (pNull[0] || pNull[1] ||
+						   pNull[2] || pNull[3] ||
+						   pNull[4] || pNull[5] ||
+						   pNull[6] ) ) )
+		    this->value.data.logptr[elem] =
+		     ellipse( pVals[0].data.dbl, pVals[1].data.dbl,
+			      pVals[2].data.dbl, pVals[3].data.dbl,
+			      pVals[4].data.dbl, pVals[5].data.dbl,
+			      pVals[6].data.dbl );
+	       }
+	    }
+	    break;
+
+            /* C Conditional expression:  bool ? expr : expr */
+
+         case ifthenelse_fct:
+            switch( this->type ) {
+            case BOOLEAN:
+	       while( row-- ) {
+		  nelem = this->value.nelem;
+		  while( nelem-- ) {
+		     elem--;
+                     if( vector[2]>1 ) {
+                        pVals[2].data.log =
+                           theParams[2]->value.data.logptr[elem];
+                        pNull[2] = theParams[2]->value.undef[elem];
+                     } else if( vector[2] ) {
+                        pVals[2].data.log =
+                           theParams[2]->value.data.logptr[row];
+                        pNull[2] = theParams[2]->value.undef[row];
+                     }
+		     i=2; while( i-- )
+			if( vector[i]>1 ) {
+			   pVals[i].data.log =
+			      theParams[i]->value.data.logptr[elem];
+			   pNull[i] = theParams[i]->value.undef[elem];
+			} else if( vector[i] ) {
+			   pVals[i].data.log =
+			      theParams[i]->value.data.logptr[row];
+			   pNull[i] = theParams[i]->value.undef[row];
+			}
+		     if( !(this->value.undef[elem] = pNull[2]) ) {
+                        if( pVals[2].data.log ) {
+                           this->value.data.logptr[elem] = pVals[0].data.log;
+                           this->value.undef[elem]       = pNull[0];
+                        } else {
+                           this->value.data.logptr[elem] = pVals[1].data.log;
+                           this->value.undef[elem]       = pNull[1];
+                        }
+                     }
+		  }
+	       }
+               break;
+            case LONG:
+	       while( row-- ) {
+		  nelem = this->value.nelem;
+		  while( nelem-- ) {
+		     elem--;
+                     if( vector[2]>1 ) {
+                        pVals[2].data.log =
+                           theParams[2]->value.data.logptr[elem];
+                        pNull[2] = theParams[2]->value.undef[elem];
+                     } else if( vector[2] ) {
+                        pVals[2].data.log =
+                           theParams[2]->value.data.logptr[row];
+                        pNull[2] = theParams[2]->value.undef[row];
+                     }
+		     i=2; while( i-- )
+			if( vector[i]>1 ) {
+			   pVals[i].data.lng =
+			      theParams[i]->value.data.lngptr[elem];
+			   pNull[i] = theParams[i]->value.undef[elem];
+			} else if( vector[i] ) {
+			   pVals[i].data.lng =
+			      theParams[i]->value.data.lngptr[row];
+			   pNull[i] = theParams[i]->value.undef[row];
+			}
+		     if( !(this->value.undef[elem] = pNull[2]) ) {
+                        if( pVals[2].data.log ) {
+                           this->value.data.lngptr[elem] = pVals[0].data.lng;
+                           this->value.undef[elem]       = pNull[0];
+                        } else {
+                           this->value.data.lngptr[elem] = pVals[1].data.lng;
+                           this->value.undef[elem]       = pNull[1];
+                        }
+                     }
+		  }
+	       }
+               break;
+            case DOUBLE:
+	       while( row-- ) {
+		  nelem = this->value.nelem;
+		  while( nelem-- ) {
+		     elem--;
+                     if( vector[2]>1 ) {
+                        pVals[2].data.log =
+                           theParams[2]->value.data.logptr[elem];
+                        pNull[2] = theParams[2]->value.undef[elem];
+                     } else if( vector[2] ) {
+                        pVals[2].data.log =
+                           theParams[2]->value.data.logptr[row];
+                        pNull[2] = theParams[2]->value.undef[row];
+                     }
+		     i=2; while( i-- )
+			if( vector[i]>1 ) {
+			   pVals[i].data.dbl =
+			      theParams[i]->value.data.dblptr[elem];
+			   pNull[i] = theParams[i]->value.undef[elem];
+			} else if( vector[i] ) {
+			   pVals[i].data.dbl =
+			      theParams[i]->value.data.dblptr[row];
+			   pNull[i] = theParams[i]->value.undef[row];
+			}
+		     if( !(this->value.undef[elem] = pNull[2]) ) {
+                        if( pVals[2].data.log ) {
+                           this->value.data.dblptr[elem] = pVals[0].data.dbl;
+                           this->value.undef[elem]       = pNull[0];
+                        } else {
+                           this->value.data.dblptr[elem] = pVals[1].data.dbl;
+                           this->value.undef[elem]       = pNull[1];
+                        }
+                     }
+		  }
+	       }
+               break;
+            case STRING:
+	       while( row-- ) {
+                  if( vector[2] ) {
+                     pVals[2].data.log = theParams[2]->value.data.logptr[row];
+                     pNull[2] = theParams[2]->value.undef[row];
+                  }
+                  i=2; while( i-- )
+                     if( vector[i] ) {
+                        strcpy( pVals[i].data.str,
+                                theParams[i]->value.data.strptr[row] );
+                        pNull[i] = theParams[i]->value.undef[row];
+                     }
+                  if( !(this->value.undef[row] = pNull[2]) ) {
+                     if( pVals[2].data.log ) {
+                        strcpy( this->value.data.strptr[row],
+                                pVals[0].data.str );
+                        this->value.undef[row]       = pNull[0];
+                     } else {
+                        strcpy( this->value.data.strptr[row],
+                                pVals[1].data.str );
+                        this->value.undef[row]       = pNull[1];
+                     }
+                  } else {
+                     this->value.data.strptr[row][0] = '\0';
+                  }
+	       }
+               break;
+
+            }
+            break;
+
+	    /* String functions */
+            case strmid_fct:
+	      {
+		int strconst = theParams[0]->operation == CONST_OP;
+		int posconst = theParams[1]->operation == CONST_OP;
+		int lenconst = theParams[2]->operation == CONST_OP;
+		int dest_len = this->value.nelem;
+		int src_len  = theParams[0]->value.nelem;
+
+		while (row--) {
+		  int pos;
+		  int len;
+		  char *str;
+		  int undef = 0;
+
+		  if (posconst) {
+		    pos = theParams[1]->value.data.lng;
+		  } else {
+		    pos = theParams[1]->value.data.lngptr[row];
+		    if (theParams[1]->value.undef[row]) undef = 1;
+		  }
+		  if (strconst) {
+		    str = theParams[0]->value.data.str;
+		    if (src_len == 0) src_len = strlen(str);
+		  } else {
+		    str = theParams[0]->value.data.strptr[row];
+		    if (theParams[0]->value.undef[row]) undef = 1;
+		  }
+		  if (lenconst) {
+		    len = dest_len;
+		  } else {
+		    len = theParams[2]->value.data.lngptr[row];
+		    if (theParams[2]->value.undef[row]) undef = 1;
+		  }
+		  this->value.data.strptr[row][0] = '\0';
+		  if (pos == 0) undef = 1;
+		  if (! undef ) {
+		    if (cstrmid(this->value.data.strptr[row], len,
+				str, src_len, pos) < 0) break;
+		  }
+		  this->value.undef[row] = undef;
+		}
+	      }		      
+	      break;
+
+	    /* String functions */
+            case strpos_fct:
+	      {
+		int const1 = theParams[0]->operation == CONST_OP;
+		int const2 = theParams[1]->operation == CONST_OP;
+
+		while (row--) {
+		  char *str1, *str2;
+		  int undef = 0;
+
+		  if (const1) {
+		    str1 = theParams[0]->value.data.str;
+		  } else {
+		    str1 = theParams[0]->value.data.strptr[row];
+		    if (theParams[0]->value.undef[row]) undef = 1;
+		  }
+		  if (const2) {
+		    str2 = theParams[1]->value.data.str;
+		  } else {
+		    str2 = theParams[1]->value.data.strptr[row];
+		    if (theParams[1]->value.undef[row]) undef = 1;
+		  }
+		  this->value.data.lngptr[row] = 0;
+		  if (! undef ) {
+		    char *res = strstr(str1, str2);
+		    if (res == NULL) {
+		      undef = 1;
+		      this->value.data.lngptr[row] = 0; 
+		    } else {
+		      this->value.data.lngptr[row] = (res - str1) + 1;
+		    }
+		  }
+		  this->value.undef[row] = undef;
+		}
+	      }
+	      break;
+
+		    
+	 } /* End switch(this->operation) */
+      } /* End if (!gParse.status) */
+   } /* End non-constant operations */
+
+   i = this->nSubNodes;
+   while( i-- ) {
+      if( theParams[i]->operation>0 ) {
+	 /*  Currently only numeric params allowed  */
+	 free( theParams[i]->value.data.ptr );
+      }
+   }
+}
+
+static void Do_Deref( Node *this )
+{
+   Node *theVar, *theDims[MAXDIMS];
+   int  isConst[MAXDIMS], allConst;
+   long dimVals[MAXDIMS];
+   int  i, nDims;
+   long row, elem, dsize;
+
+   theVar = gParse.Nodes + this->SubNodes[0];
+
+   i = nDims = this->nSubNodes-1;
+   allConst = 1;
+   while( i-- ) {
+      theDims[i] = gParse.Nodes + this->SubNodes[i+1];
+      isConst[i] = ( theDims[i]->operation==CONST_OP );
+      if( isConst[i] )
+	 dimVals[i] = theDims[i]->value.data.lng;
+      else
+	 allConst = 0;
+   }
+
+   if( this->type==DOUBLE ) {
+      dsize = sizeof( double );
+   } else if( this->type==LONG ) {
+      dsize = sizeof( long );
+   } else if( this->type==BOOLEAN ) {
+      dsize = sizeof( char );
+   } else
+      dsize = 0;
+
+   Allocate_Ptrs( this );
+
+   if( !gParse.status ) {
+
+      if( allConst && theVar->value.naxis==nDims ) {
+
+	 /* Dereference completely using constant indices */
+
+	 elem = 0;
+	 i    = nDims;
+	 while( i-- ) {
+	    if( dimVals[i]<1 || dimVals[i]>theVar->value.naxes[i] ) break;
+	    elem = theVar->value.naxes[i]*elem + dimVals[i]-1;
+	 }
+	 if( i<0 ) {
+	    for( row=0; row<gParse.nRows; row++ ) {
+	       if( this->type==STRING )
+		 this->value.undef[row] = theVar->value.undef[row];
+	       else if( this->type==BITSTR ) 
+		 this->value.undef;  /* Dummy - BITSTRs do not have undefs */
+	       else 
+		 this->value.undef[row] = theVar->value.undef[elem];
+
+	       if( this->type==DOUBLE )
+		  this->value.data.dblptr[row] = 
+		     theVar->value.data.dblptr[elem];
+	       else if( this->type==LONG )
+		  this->value.data.lngptr[row] = 
+		     theVar->value.data.lngptr[elem];
+	       else if( this->type==BOOLEAN )
+		  this->value.data.logptr[row] = 
+		     theVar->value.data.logptr[elem];
+	       else {
+		 /* XXX Note, the below expression uses knowledge of
+                    the layout of the string format, namely (nelem+1)
+                    characters per string, followed by (nelem+1)
+                    "undef" values. */
+		  this->value.data.strptr[row][0] = 
+		     theVar->value.data.strptr[0][elem+row];
+		  this->value.data.strptr[row][1] = 0;  /* Null terminate */
+	       }
+	       elem += theVar->value.nelem;
+	    }
+	 } else {
+	    fferror("Index out of range");
+	    free( this->value.data.ptr );
+	 }
+	 
+      } else if( allConst && nDims==1 ) {
+	 
+	 /* Reduce dimensions by 1, using a constant index */
+	 
+	 if( dimVals[0] < 1 ||
+	     dimVals[0] > theVar->value.naxes[ theVar->value.naxis-1 ] ) {
+	    fferror("Index out of range");
+	    free( this->value.data.ptr );
+	 } else if ( this->type == BITSTR || this->type == STRING ) {
+	    elem = this->value.nelem * (dimVals[0]-1);
+	    for( row=0; row<gParse.nRows; row++ ) {
+	      if (this->value.undef) 
+		this->value.undef[row] = theVar->value.undef[row];
+	      memcpy( (char*)this->value.data.strptr[0]
+		      + row*sizeof(char)*(this->value.nelem+1),
+		      (char*)theVar->value.data.strptr[0] + elem*sizeof(char),
+		      this->value.nelem * sizeof(char) );
+	      /* Null terminate */
+	      this->value.data.strptr[row][this->value.nelem] = 0;
+	      elem += theVar->value.nelem+1;
+	    }	       
+	 } else {
+	    elem = this->value.nelem * (dimVals[0]-1);
+	    for( row=0; row<gParse.nRows; row++ ) {
+	       memcpy( this->value.undef + row*this->value.nelem,
+		       theVar->value.undef + elem,
+		       this->value.nelem * sizeof(char) );
+	       memcpy( (char*)this->value.data.ptr
+		       + row*dsize*this->value.nelem,
+		       (char*)theVar->value.data.ptr + elem*dsize,
+		       this->value.nelem * dsize );
+	       elem += theVar->value.nelem;
+	    }	       
+	 }
+      
+      } else if( theVar->value.naxis==nDims ) {
+
+	 /* Dereference completely using an expression for the indices */
+
+	 for( row=0; row<gParse.nRows; row++ ) {
+
+	    for( i=0; i<nDims; i++ ) {
+	       if( !isConst[i] ) {
+		  if( theDims[i]->value.undef[row] ) {
+		     fferror("Null encountered as vector index");
+		     free( this->value.data.ptr );
+		     break;
+		  } else
+		     dimVals[i] = theDims[i]->value.data.lngptr[row];
+	       }
+	    }
+	    if( gParse.status ) break;
+
+	    elem = 0;
+	    i    = nDims;
+	    while( i-- ) {
+	       if( dimVals[i]<1 || dimVals[i]>theVar->value.naxes[i] ) break;
+	       elem = theVar->value.naxes[i]*elem + dimVals[i]-1;
+	    }
+	    if( i<0 ) {
+	       elem += row*theVar->value.nelem;
+
+	       if( this->type==STRING )
+		 this->value.undef[row] = theVar->value.undef[row];
+	       else if( this->type==BITSTR ) 
+		 this->value.undef;  /* Dummy - BITSTRs do not have undefs */
+	       else 
+		 this->value.undef[row] = theVar->value.undef[elem];
+
+	       if( this->type==DOUBLE )
+		  this->value.data.dblptr[row] = 
+		     theVar->value.data.dblptr[elem];
+	       else if( this->type==LONG )
+		  this->value.data.lngptr[row] = 
+		     theVar->value.data.lngptr[elem];
+	       else if( this->type==BOOLEAN )
+		  this->value.data.logptr[row] = 
+		     theVar->value.data.logptr[elem];
+	       else {
+		 /* XXX Note, the below expression uses knowledge of
+                    the layout of the string format, namely (nelem+1)
+                    characters per string, followed by (nelem+1)
+                    "undef" values. */
+		  this->value.data.strptr[row][0] = 
+		     theVar->value.data.strptr[0][elem+row];
+		  this->value.data.strptr[row][1] = 0;  /* Null terminate */
+	       }
+	    } else {
+	       fferror("Index out of range");
+	       free( this->value.data.ptr );
+	    }
+	 }
+
+      } else {
+
+	 /* Reduce dimensions by 1, using a nonconstant expression */
+
+	 for( row=0; row<gParse.nRows; row++ ) {
+
+	    /* Index cannot be a constant */
+
+	    if( theDims[0]->value.undef[row] ) {
+	       fferror("Null encountered as vector index");
+	       free( this->value.data.ptr );
+	       break;
+	    } else
+	       dimVals[0] = theDims[0]->value.data.lngptr[row];
+
+	    if( dimVals[0] < 1 ||
+		dimVals[0] > theVar->value.naxes[ theVar->value.naxis-1 ] ) {
+	       fferror("Index out of range");
+	       free( this->value.data.ptr );
+	    } else if ( this->type == BITSTR || this->type == STRING ) {
+	      elem = this->value.nelem * (dimVals[0]-1);
+	      elem += row*(theVar->value.nelem+1);
+	      if (this->value.undef) 
+		this->value.undef[row] = theVar->value.undef[row];
+	      memcpy( (char*)this->value.data.strptr[0]
+		      + row*sizeof(char)*(this->value.nelem+1),
+		      (char*)theVar->value.data.strptr[0] + elem*sizeof(char),
+		      this->value.nelem * sizeof(char) );
+	      /* Null terminate */
+	      this->value.data.strptr[row][this->value.nelem] = 0;
+	    } else {
+	       elem  = this->value.nelem * (dimVals[0]-1);
+	       elem += row*theVar->value.nelem;
+	       memcpy( this->value.undef + row*this->value.nelem,
+		       theVar->value.undef + elem,
+		       this->value.nelem * sizeof(char) );
+	       memcpy( (char*)this->value.data.ptr
+		       + row*dsize*this->value.nelem,
+		       (char*)theVar->value.data.ptr + elem*dsize,
+		       this->value.nelem * dsize );
+	    }
+	 }
+      }
+   }
+
+   if( theVar->operation>0 ) {
+     if (theVar->type == STRING || theVar->type == BITSTR) 
+       free(theVar->value.data.strptr[0] );
+     else 
+       free( theVar->value.data.ptr );
+   }
+   for( i=0; i<nDims; i++ )
+      if( theDims[i]->operation>0 ) {
+	 free( theDims[i]->value.data.ptr );
+      }
+}
+
+static void Do_GTI( Node *this )
+{
+   Node *theExpr, *theTimes;
+   double *start, *stop, *times;
+   long elem, nGTI, gti;
+   int ordered;
+
+   theTimes = gParse.Nodes + this->SubNodes[0];
+   theExpr  = gParse.Nodes + this->SubNodes[1];
+
+   nGTI    = theTimes->value.nelem;
+   start   = theTimes->value.data.dblptr;
+   stop    = theTimes->value.data.dblptr + nGTI;
+   ordered = theTimes->type;
+
+   if( theExpr->operation==CONST_OP ) {
+
+      this->value.data.log = 
+	 (Search_GTI( theExpr->value.data.dbl, nGTI, start, stop, ordered )>=0);
+      this->operation      = CONST_OP;
+
+   } else {
+
+      Allocate_Ptrs( this );
+
+      times = theExpr->value.data.dblptr;
+      if( !gParse.status ) {
+
+	 elem = gParse.nRows * this->value.nelem;
+	 if( nGTI ) {
+	    gti = -1;
+	    while( elem-- ) {
+	       if( (this->value.undef[elem] = theExpr->value.undef[elem]) )
+		  continue;
+
+            /*  Before searching entire GTI, check the GTI found last time  */
+	       if( gti<0 || times[elem]<start[gti] || times[elem]>stop[gti] ) {
+		  gti = Search_GTI( times[elem], nGTI, start, stop, ordered );
+	       }
+	       this->value.data.logptr[elem] = ( gti>=0 );
+	    }
+	 } else
+	    while( elem-- ) {
+	       this->value.data.logptr[elem] = 0;
+	       this->value.undef[elem]       = 0;
+	    }
+      }
+   }
+
+   if( theExpr->operation>0 )
+      free( theExpr->value.data.ptr );
+}
+
+static long Search_GTI( double evtTime, long nGTI, double *start,
+			double *stop, int ordered )
+{
+   long gti, step;
+                             
+   if( ordered && nGTI>15 ) { /*  If time-ordered and lots of GTIs,   */
+                              /*  use "FAST" Binary search algorithm  */
+      if( evtTime>=start[0] && evtTime<=stop[nGTI-1] ) {
+	 gti = step = (nGTI >> 1);
+	 while(1) {
+	    if( step>1L ) step >>= 1;
+	    
+	    if( evtTime>stop[gti] ) {
+	       if( evtTime>=start[gti+1] )
+		  gti += step;
+	       else {
+		  gti = -1L;
+		  break;
+	       }
+	    } else if( evtTime<start[gti] ) {
+	       if( evtTime<=stop[gti-1] )
+		  gti -= step;
+	       else {
+		  gti = -1L;
+		  break;
+	       }
+	    } else {
+	       break;
+	    }
+	 }
+      } else
+	 gti = -1L;
+      
+   } else { /*  Use "SLOW" linear search  */
+      gti = nGTI;
+      while( gti-- )
+	 if( evtTime>=start[gti] && evtTime<=stop[gti] )
+	    break;
+   }
+   return( gti );
+}
+
+static void Do_REG( Node *this )
+{
+   Node *theRegion, *theX, *theY;
+   double Xval=0.0, Yval=0.0;
+   char   Xnull=0, Ynull=0;
+   int    Xvector, Yvector;
+   long   nelem, elem, rows;
+
+   theRegion = gParse.Nodes + this->SubNodes[0];
+   theX      = gParse.Nodes + this->SubNodes[1];
+   theY      = gParse.Nodes + this->SubNodes[2];
+
+   Xvector = ( theX->operation!=CONST_OP );
+   if( Xvector )
+      Xvector = theX->value.nelem;
+   else {
+      Xval  = theX->value.data.dbl;
+   }
+
+   Yvector = ( theY->operation!=CONST_OP );
+   if( Yvector )
+      Yvector = theY->value.nelem;
+   else {
+      Yval  = theY->value.data.dbl;
+   } 
+
+   if( !Xvector && !Yvector ) {
+
+      this->value.data.log =
+	 ( fits_in_region( Xval, Yval, (SAORegion *)theRegion->value.data.ptr )
+	   != 0 );
+      this->operation      = CONST_OP;
+
+   } else {
+
+      Allocate_Ptrs( this );
+
+      if( !gParse.status ) {
+
+	 rows  = gParse.nRows;
+	 nelem = this->value.nelem;
+	 elem  = rows*nelem;
+
+	 while( rows-- ) {
+	    while( nelem-- ) {
+	       elem--;
+
+	       if( Xvector>1 ) {
+		  Xval  = theX->value.data.dblptr[elem];
+		  Xnull = theX->value.undef[elem];
+	       } else if( Xvector ) {
+		  Xval  = theX->value.data.dblptr[rows];
+		  Xnull = theX->value.undef[rows];
+	       }
+
+	       if( Yvector>1 ) {
+		  Yval  = theY->value.data.dblptr[elem];
+		  Ynull = theY->value.undef[elem];
+	       } else if( Yvector ) {
+		  Yval  = theY->value.data.dblptr[rows];
+		  Ynull = theY->value.undef[rows];
+	       }
+
+	       this->value.undef[elem] = ( Xnull || Ynull );
+	       if( this->value.undef[elem] )
+		  continue;
+
+	       this->value.data.logptr[elem] = 
+		  ( fits_in_region( Xval, Yval,
+				    (SAORegion *)theRegion->value.data.ptr )
+		    != 0 );
+	    }
+	    nelem = this->value.nelem;
+	 }
+      }
+   }
+
+   if( theX->operation>0 )
+      free( theX->value.data.ptr );
+   if( theY->operation>0 )
+      free( theY->value.data.ptr );
+}
+
+static void Do_Vector( Node *this )
+{
+   Node *that;
+   long row, elem, idx, jdx, offset=0;
+   int node;
+
+   Allocate_Ptrs( this );
+
+   if( !gParse.status ) {
+
+      for( node=0; node<this->nSubNodes; node++ ) {
+
+	 that = gParse.Nodes + this->SubNodes[node];
+
+	 if( that->operation == CONST_OP ) {
+
+	    idx = gParse.nRows*this->value.nelem + offset;
+	    while( (idx-=this->value.nelem)>=0 ) {
+	       
+	       this->value.undef[idx] = 0;
+
+	       switch( this->type ) {
+	       case BOOLEAN:
+		  this->value.data.logptr[idx] = that->value.data.log;
+		  break;
+	       case LONG:
+		  this->value.data.lngptr[idx] = that->value.data.lng;
+		  break;
+	       case DOUBLE:
+		  this->value.data.dblptr[idx] = that->value.data.dbl;
+		  break;
+	       }
+	    }
+	    
+	 } else {
+	       
+	    row  = gParse.nRows;
+	    idx  = row * that->value.nelem;
+	    while( row-- ) {
+	       elem = that->value.nelem;
+	       jdx = row*this->value.nelem + offset;
+	       while( elem-- ) {
+		  this->value.undef[jdx+elem] =
+		     that->value.undef[--idx];
+
+		  switch( this->type ) {
+		  case BOOLEAN:
+		     this->value.data.logptr[jdx+elem] =
+			that->value.data.logptr[idx];
+		     break;
+		  case LONG:
+		     this->value.data.lngptr[jdx+elem] =
+			that->value.data.lngptr[idx];
+		     break;
+		  case DOUBLE:
+		     this->value.data.dblptr[jdx+elem] =
+			that->value.data.dblptr[idx];
+		     break;
+		  }
+	       }
+	    }
+	 }
+	 offset += that->value.nelem;
+      }
+
+   }
+
+   for( node=0; node < this->nSubNodes; node++ )
+     if( OPER(this->SubNodes[node])>0 )
+       free( gParse.Nodes[this->SubNodes[node]].value.data.ptr );
+}
+
+/*****************************************************************************/
+/*  Utility routines which perform the calculations on bits and SAO regions  */
+/*****************************************************************************/
+
+static char bitlgte(char *bits1, int oper, char *bits2)
+{
+ int val1, val2, nextbit;
+ char result;
+ int i, l1, l2, length, ldiff;
+ char stream[256];
+ char chr1, chr2;
+
+ l1 = strlen(bits1);
+ l2 = strlen(bits2);
+ if (l1 < l2)
+   {
+    length = l2;
+    ldiff = l2 - l1;
+    i=0;
+    while( ldiff-- ) stream[i++] = '0';
+    while( l1--    ) stream[i++] = *(bits1++);
+    stream[i] = '\0';
+    bits1 = stream;
+   }
+ else if (l2 < l1)
+   {
+    length = l1;
+    ldiff = l1 - l2;
+    i=0;
+    while( ldiff-- ) stream[i++] = '0';
+    while( l2--    ) stream[i++] = *(bits2++);
+    stream[i] = '\0';
+    bits2 = stream;
+   }
+ else
+    length = l1;
+
+ val1 = val2 = 0;
+ nextbit = 1;
+
+ while( length-- )
+    {
+     chr1 = bits1[length];
+     chr2 = bits2[length];
+     if ((chr1 != 'x')&&(chr1 != 'X')&&(chr2 != 'x')&&(chr2 != 'X'))
+       {
+        if (chr1 == '1') val1 += nextbit;
+        if (chr2 == '1') val2 += nextbit;
+        nextbit *= 2;
+       }
+    }
+ result = 0;
+ switch (oper)
+       {
+        case LT:
+             if (val1 < val2) result = 1;
+             break;
+        case LTE:
+             if (val1 <= val2) result = 1;
+             break;
+        case GT:
+             if (val1 > val2) result = 1;
+             break;
+        case GTE:
+             if (val1 >= val2) result = 1;
+             break;
+       }
+ return (result);
+}
+
+static void bitand(char *result,char *bitstrm1,char *bitstrm2)
+{
+ int i, l1, l2, ldiff;
+ char stream[256];
+ char chr1, chr2;
+
+ l1 = strlen(bitstrm1);
+ l2 = strlen(bitstrm2);
+ if (l1 < l2)
+   {
+    ldiff = l2 - l1;
+    i=0;
+    while( ldiff-- ) stream[i++] = '0';
+    while( l1--    ) stream[i++] = *(bitstrm1++);
+    stream[i] = '\0';
+    bitstrm1 = stream;
+   }
+ else if (l2 < l1)
+   {
+    ldiff = l1 - l2;
+    i=0;
+    while( ldiff-- ) stream[i++] = '0';
+    while( l2--    ) stream[i++] = *(bitstrm2++);
+    stream[i] = '\0';
+    bitstrm2 = stream;
+   }
+ while ( (chr1 = *(bitstrm1++)) ) 
+    {
+       chr2 = *(bitstrm2++);
+       if ((chr1 == 'x') || (chr2 == 'x'))
+          *result = 'x';
+       else if ((chr1 == '1') && (chr2 == '1'))
+          *result = '1';
+       else
+          *result = '0';
+       result++;
+    }
+ *result = '\0';
+}
+
+static void bitor(char *result,char *bitstrm1,char *bitstrm2)
+{
+ int i, l1, l2, ldiff;
+ char stream[256];
+ char chr1, chr2;
+
+ l1 = strlen(bitstrm1);
+ l2 = strlen(bitstrm2);
+ if (l1 < l2)
+   {
+    ldiff = l2 - l1;
+    i=0;
+    while( ldiff-- ) stream[i++] = '0';
+    while( l1--    ) stream[i++] = *(bitstrm1++);
+    stream[i] = '\0';
+    bitstrm1 = stream;
+   }
+ else if (l2 < l1)
+   {
+    ldiff = l1 - l2;
+    i=0;
+    while( ldiff-- ) stream[i++] = '0';
+    while( l2--    ) stream[i++] = *(bitstrm2++);
+    stream[i] = '\0';
+    bitstrm2 = stream;
+   }
+ while ( (chr1 = *(bitstrm1++)) ) 
+    {
+       chr2 = *(bitstrm2++);
+       if ((chr1 == '1') || (chr2 == '1'))
+          *result = '1';
+       else if ((chr1 == '0') || (chr2 == '0'))
+          *result = '0';
+       else
+          *result = 'x';
+       result++;
+    }
+ *result = '\0';
+}
+
+static void bitnot(char *result,char *bits)
+{
+   int length;
+   char chr;
+
+   length = strlen(bits);
+   while( length-- ) {
+      chr = *(bits++);
+      *(result++) = ( chr=='1' ? '0' : ( chr=='0' ? '1' : chr ) );
+   }
+   *result = '\0';
+}
+
+static char bitcmp(char *bitstrm1, char *bitstrm2)
+{
+ int i, l1, l2, ldiff;
+ char stream[256];
+ char chr1, chr2;
+
+ l1 = strlen(bitstrm1);
+ l2 = strlen(bitstrm2);
+ if (l1 < l2)
+   {
+    ldiff = l2 - l1;
+    i=0;
+    while( ldiff-- ) stream[i++] = '0';
+    while( l1--    ) stream[i++] = *(bitstrm1++);
+    stream[i] = '\0';
+    bitstrm1 = stream;
+   }
+ else if (l2 < l1)
+   {
+    ldiff = l1 - l2;
+    i=0;
+    while( ldiff-- ) stream[i++] = '0';
+    while( l2--    ) stream[i++] = *(bitstrm2++);
+    stream[i] = '\0';
+    bitstrm2 = stream;
+   }
+ while( (chr1 = *(bitstrm1++)) )
+    {
+       chr2 = *(bitstrm2++);
+       if ( ((chr1 == '0') && (chr2 == '1'))
+	    || ((chr1 == '1') && (chr2 == '0')) )
+	  return( 0 );
+    }
+ return( 1 );
+}
+
+static char bnear(double x, double y, double tolerance)
+{
+ if (fabs(x - y) < tolerance)
+   return ( 1 );
+ else
+   return ( 0 );
+}
+
+static char saobox(double xcen, double ycen, double xwid, double ywid,
+		   double rot,  double xcol, double ycol)
+{
+ double x,y,xprime,yprime,xmin,xmax,ymin,ymax,theta;
+
+ theta = (rot / 180.0) * myPI;
+ xprime = xcol - xcen;
+ yprime = ycol - ycen;
+ x =  xprime * cos(theta) + yprime * sin(theta);
+ y = -xprime * sin(theta) + yprime * cos(theta);
+ xmin = - 0.5 * xwid; xmax = 0.5 * xwid;
+ ymin = - 0.5 * ywid; ymax = 0.5 * ywid;
+ if ((x >= xmin) && (x <= xmax) && (y >= ymin) && (y <= ymax))
+   return ( 1 );
+ else
+   return ( 0 );
+}
+
+static char circle(double xcen, double ycen, double rad,
+		   double xcol, double ycol)
+{
+ double r2,dx,dy,dlen;
+
+ dx = xcol - xcen;
+ dy = ycol - ycen;
+ dx *= dx; dy *= dy;
+ dlen = dx + dy;
+ r2 = rad * rad;
+ if (dlen <= r2)
+   return ( 1 );
+ else
+   return ( 0 );
+}
+
+static char ellipse(double xcen, double ycen, double xrad, double yrad,
+		    double rot, double xcol, double ycol)
+{
+ double x,y,xprime,yprime,dx,dy,dlen,theta;
+
+ theta = (rot / 180.0) * myPI;
+ xprime = xcol - xcen;
+ yprime = ycol - ycen;
+ x =  xprime * cos(theta) + yprime * sin(theta);
+ y = -xprime * sin(theta) + yprime * cos(theta);
+ dx = x / xrad; dy = y / yrad;
+ dx *= dx; dy *= dy;
+ dlen = dx + dy;
+ if (dlen <= 1.0)
+   return ( 1 );
+ else
+   return ( 0 );
+}
+
+/*
+ * Extract substring
+ */
+int cstrmid(char *dest_str, int dest_len,
+	    char *src_str,  int src_len,
+	    int pos)
+{
+  /* char fill_char = ' '; */
+  char fill_char = '\0';
+  if (src_len == 0) { src_len = strlen(src_str); } /* .. if constant */
+
+  /* Fill destination with blanks */
+  if (pos < 0) { 
+    fferror("STRMID(S,P,N) P must be 0 or greater");
+    return -1;
+  }
+  if (pos > src_len || pos == 0) {
+    /* pos==0: blank string requested */
+    memset(dest_str, fill_char, dest_len);
+  } else if (pos+dest_len > src_len) {
+    /* Copy a subset */
+    int nsub = src_len-pos+1;
+    int npad = dest_len - nsub;
+    memcpy(dest_str, src_str+pos-1, nsub);
+    /* Fill remaining string with blanks */
+    memset(dest_str+nsub, fill_char, npad);
+  } else {
+    /* Full string copy */
+    memcpy(dest_str, src_str+pos-1, dest_len);
+  }
+  dest_str[dest_len] = '\0'; /* Null-terminate */
+
+  return 0;
+}
+
+
+static void fferror(char *s)
+{
+    char msg[80];
+
+    if( !gParse.status ) gParse.status = PARSE_SYNTAX_ERR;
+
+    strncpy(msg, s, 80);
+    msg[79] = '\0';
+    ffpmsg(msg);
+}
diff --git a/src/plugins/cfitsio/f77_wrap.h b/src/plugins/cfitsio/f77_wrap.h
new file mode 100644
index 0000000..d512855
--- /dev/null
+++ b/src/plugins/cfitsio/f77_wrap.h
@@ -0,0 +1,288 @@
+#define UNSIGNED_BYTE
+
+#include "cfortran.h"
+
+/************************************************************************
+   Some platforms creates longs as 8-byte integers.  On other machines, ints
+   and longs are both 4-bytes, so both are compatible with Fortrans
+   default integer which is 4-bytes.  To support 8-byte longs, we must redefine
+   LONGs and convert them to 8-bytes when going to C, and restore them
+   to 4-bytes when returning to Fortran.  Ugh!!!
+*************************************************************************/
+
+#if defined(DECFortran) || (defined(__alpha) && defined(g77Fortran)) \
+    || (defined(mipsFortran)  && _MIPS_SZLONG==64) \
+    || (defined(IBMR2Fortran) && defined(__64BIT__)) \
+    ||  defined(__ia64__)  \
+    ||  defined (__sparcv9) || (defined(__sparc__) && defined(__arch64__)) \
+    ||  defined (__x86_64__) \
+    ||  defined (_SX) \
+    ||  defined (__powerpc64__)\
+    ||  defined (__s390x__)
+
+#define   LONG8BYTES_INT4BYTES
+
+#undef LONGV_cfSTR
+#undef PLONG_cfSTR
+#undef LONGVVVVVVV_cfTYPE
+#undef PLONG_cfTYPE
+#undef LONGV_cfT
+#undef PLONG_cfT
+
+#define    LONGV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,LONGV,A,B,C,D,E)
+#define    PLONG_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,PLONG,A,B,C,D,E)
+#define    LONGVVVVVVV_cfTYPE    int
+#define    PLONG_cfTYPE          int
+#define    LONGV_cfQ(B)          long *B, _(B,N);
+#define    PLONG_cfQ(B)          long B;
+#define    LONGV_cfT(M,I,A,B,D)  ( (_(B,N) = * _3(M,_LONGV_A,I)), \
+				    B = F2Clongv(_(B,N),A) )
+#define    PLONG_cfT(M,I,A,B,D)  ((B=*A),&B)
+#define    LONGV_cfR(A,B,D)      C2Flongv(_(B,N),A,B);
+#define    PLONG_cfR(A,B,D)      *A=B;
+#define    LONGV_cfH(S,U,B)
+#define    PLONG_cfH(S,U,B)
+
+static long *F2Clongv(long size, int *A)
+{
+  long i;
+  long *B;
+
+  B=(long *)malloc( size*sizeof(long) );
+  for(i=0;i<size;i++) B[i]=A[i];
+  return(B);
+}
+
+static void C2Flongv(long size, int *A, long *B)
+{
+  long i;
+
+  for(i=0;i<size;i++) A[i]=B[i];
+  free(B);
+}
+
+#endif
+
+/************************************************************************
+   Modify cfortran.h's handling of strings.  C interprets a "char **"
+   parameter as an array of pointers to the strings (or as a handle),
+   not as a pointer to a block of contiguous strings.  Also set a
+   a minimum length for string allocations, to minimize risk of
+   overflow.
+*************************************************************************/
+
+extern unsigned long gMinStrLen;
+
+#undef  STRINGV_cfQ
+#undef  STRINGV_cfR
+#undef  TTSTR
+#undef  TTTTSTRV
+#undef  RRRRPSTRV
+
+#undef  PPSTRING_cfT
+
+#ifdef vmsFortran
+#define       PPSTRING_cfT(M,I,A,B,D)     (unsigned char*)A->dsc$a_pointer
+
+/*  We want single strings to be equivalent to string vectors with  */
+/*  a single element, so ignore the number of elements info in the  */
+/*  vector structure, and rely on the NUM_ELEM definitions.         */
+
+#undef  STRINGV_cfT
+#define STRINGV_cfT(M,I,A,B,D)  TTTTSTRV(A->dsc$a_pointer, B, \
+                                         A->dsc$w_length, \
+                                         num_elem(A->dsc$a_pointer, \
+                                                  A->dsc$w_length, \
+                                                  _3(M,_STRV_A,I) ) )
+#else
+#ifdef CRAYFortran
+#define       PPSTRING_cfT(M,I,A,B,D)     (unsigned char*)_fcdtocp(A)
+#else
+#define       PPSTRING_cfT(M,I,A,B,D)     (unsigned char*)A
+#endif
+#endif
+
+#define _cfMAX(A,B)  ( (A>B) ? A : B )
+#define  STRINGV_cfQ(B)      char **B; unsigned int _(B,N), _(B,M);
+#define  STRINGV_cfR(A,B,D)  free(B[0]); free(B);
+#define  TTSTR(    A,B,D)  \
+            ((B=(char*)malloc(_cfMAX(D,gMinStrLen)+1))[D]='\0',memcpy(B,A,D), \
+               kill_trailing(B,' '))
+#define  TTTTSTRV( A,B,D,E)  ( \
+            _(B,N)=_cfMAX(E,1), \
+            _(B,M)=_cfMAX(D,gMinStrLen)+1, \
+            B=(char**)malloc(_(B,N)*sizeof(char*)), \
+            B[0]=(char*)malloc(_(B,N)*_(B,M)), \
+            vindex(B,_(B,M),_(B,N),f2cstrv2(A,B[0],D,_(B,M),_(B,N))) \
+            )
+#define  RRRRPSTRV(A,B,D)    \
+            c2fstrv2(B[0],A,_(B,M),D,_(B,N)), \
+            free(B[0]), \
+            free(B);
+
+static char **vindex(char **B, int elem_len, int nelem, char *B0)
+{
+   int i;
+   if( nelem )
+      for( i=0;i<nelem;i++ ) B[i] = B0+i*elem_len;
+   return B;
+}
+
+static char *c2fstrv2(char* cstr, char *fstr, int celem_len, int felem_len,
+               int nelem)
+{
+   int i,j;
+
+   if( nelem )
+      for (i=0; i<nelem; i++) {
+	 for (j=0; j<felem_len && *cstr; j++) *fstr++ = *cstr++;
+	 cstr += celem_len-j;
+	 for (; j<felem_len; j++) *fstr++ = ' ';
+      }
+   return( fstr-felem_len*nelem );
+}
+
+static char *f2cstrv2(char *fstr, char* cstr, int felem_len, int celem_len,
+               int nelem)
+{
+   int i,j;
+
+   if( nelem )
+      for (i=0; i<nelem; i++, cstr+=(celem_len-felem_len)) {
+	 for (j=0; j<felem_len; j++) *cstr++ = *fstr++;
+	 *cstr='\0';
+	 kill_trailingn( cstr-felem_len, ' ', cstr );
+      }
+   return( cstr-celem_len*nelem );
+}
+
+/************************************************************************
+   The following definitions redefine the BYTE data type to be
+   interpretted as a character*1 string instead of an integer*1 which
+   is not supported by all compilers.
+*************************************************************************/
+
+#undef   BYTE_cfT
+#undef   BYTEV_cfT
+#undef   BYTE_cfINT
+#undef   BYTEV_cfINT
+#undef   BYTE_cfSTR
+#undef   BYTEV_cfSTR
+
+#define   BYTE_cfINT(N,A,B,X,Y,Z)      _(CFARGS,N)(A,BYTE,B,X,Y,Z,0)
+#define   BYTEV_cfINT(N,A,B,X,Y,Z)     _(CFARGS,N)(A,BYTEV,B,X,Y,Z,0)
+#define   BYTE_cfSTR(N,T,A,B,C,D,E)    _(CFARGS,N)(T,BYTE,A,B,C,D,E)
+#define   BYTEV_cfSTR(N,T,A,B,C,D,E)   _(CFARGS,N)(T,BYTEV,A,B,C,D,E)
+#define   BYTE_cfSEP(T,B)              INT_cfSEP(T,B)
+#define   BYTEV_cfSEP(T,B)             INT_cfSEP(T,B)
+#define   BYTE_cfH(S,U,B)              STRING_cfH(S,U,B)
+#define   BYTEV_cfH(S,U,B)             STRING_cfH(S,U,B)
+#define   BYTE_cfQ(B)
+#define   BYTEV_cfQ(B)
+#define   BYTE_cfR(A,B,D)
+#define   BYTEV_cfR(A,B,D)
+
+#ifdef vmsFortran
+#define   BYTE_cfN(T,A)           fstring * A
+#define   BYTEV_cfN(T,A)          fstringvector * A
+#define   BYTE_cfT(M,I,A,B,D)     (INTEGER_BYTE)((A->dsc$a_pointer)[0])
+#define   BYTEV_cfT(M,I,A,B,D)    (INTEGER_BYTE*)A->dsc$a_pointer
+#else
+#ifdef CRAYFortran
+#define   BYTE_cfN(T,A)           _fcd A
+#define   BYTEV_cfN(T,A)          _fcd A
+#define   BYTE_cfT(M,I,A,B,D)     (INTEGER_BYTE)((_fcdtocp(A))[0])
+#define   BYTEV_cfT(M,I,A,B,D)    (INTEGER_BYTE*)_fcdtocp(A)
+#else
+#define   BYTE_cfN(T,A)           INTEGER_BYTE * A
+#define   BYTEV_cfN(T,A)          INTEGER_BYTE * A
+#define   BYTE_cfT(M,I,A,B,D)     A[0]
+#define   BYTEV_cfT(M,I,A,B,D)    A
+#endif
+#endif
+
+/************************************************************************
+   The following definitions and functions handle conversions between
+   C and Fortran arrays of LOGICALS.  Individually, LOGICALS are
+   treated as int's but as char's when in an array.  cfortran defines
+   (F2C/C2F)LOGICALV but never uses them, so these routines also
+   handle TRUE/FALSE conversions.
+*************************************************************************/
+
+#undef  LOGICALV_cfSTR
+#undef  LOGICALV_cfT
+#define LOGICALV_cfSTR(N,T,A,B,C,D,E) _(CFARGS,N)(T,LOGICALV,A,B,C,D,E)
+#define LOGICALV_cfQ(B)               char *B; unsigned int _(B,N);
+#define LOGICALV_cfT(M,I,A,B,D)       (_(B,N)= * _3(M,_LOGV_A,I), \
+                                            B=F2CcopyLogVect(_(B,N),A))
+#define LOGICALV_cfR(A,B,D)           C2FcopyLogVect(_(B,N),A,B);
+#define LOGICALV_cfH(S,U,B)
+
+static char *F2CcopyLogVect(long size, int *A)
+{
+   long i;
+   char *B;
+
+   B=(char *)malloc(size*sizeof(char));
+   for( i=0; i<size; i++ ) B[i]=F2CLOGICAL(A[i]);
+   return(B);
+}
+
+static void C2FcopyLogVect(long size, int *A, char *B)
+{
+   long i;
+
+   for( i=0; i<size; i++ ) A[i]=C2FLOGICAL(B[i]);
+   free(B);
+}
+
+/*------------------  Fortran File Handling  ----------------------*/
+/*  Fortran uses unit numbers, whereas C uses file pointers, so    */
+/*  a global array of file pointers is setup in which Fortran's    */
+/*  unit number serves as the index.  Two FITSIO routines are      */
+/*  the integer unit number and the fitsfile file pointer.         */
+/*-----------------------------------------------------------------*/
+
+#define MAXFITSFILES 200             /*  Array of file pointers indexed  */
+extern fitsfile *gFitsFiles[];       /*    by Fortran unit numbers       */
+
+#define  FITSUNIT_cfINT(N,A,B,X,Y,Z)   INT_cfINT(N,A,B,X,Y,Z)
+#define  FITSUNIT_cfSTR(N,T,A,B,C,D,E) INT_cfSTR(N,T,A,B,C,D,E)
+#define  FITSUNIT_cfT(M,I,A,B,D)       gFitsFiles[*A]
+#define  FITSUNITVVVVVVV_cfTYPE        int
+#define PFITSUNIT_cfINT(N,A,B,X,Y,Z)   PINT_cfINT(N,A,B,X,Y,Z)
+#define PFITSUNIT_cfSTR(N,T,A,B,C,D,E) PINT_cfSTR(N,T,A,B,C,D,E)
+#define PFITSUNIT_cfT(M,I,A,B,D)       (gFitsFiles + *A)
+#define PFITSUNIT_cfTYPE               int
+
+
+/*---------------------- Make C++ Happy -----------------------------*/
+/* Redefine FCALLSCFUNn so that they create prototypes of themselves */
+/*   and change TTTTSTR to use (char *)0 instead of NULL             */
+/*-------------------------------------------------------------------*/
+
+#undef FCALLSCFUN0
+#undef FCALLSCFUN14
+#undef TTTTSTR
+
+#define TTTTSTR(A,B,D)   ( !(D<4||A[0]||A[1]||A[2]||A[3]) ) ? ((char*)0) :     \
+                             memchr(A,'\0',D) ? A : TTSTR(A,B,D)
+
+#define FCALLSCFUN0(T0,CN,UN,LN) \
+  CFextern _(T0,_cfFZ)(UN,LN) void ABSOFT_cf2(T0)); \
+  CFextern _(T0,_cfFZ)(UN,LN) void ABSOFT_cf2(T0))  \
+  {_Icf(2,UU,T0,A0,0); _Icf(0,L,T0,0,0) CN(); _Icf(0,K,T0,0,0) _(T0,_cfI)}
+
+#define FCALLSCFUN14(T0,CN,UN,LN,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)    \
+  CFextern _(T0,_cfF)(UN,LN)                                                   \
+  CFARGT14(NCF,DCF,ABSOFT_cf2(T0),T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)); \
+  CFextern _(T0,_cfF)(UN,LN)                                                   \
+  CFARGT14(NCF,DCF,ABSOFT_cf2(T0),T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE))  \
+  {                 CFARGT14S(QCF,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)   \
+  _Icf(2,UU,T0,A0,0); _Icf(0,L,T0,0,0)      CN(  TCF(LN,T1,1,0) TCF(LN,T2,2,1) \
+    TCF(LN,T3,3,1) TCF(LN,T4,4,1) TCF(LN,T5,5,1) TCF(LN,T6,6,1) TCF(LN,T7,7,1) \
+    TCF(LN,T8,8,1) TCF(LN,T9,9,1) TCF(LN,TA,10,1) TCF(LN,TB,11,1) TCF(LN,TC,12,1) \
+    TCF(LN,TD,13,1) TCF(LN,TE,14,1) );                          _Icf(0,K,T0,0,0) \
+    CFARGT14S(RCF,T1,T2,T3,T4,T5,T6,T7,T8,T9,TA,TB,TC,TD,TE)        _(T0,_cfI) \
+  }
+
diff --git a/src/plugins/cfitsio/f77_wrap1.c b/src/plugins/cfitsio/f77_wrap1.c
new file mode 100644
index 0000000..db7001b
--- /dev/null
+++ b/src/plugins/cfitsio/f77_wrap1.c
@@ -0,0 +1,345 @@
+/************************************************************************
+
+     f77_wrap1.c and f77_wrap2.c have now been split into 4 files to
+     prevent compile-time memory errors (from expansion of compiler commands).
+     f77_wrap1.c was split into f77_wrap1.c and f77_wrap3.c, and
+     f77_wrap2.c was split into f77_wrap2.c and f77_wrap4.c:
+       
+       f77_wrap1.c contains routines operating on whole files and some 
+       utility routines.
+     
+       f77_wrap2.c contains routines operating on primary array, image, 
+       or column elements.
+
+       f77_wrap3.c contains routines operating on headers & keywords.
+
+       f77_wrap4.c contains miscellaneous routines.
+
+     Peter's original comments:
+
+     Together, f77_wrap1.c and f77_wrap2.c contain C wrappers for all
+     the CFITSIO routines prototyped in fitsio.h, except for the
+     generic datatype routines and features not supported in fortran
+     (eg, unsigned integers), a few routines prototyped in fitsio2.h,
+     which only a handful of FTOOLS use, plus a few obsolete FITSIO
+     routines not present in CFITSIO.  This file allows Fortran code
+     to use the CFITSIO library instead of the FITSIO library without
+     modification.  It also gives access to new routines not present
+     in FITSIO.  Fortran FTOOLS must continue using the old routine
+     names from FITSIO (ie, ftxxxx), but most of the C-wrappers simply
+     redirect those calls to the corresponding CFITSIO routines (ie,
+     ffxxxx), with appropriate parameter massaging where necessary.
+     The main exception are read/write routines ending in j (ie, long
+     data) which get redirected to C routines ending in k (ie, int
+     data). This is more consistent with the default integer type in
+     Fortran. f77_wrap1.c primarily holds routines operating on whole
+     files and extension headers.  f77_wrap2.c handle routines which
+     read and write the data portion, plus miscellaneous extra routines.
+     
+        File created by Peter Wilson (HSTX), Oct-Dec. 1997
+************************************************************************/
+
+#include "fitsio2.h"
+#include "f77_wrap.h"
+
+unsigned long gMinStrLen=80L;
+fitsfile *gFitsFiles[MAXFITSFILES]={0};
+
+/*----------------  Fortran Unit Number Allocation -------------*/
+
+void Cffgiou( int *unit, int *status );
+void Cffgiou( int *unit, int *status )
+{
+   int i;
+
+   if( *status>0 ) return;
+   for( i=50;i<MAXFITSFILES;i++ ) /* Using a unit=0 sounds bad, so start at 1 */
+      if( gFitsFiles[i]==NULL ) break;
+   if( i==MAXFITSFILES ) {
+      *unit = 0;
+      *status = TOO_MANY_FILES;
+      ffpmsg("Cffgiou has no more available unit numbers.");
+   } else {
+      *unit=i;
+      gFitsFiles[i] = (fitsfile *)1; /*  Flag it as taken until ftopen/init  */
+                                     /*  can be called and set a real value  */
+   }
+}
+FCALLSCSUB2(Cffgiou,FTGIOU,ftgiou,PINT,PINT)
+
+void Cfffiou( int unit, int *status );
+void Cfffiou( int unit, int *status )
+{
+   if( *status>0 ) return;
+   if( unit == -1 ) {
+      int i; for( i=50; i<MAXFITSFILES; ) gFitsFiles[i++]=NULL;
+   } else if( unit<1 || unit>=MAXFITSFILES ) {
+      *status = BAD_FILEPTR;
+      ffpmsg("Cfffiou was sent an unacceptable unit number.");
+   } else gFitsFiles[unit]=NULL;
+}
+FCALLSCSUB2(Cfffiou,FTFIOU,ftfiou,INT,PINT)
+
+
+int CFITS2Unit( fitsfile *fptr )
+     /* Utility routine to convert a fitspointer to a Fortran unit number */
+     /* for use when a C program is calling a Fortran routine which could */
+     /* in turn call CFITSIO... Modelled after code by Ning Gan.          */
+{
+   static fitsfile *last_fptr = (fitsfile *)NULL; /* Remember last fptr */
+   static int last_unit = 0;                      /* Remember last unit */
+   int status = 0;
+
+   /*  Test whether we are repeating the last lookup  */
+
+   if( last_unit && fptr==gFitsFiles[last_unit] )
+      return( last_unit );
+
+   /*  Check if gFitsFiles has an entry for this fptr.  */
+   /*  Allows Fortran to call C to call Fortran to      */
+   /*  call CFITSIO... OUCH!!!                          */
+
+   last_fptr = fptr;
+   for( last_unit=1; last_unit<MAXFITSFILES; last_unit++ ) {
+      if( fptr == gFitsFiles[last_unit] )
+	 return( last_unit );
+   }
+
+   /*  Allocate a new unit number for this fptr  */
+   Cffgiou( &last_unit, &status );
+   if( status )
+      last_unit = 0;
+   else
+      gFitsFiles[last_unit] = fptr;
+   return( last_unit );
+}
+
+
+fitsfile* CUnit2FITS(int unit)
+{
+    if( unit<1 || unit>=MAXFITSFILES )
+        return(0);
+	
+    return(gFitsFiles[unit]);
+}
+
+     /**************************************************/
+     /*   Start of wrappers for routines in fitsio.h   */
+     /**************************************************/
+
+/*----------------  FITS file URL parsing routines -------------*/
+
+FCALLSCSUB9(ffiurl,FTIURL,ftiurl,STRING,PSTRING,PSTRING,PSTRING,PSTRING,PSTRING,PSTRING,PSTRING,PINT)
+FCALLSCSUB3(ffrtnm,FTRTNM,ftrtnm,STRING,PSTRING,PINT)
+FCALLSCSUB3(ffexist,FTEXIST,ftexist,STRING,PINT,PINT)
+FCALLSCSUB3(ffextn,FTEXTN,ftextn,STRING,PINT,PINT)
+FCALLSCSUB7(ffrwrg,FTRWRG,ftrwrg,STRING,LONG,INT,PINT,PLONG,PLONG,PINT)
+
+/*---------------- FITS file I/O routines ---------------*/
+
+void Cffopen( fitsfile **fptr, const char *filename, int iomode, int *blocksize, int *status );
+void Cffopen( fitsfile **fptr, const char *filename, int iomode, int *blocksize, int *status )
+{
+   int hdutype;
+
+   if( *fptr==NULL || *fptr==(fitsfile*)1 ) {
+      ffopen( fptr, filename, iomode, status );
+      ffmahd( *fptr, 1, &hdutype, status );
+      *blocksize = 1;
+   } else {
+      *status = FILE_NOT_OPENED;
+      ffpmsg("Cffopen tried to use an already opened unit.");
+   }
+}
+FCALLSCSUB5(Cffopen,FTOPEN,ftopen,PFITSUNIT,STRING,INT,PINT,PINT)
+
+void Cffdkopn( fitsfile **fptr, const char *filename, int iomode, int *blocksize, int *status );
+void Cffdkopn( fitsfile **fptr, const char *filename, int iomode, int *blocksize, int *status )
+{
+   int hdutype;
+
+   if( *fptr==NULL || *fptr==(fitsfile*)1 ) {
+      ffdkopn( fptr, filename, iomode, status );
+      ffmahd( *fptr, 1, &hdutype, status );
+      *blocksize = 1;
+   } else {
+      *status = FILE_NOT_OPENED;
+      ffpmsg("Cffdkopn tried to use an already opened unit.");
+   }
+}
+FCALLSCSUB5(Cffdkopn,FTDKOPN,ftdkopn,PFITSUNIT,STRING,INT,PINT,PINT)
+
+
+void Cffnopn( fitsfile **fptr, const char *filename, int iomode, int *status );
+void Cffnopn( fitsfile **fptr, const char *filename, int iomode, int *status )
+{
+   if( *fptr==NULL || *fptr==(fitsfile*)1 ) {
+      ffopen( fptr, filename, iomode, status );
+   } else {
+      *status = FILE_NOT_OPENED;
+      ffpmsg("Cffnopn tried to use an already opened unit.");
+   }
+}
+FCALLSCSUB4(Cffnopn,FTNOPN,ftnopn,PFITSUNIT,STRING,INT,PINT)
+
+void Cffdopn( fitsfile **fptr, const char *filename, int iomode, int *status );
+void Cffdopn( fitsfile **fptr, const char *filename, int iomode, int *status )
+{
+   if( *fptr==NULL || *fptr==(fitsfile*)1 ) {
+      ffdopn( fptr, filename, iomode, status );
+   } else {
+      *status = FILE_NOT_OPENED;
+      ffpmsg("Cffdopn tried to use an already opened unit.");
+   }
+}
+FCALLSCSUB4(Cffdopn,FTDOPN,ftdopn,PFITSUNIT,STRING,INT,PINT)
+
+void Cfftopn( fitsfile **fptr, const char *filename, int iomode, int *status );
+void Cfftopn( fitsfile **fptr, const char *filename, int iomode, int *status )
+{
+   if( *fptr==NULL || *fptr==(fitsfile*)1 ) {
+      fftopn( fptr, filename, iomode, status );
+   } else {
+      *status = FILE_NOT_OPENED;
+      ffpmsg("Cfftopn tried to use an already opened unit.");
+   }
+}
+FCALLSCSUB4(Cfftopn,FTTOPN,fttopn,PFITSUNIT,STRING,INT,PINT)
+
+void Cffiopn( fitsfile **fptr, const char *filename, int iomode, int *status );
+void Cffiopn( fitsfile **fptr, const char *filename, int iomode, int *status )
+{
+   if( *fptr==NULL || *fptr==(fitsfile*)1 ) {
+      ffiopn( fptr, filename, iomode, status );
+   } else {
+      *status = FILE_NOT_OPENED;
+      ffpmsg("Cffiopn tried to use an already opened unit.");
+   }
+}
+FCALLSCSUB4(Cffiopn,FTIOPN,ftiopn,PFITSUNIT,STRING,INT,PINT)
+
+void Cffreopen( fitsfile *openfptr, fitsfile **newfptr, int *status );
+void Cffreopen( fitsfile *openfptr, fitsfile **newfptr, int *status )
+{
+   if( *newfptr==NULL || *newfptr==(fitsfile*)1 ) {
+      ffreopen( openfptr, newfptr, status );
+   } else {
+      *status = FILE_NOT_OPENED;
+      ffpmsg("Cffreopen tried to use an already opened unit.");
+   }
+}
+FCALLSCSUB3(Cffreopen,FTREOPEN,ftreopen,FITSUNIT,PFITSUNIT,PINT)
+
+void Cffinit( fitsfile **fptr, const char *filename, int blocksize, int *status );
+void Cffinit( fitsfile **fptr, const char *filename, int blocksize, int *status )
+{
+   if( *fptr==NULL || *fptr==(fitsfile*)1 ) {
+      ffinit( fptr, filename, status );
+   } else {
+      *status = FILE_NOT_CREATED;
+      ffpmsg("Cffinit tried to use an already opened unit.");
+   }
+}
+FCALLSCSUB4(Cffinit,FTINIT,ftinit,PFITSUNIT,STRING,INT,PINT)
+
+void Cffdkinit( fitsfile **fptr, const char *filename, int blocksize, int *status );
+void Cffdkinit( fitsfile **fptr, const char *filename, int blocksize, int *status )
+{
+   if( *fptr==NULL || *fptr==(fitsfile*)1 ) {
+      ffdkinit( fptr, filename, status );
+   } else {
+      *status = FILE_NOT_CREATED;
+      ffpmsg("Cffdkinit tried to use an already opened unit.");
+   }
+}
+FCALLSCSUB4(Cffdkinit,FTDKINIT,ftdkinit,PFITSUNIT,STRING,INT,PINT)
+
+void Cfftplt( fitsfile **fptr, const char *filename, const char *tempname,
+	      int *status );
+void Cfftplt( fitsfile **fptr, const char *filename, const char *tempname, 
+	      int *status )
+{
+   if( *fptr==NULL || *fptr==(fitsfile*)1 ) {
+      fftplt( fptr, filename, tempname, status );
+   } else {
+      *status = FILE_NOT_CREATED;
+      ffpmsg("Cfftplt tried to use an already opened unit.");
+   }
+}
+FCALLSCSUB4(Cfftplt,FTTPLT,fttplt,PFITSUNIT,STRING,STRING,PINT)
+
+FCALLSCSUB2(ffflus,FTFLUS,ftflus,FITSUNIT,PINT)
+FCALLSCSUB3(ffflsh,FTFLSH,ftflsh,FITSUNIT, INT, PINT)
+
+void Cffclos( int unit, int *status );
+void Cffclos( int unit, int *status )
+{
+   if( gFitsFiles[unit]!=NULL && gFitsFiles[unit]!=(void*)1 ) {
+      ffclos( gFitsFiles[unit], status );  /* Flag unit number as unavailable */
+      gFitsFiles[unit]=(fitsfile*)1;       /* in case want to reuse it        */
+   }
+}
+FCALLSCSUB2(Cffclos,FTCLOS,ftclos,INT,PINT)
+
+void Cffdelt( int unit, int *status );
+void Cffdelt( int unit, int *status )
+{
+   if( gFitsFiles[unit]!=NULL && gFitsFiles[unit]!=(void*)1 ) {
+      ffdelt( gFitsFiles[unit], status );  /* Flag unit number as unavailable */
+      gFitsFiles[unit]=(fitsfile*)1;       /* in case want to reuse it        */
+   }
+}
+FCALLSCSUB2(Cffdelt,FTDELT,ftdelt,INT,PINT)
+
+FCALLSCSUB3(ffflnm,FTFLNM,ftflnm,FITSUNIT,PSTRING,PINT)
+FCALLSCSUB3(ffflmd,FTFLMD,ftflmd,FITSUNIT,PINT,PINT)
+
+/*--------------- utility routines ---------------*/
+FCALLSCSUB1(ffvers,FTVERS,ftvers,PFLOAT)
+FCALLSCSUB1(ffupch,FTUPCH,ftupch,PSTRING)
+FCALLSCSUB2(ffgerr,FTGERR,ftgerr,INT,PSTRING)
+FCALLSCSUB1(ffpmsg,FTPMSG,ftpmsg,STRING)
+FCALLSCSUB1(ffgmsg,FTGMSG,ftgmsg,PSTRING)
+FCALLSCSUB0(ffcmsg,FTCMSG,ftcmsg)
+FCALLSCSUB0(ffpmrk,FTPMRK,ftpmrk)
+FCALLSCSUB0(ffcmrk,FTCMRK,ftcmrk)
+
+void Cffrprt( char *fname, int status );
+void Cffrprt( char *fname, int status )
+{
+   if( !strcmp(fname,"STDOUT") || !strcmp(fname,"stdout") )
+      ffrprt( stdout, status );
+   else if( !strcmp(fname,"STDERR") || !strcmp(fname,"stderr") )
+      ffrprt( stderr, status );
+   else {
+      FILE *fptr;
+
+      fptr = fopen(fname, "a");
+      if (fptr==NULL)
+	 printf("file pointer is null.\n");
+      else {
+	 ffrprt(fptr,status);
+	 fclose(fptr);
+      }
+   }
+}
+FCALLSCSUB2(Cffrprt,FTRPRT,ftrprt,STRING,INT)
+
+FCALLSCSUB5(ffcmps,FTCMPS,ftcmps,STRING,STRING,LOGICAL,PLOGICAL,PLOGICAL)
+FCALLSCSUB2(fftkey,FTTKEY,fttkey,STRING,PINT)
+FCALLSCSUB2(fftrec,FTTREC,fttrec,STRING,PINT)
+FCALLSCSUB2(ffnchk,FTNCHK,ftnchk,FITSUNIT,PINT)
+FCALLSCSUB4(ffkeyn,FTKEYN,ftkeyn,STRING,INT,PSTRING,PINT)
+FCALLSCSUB4(ffgknm,FTGKNM,ftgknm,STRING,PSTRING, PINT, PINT)
+FCALLSCSUB4(ffnkey,FTNKEY,ftnkey,INT,STRING,PSTRING,PINT)
+FCALLSCSUB3(ffdtyp,FTDTYP,ftdtyp,STRING,PSTRING,PINT)
+FCALLSCFUN1(INT,ffgkcl,FTGKCL,ftgkcl,STRING)
+FCALLSCSUB4(ffpsvc,FTPSVC,ftpsvc,STRING,PSTRING,PSTRING,PINT)
+FCALLSCSUB4(ffgthd,FTGTHD,ftgthd,STRING,PSTRING,PINT,PINT)
+FCALLSCSUB5(ffasfm,FTASFM,ftasfm,STRING,PINT,PLONG,PINT,PINT)
+FCALLSCSUB5(ffbnfm,FTBNFM,ftbnfm,STRING,PINT,PLONG,PLONG,PINT)
+
+#define ftgabc_STRV_A2 NUM_ELEM_ARG(1)
+#define ftgabc_LONGV_A5 A1
+FCALLSCSUB6(ffgabc,FTGABC,ftgabc,INT,STRINGV,INT,PLONG,LONGV,PINT)
+
diff --git a/src/plugins/cfitsio/f77_wrap2.c b/src/plugins/cfitsio/f77_wrap2.c
new file mode 100644
index 0000000..8b7de36
--- /dev/null
+++ b/src/plugins/cfitsio/f77_wrap2.c
@@ -0,0 +1,711 @@
+/************************************************************************
+
+     f77_wrap1.c and f77_wrap2.c have now been split into 4 files to
+     prevent compile-time memory errors (from expansion of compiler commands).
+     f77_wrap1.c was split into f77_wrap1.c and f77_wrap3.c, and
+     f77_wrap2.c was split into f77_wrap2.c and f77_wrap4.c:
+       
+       f77_wrap1.c contains routines operating on whole files and some 
+       utility routines.
+     
+       f77_wrap2.c contains routines operating on primary array, image, 
+       or column elements.
+
+       f77_wrap3.c contains routines operating on headers & keywords.
+
+       f77_wrap4.c contains miscellaneous routines.
+
+     Peter's original comments:
+
+     Together, f77_wrap1.c and f77_wrap2.c contain C wrappers for all
+     the CFITSIO routines prototyped in fitsio.h, except for the
+     generic datatype routines and features not supported in fortran
+     (eg, unsigned integers), a few routines prototyped in fitsio2.h,
+     which only a handful of FTOOLS use, plus a few obsolete FITSIO
+     routines not present in CFITSIO.  This file allows Fortran code
+     to use the CFITSIO library instead of the FITSIO library without
+     modification.  It also gives access to new routines not present
+     in FITSIO.  Fortran FTOOLS must continue using the old routine
+     names from FITSIO (ie, ftxxxx), but most of the C-wrappers simply
+     redirect those calls to the corresponding CFITSIO routines (ie,
+     ffxxxx), with appropriate parameter massaging where necessary.
+     The main exception are read/write routines ending in j (ie, long
+     data) which get redirected to C routines ending in k (ie, int
+     data). This is more consistent with the default integer type in
+     Fortran. f77_wrap1.c primarily holds routines operating on whole
+     files and extension headers.  f77_wrap2.c handle routines which
+     read and write the data portion, plus miscellaneous extra routines.
+     
+        File created by Peter Wilson (HSTX), Oct-Dec. 1997
+************************************************************************/
+
+#include "fitsio2.h"
+#include "f77_wrap.h"
+
+
+FCALLSCSUB5(ffgextn,FTGEXTN,ftgextn,FITSUNIT,LONG,LONG,BYTEV,PINT)
+FCALLSCSUB5(ffpextn,FTPEXTN,ftpextn,FITSUNIT,LONG,LONG,BYTEV,PINT)
+
+/*------------ read primary array or image elements -------------*/
+FCALLSCSUB8(ffgpvb,FTGPVB,ftgpvb,FITSUNIT,LONG,LONG,LONG,BYTE,BYTEV,PLOGICAL,PINT)
+FCALLSCSUB8(ffgpvi,FTGPVI,ftgpvi,FITSUNIT,LONG,LONG,LONG,SHORT,SHORTV,PLOGICAL,PINT)
+FCALLSCSUB8(ffgpvk,FTGPVJ,ftgpvj,FITSUNIT,LONG,LONG,LONG,INT,INTV,PLOGICAL,PINT)
+FCALLSCSUB8(ffgpvjj,FTGPVK,ftgpvk,FITSUNIT,LONG,LONG,LONG,LONGLONG,LONGLONGV,PLOGICAL,PINT)
+FCALLSCSUB8(ffgpve,FTGPVE,ftgpve,FITSUNIT,LONG,LONG,LONG,FLOAT,FLOATV,PLOGICAL,PINT)
+FCALLSCSUB8(ffgpvd,FTGPVD,ftgpvd,FITSUNIT,LONG,LONG,LONG,DOUBLE,DOUBLEV,PLOGICAL,PINT)
+
+
+#define ftgpfb_LOGV_A6 A4
+FCALLSCSUB8(ffgpfb,FTGPFB,ftgpfb,FITSUNIT,LONG,LONG,LONG,BYTEV,LOGICALV,PLOGICAL,PINT)
+
+#define ftgpfi_LOGV_A6 A4
+FCALLSCSUB8(ffgpfi,FTGPFI,ftgpfi,FITSUNIT,LONG,LONG,LONG,SHORTV,LOGICALV,PLOGICAL,PINT)
+
+#define ftgpfj_LOGV_A6 A4
+FCALLSCSUB8(ffgpfk,FTGPFJ,ftgpfj,FITSUNIT,LONG,LONG,LONG,INTV,LOGICALV,PLOGICAL,PINT)
+
+#define ftgpfk_LOGV_A6 A4
+FCALLSCSUB8(ffgpfjj,FTGPFK,ftgpfk,FITSUNIT,LONG,LONG,LONG,LONGLONGV,LOGICALV,PLOGICAL,PINT)
+
+#define ftgpfe_LOGV_A6 A4
+FCALLSCSUB8(ffgpfe,FTGPFE,ftgpfe,FITSUNIT,LONG,LONG,LONG,FLOATV,LOGICALV,PLOGICAL,PINT)
+
+#define ftgpfd_LOGV_A6 A4
+FCALLSCSUB8(ffgpfd,FTGPFD,ftgpfd,FITSUNIT,LONG,LONG,LONG,DOUBLEV,LOGICALV,PLOGICAL,PINT)
+
+FCALLSCSUB9(ffg2db,FTG2DB,ftg2db,FITSUNIT,LONG,BYTE,LONG,LONG,LONG,BYTEV,PLOGICAL,PINT)
+FCALLSCSUB9(ffg2di,FTG2DI,ftg2di,FITSUNIT,LONG,SHORT,LONG,LONG,LONG,SHORTV,PLOGICAL,PINT)
+FCALLSCSUB9(ffg2dk,FTG2DJ,ftg2dj,FITSUNIT,LONG,INT,LONG,LONG,LONG,INTV,PLOGICAL,PINT)
+FCALLSCSUB9(ffg2djj,FTG2DK,ftg2dk,FITSUNIT,LONG,LONGLONG,LONG,LONG,LONG,LONGLONGV,PLOGICAL,PINT)
+FCALLSCSUB9(ffg2de,FTG2DE,ftg2de,FITSUNIT,LONG,FLOAT,LONG,LONG,LONG,FLOATV,PLOGICAL,PINT)
+FCALLSCSUB9(ffg2dd,FTG2DD,ftg2dd,FITSUNIT,LONG,DOUBLE,LONG,LONG,LONG,DOUBLEV,PLOGICAL,PINT)
+
+FCALLSCSUB11(ffg3db,FTG3DB,ftg3db,FITSUNIT,LONG,BYTE,LONG,LONG,LONG,LONG,LONG,BYTEV,PLOGICAL,PINT)
+FCALLSCSUB11(ffg3di,FTG3DI,ftg3di,FITSUNIT,LONG,SHORT,LONG,LONG,LONG,LONG,LONG,SHORTV,PLOGICAL,PINT)
+FCALLSCSUB11(ffg3dk,FTG3DJ,ftg3dj,FITSUNIT,LONG,INT,LONG,LONG,LONG,LONG,LONG,INTV,PLOGICAL,PINT)
+FCALLSCSUB11(ffg3djj,FTG3DK,ftg3dk,FITSUNIT,LONG,LONGLONG,LONG,LONG,LONG,LONG,LONG,LONGLONGV,PLOGICAL,PINT)
+FCALLSCSUB11(ffg3de,FTG3DE,ftg3de,FITSUNIT,LONG,FLOAT,LONG,LONG,LONG,LONG,LONG,FLOATV,PLOGICAL,PINT)
+FCALLSCSUB11(ffg3dd,FTG3DD,ftg3dd,FITSUNIT,LONG,DOUBLE,LONG,LONG,LONG,LONG,LONG,DOUBLEV,PLOGICAL,PINT)
+
+     /*  The follow LONGV definitions have +1 appended because the   */
+     /*  routines use of NAXIS+1 elements of the long vectors.       */
+
+#define ftgsvb_LONGV_A4 A3+1
+#define ftgsvb_LONGV_A5 A3+1
+#define ftgsvb_LONGV_A6 A3+1
+#define ftgsvb_LONGV_A7 A3+1
+FCALLSCSUB11(ffgsvb,FTGSVB,ftgsvb,FITSUNIT,INT,INT,LONGV,LONGV,LONGV,LONGV,BYTE,BYTEV,PLOGICAL,PINT)
+
+#define ftgsvi_LONGV_A4 A3+1
+#define ftgsvi_LONGV_A5 A3+1
+#define ftgsvi_LONGV_A6 A3+1
+#define ftgsvi_LONGV_A7 A3+1
+FCALLSCSUB11(ffgsvi,FTGSVI,ftgsvi,FITSUNIT,INT,INT,LONGV,LONGV,LONGV,LONGV,SHORT,SHORTV,PLOGICAL,PINT)
+
+#define ftgsvj_LONGV_A4 A3+1
+#define ftgsvj_LONGV_A5 A3+1
+#define ftgsvj_LONGV_A6 A3+1
+#define ftgsvj_LONGV_A7 A3+1
+FCALLSCSUB11(ffgsvk,FTGSVJ,ftgsvj,FITSUNIT,INT,INT,LONGV,LONGV,LONGV,LONGV,INT,INTV,PLOGICAL,PINT)
+
+#define ftgsvk_LONGV_A4 A3+1
+#define ftgsvk_LONGV_A5 A3+1
+#define ftgsvk_LONGV_A6 A3+1
+#define ftgsvk_LONGV_A7 A3+1
+FCALLSCSUB11(ffgsvjj,FTGSVK,ftgsvk,FITSUNIT,INT,INT,LONGV,LONGV,LONGV,LONGV,LONGLONG,LONGLONGV,PLOGICAL,PINT)
+
+#define ftgsve_LONGV_A4 A3+1
+#define ftgsve_LONGV_A5 A3+1
+#define ftgsve_LONGV_A6 A3+1
+#define ftgsve_LONGV_A7 A3+1
+FCALLSCSUB11(ffgsve,FTGSVE,ftgsve,FITSUNIT,INT,INT,LONGV,LONGV,LONGV,LONGV,FLOAT,FLOATV,PLOGICAL,PINT)
+
+#define ftgsvd_LONGV_A4 A3+1
+#define ftgsvd_LONGV_A5 A3+1
+#define ftgsvd_LONGV_A6 A3+1
+#define ftgsvd_LONGV_A7 A3+1
+FCALLSCSUB11(ffgsvd,FTGSVD,ftgsvd,FITSUNIT,INT,INT,LONGV,LONGV,LONGV,LONGV,DOUBLE,DOUBLEV,PLOGICAL,PINT)
+
+
+/*   Must handle LOGICALV conversion manually   */
+void Cffgsfb( fitsfile *fptr, int colnum, int naxis, long *naxes, long *blc, long *trc, long *inc, unsigned char *array, int *flagval, int *anynul, int *status );
+void Cffgsfb( fitsfile *fptr, int colnum, int naxis, long *naxes, long *blc, long *trc, long *inc, unsigned char *array, int *flagval, int *anynul, int *status )
+{
+   char *Cflagval;
+   long nflagval;
+   int i;
+ 
+   for( nflagval=1, i=0; i<naxis; i++ )
+      nflagval *= (trc[i]-blc[i])/inc[i]+1;
+   Cflagval = F2CcopyLogVect(nflagval, flagval );
+   ffgsfb( fptr, colnum, naxis, naxes, blc, trc, inc, array, Cflagval, anynul, status );   
+   C2FcopyLogVect(nflagval, flagval, Cflagval);
+}
+#define ftgsfb_LONGV_A4 A3+1
+#define ftgsfb_LONGV_A5 A3+1
+#define ftgsfb_LONGV_A6 A3+1
+#define ftgsfb_LONGV_A7 A3+1
+FCALLSCSUB11(Cffgsfb,FTGSFB,ftgsfb,FITSUNIT,INT,INT,LONGV,LONGV,LONGV,LONGV,BYTEV,INTV,PLOGICAL,PINT)
+
+/*   Must handle LOGICALV conversion manually   */
+void Cffgsfi( fitsfile *fptr, int colnum, int naxis, long *naxes, long *blc, long *trc, long *inc, short *array, int *flagval, int *anynul, int *status );
+void Cffgsfi( fitsfile *fptr, int colnum, int naxis, long *naxes, long *blc, long *trc, long *inc, short *array, int *flagval, int *anynul, int *status )
+{
+   char *Cflagval;
+   long nflagval;
+   int i;
+ 
+   for( nflagval=1, i=0; i<naxis; i++ )
+      nflagval *= (trc[i]-blc[i])/inc[i]+1;
+   Cflagval = F2CcopyLogVect(nflagval, flagval );
+   ffgsfi( fptr, colnum, naxis, naxes, blc, trc, inc, array, Cflagval, anynul, status );   
+   C2FcopyLogVect(nflagval, flagval, Cflagval);
+}
+#define ftgsfi_LONGV_A4 A3+1
+#define ftgsfi_LONGV_A5 A3+1
+#define ftgsfi_LONGV_A6 A3+1
+#define ftgsfi_LONGV_A7 A3+1
+FCALLSCSUB11(Cffgsfi,FTGSFI,ftgsfi,FITSUNIT,INT,INT,LONGV,LONGV,LONGV,LONGV,SHORTV,INTV,PLOGICAL,PINT)
+
+/*   Must handle LOGICALV conversion manually   */
+void Cffgsfk( fitsfile *fptr, int colnum, int naxis, long *naxes, long *blc, long *trc, long *inc, int *array, int *flagval, int *anynul, int *status );
+void Cffgsfk( fitsfile *fptr, int colnum, int naxis, long *naxes, long *blc, long *trc, long *inc, int *array, int *flagval, int *anynul, int *status )
+{
+   char *Cflagval;
+   long nflagval;
+   int i;
+ 
+   for( nflagval=1, i=0; i<naxis; i++ )
+      nflagval *= (trc[i]-blc[i])/inc[i]+1;
+   Cflagval = F2CcopyLogVect(nflagval, flagval );
+   ffgsfk( fptr, colnum, naxis, naxes, blc, trc, inc, array, Cflagval, anynul, status );   
+   C2FcopyLogVect(nflagval, flagval, Cflagval);
+}
+#define ftgsfj_LONGV_A4 A3+1
+#define ftgsfj_LONGV_A5 A3+1
+#define ftgsfj_LONGV_A6 A3+1
+#define ftgsfj_LONGV_A7 A3+1
+FCALLSCSUB11(Cffgsfk,FTGSFJ,ftgsfj,FITSUNIT,INT,INT,LONGV,LONGV,LONGV,LONGV,INTV,INTV,PLOGICAL,PINT)
+
+
+/*   Must handle LOGICALV conversion manually   */
+void Cffgsfjj( fitsfile *fptr, int colnum, int naxis, long *naxes, long *blc, long *trc, long *inc, LONGLONG *array, int *flagval, int *anynul, int *status );
+void Cffgsfjj( fitsfile *fptr, int colnum, int naxis, long *naxes, long *blc, long *trc, long *inc, LONGLONG *array, int *flagval, int *anynul, int *status )
+{
+   char *Cflagval;
+   long nflagval;
+   int i;
+ 
+   for( nflagval=1, i=0; i<naxis; i++ )
+      nflagval *= (trc[i]-blc[i])/inc[i]+1;
+   Cflagval = F2CcopyLogVect(nflagval, flagval );
+   ffgsfjj( fptr, colnum, naxis, naxes, blc, trc, inc, array, Cflagval, anynul, status );   
+   C2FcopyLogVect(nflagval, flagval, Cflagval);
+}
+#define ftgsfk_LONGV_A4 A3+1
+#define ftgsfk_LONGV_A5 A3+1
+#define ftgsfk_LONGV_A6 A3+1
+#define ftgsfk_LONGV_A7 A3+1
+FCALLSCSUB11(Cffgsfjj,FTGSFK,ftgsfk,FITSUNIT,INT,INT,LONGV,LONGV,LONGV,LONGV,LONGLONGV,INTV,PLOGICAL,PINT)
+
+
+/*   Must handle LOGICALV conversion manually   */
+void Cffgsfe( fitsfile *fptr, int colnum, int naxis, long *naxes, long *blc, long *trc, long *inc, float *array, int *flagval, int *anynul, int *status );
+void Cffgsfe( fitsfile *fptr, int colnum, int naxis, long *naxes, long *blc, long *trc, long *inc, float *array, int *flagval, int *anynul, int *status )
+{
+   char *Cflagval;
+   long nflagval;
+   int i;
+ 
+   for( nflagval=1, i=0; i<naxis; i++ )
+      nflagval *= (trc[i]-blc[i])/inc[i]+1;
+   Cflagval = F2CcopyLogVect(nflagval, flagval );
+   ffgsfe( fptr, colnum, naxis, naxes, blc, trc, inc, array, Cflagval, anynul, status );   
+   C2FcopyLogVect(nflagval, flagval, Cflagval);
+}
+#define ftgsfe_LONGV_A4 A3+1
+#define ftgsfe_LONGV_A5 A3+1
+#define ftgsfe_LONGV_A6 A3+1
+#define ftgsfe_LONGV_A7 A3+1
+FCALLSCSUB11(Cffgsfe,FTGSFE,ftgsfe,FITSUNIT,INT,INT,LONGV,LONGV,LONGV,LONGV,FLOATV,INTV,PLOGICAL,PINT)
+
+/*   Must handle LOGICALV conversion manually   */
+void Cffgsfd( fitsfile *fptr, int colnum, int naxis, long *naxes, long *blc, long *trc, long *inc, double *array, int *flagval, int *anynul, int *status );
+void Cffgsfd( fitsfile *fptr, int colnum, int naxis, long *naxes, long *blc, long *trc, long *inc, double *array, int *flagval, int *anynul, int *status )
+{
+   char *Cflagval;
+   long nflagval;
+   int i;
+ 
+   for( nflagval=1, i=0; i<naxis; i++ )
+      nflagval *= (trc[i]-blc[i])/inc[i]+1;
+   Cflagval = F2CcopyLogVect(nflagval, flagval );
+   ffgsfd( fptr, colnum, naxis, naxes, blc, trc, inc, array, Cflagval, anynul, status );   
+   C2FcopyLogVect(nflagval, flagval, Cflagval);
+}
+#define ftgsfd_LONGV_A4 A3+1
+#define ftgsfd_LONGV_A5 A3+1
+#define ftgsfd_LONGV_A6 A3+1
+#define ftgsfd_LONGV_A7 A3+1
+FCALLSCSUB11(Cffgsfd,FTGSFD,ftgsfd,FITSUNIT,INT,INT,LONGV,LONGV,LONGV,LONGV,DOUBLEV,INTV,PLOGICAL,PINT)
+
+FCALLSCSUB6(ffggpb,FTGGPB,ftggpb,FITSUNIT,LONG,LONG,LONG,BYTEV,PINT)
+FCALLSCSUB6(ffggpi,FTGGPI,ftggpi,FITSUNIT,LONG,LONG,LONG,SHORTV,PINT)
+FCALLSCSUB6(ffggpk,FTGGPJ,ftggpj,FITSUNIT,LONG,LONG,LONG,INTV,PINT)
+FCALLSCSUB6(ffggpjj,FTGGPK,ftggpk,FITSUNIT,LONG,LONG,LONG,LONGLONGV,PINT)
+FCALLSCSUB6(ffggpe,FTGGPE,ftggpe,FITSUNIT,LONG,LONG,LONG,FLOATV,PINT)
+FCALLSCSUB6(ffggpd,FTGGPD,ftggpd,FITSUNIT,LONG,LONG,LONG,DOUBLEV,PINT)
+
+/*--------------------- read column elements -------------*/
+/*   To guarantee that we allocate enough memory to hold strings within
+     a table, call FFGTCL first to obtain width of the unique string
+     and use it as the minimum string width.  Also test whether column
+     has a variable width in which case a single string is read
+     containing all its characters, so only declare a string vector
+     with 1 element.                                                     */
+
+#define ftgcvs_STRV_A7 NUM_ELEMS(velem)
+CFextern VOID_cfF(FTGCVS,ftgcvs)
+CFARGT14(NCF,DCF,ABSOFT_cf2(VOID),FITSUNIT,INT,LONG,LONG,LONG,STRING,PSTRINGV,PLOGICAL,PINT,CF_0,CF_0,CF_0,CF_0,CF_0));
+CFextern VOID_cfF(FTGCVS,ftgcvs)
+CFARGT14(NCF,DCF,ABSOFT_cf2(VOID),FITSUNIT,INT,LONG,LONG,LONG,STRING,PSTRINGV,PLOGICAL,PINT,CF_0,CF_0,CF_0,CF_0,CF_0))
+{
+   QCF(FITSUNIT,1)
+   QCF(INT,2)
+   QCF(LONG,3)
+   QCF(LONG,4)
+   QCF(LONG,5)
+   QCF(STRING,6)
+   QCF(PSTRINGV,7)
+   QCF(PLOGICAL,8)
+   QCF(PINT,9)
+
+   fitsfile *fptr;
+   int colnum, *anynul, *status, velem, type;
+   long firstrow, firstelem, nelem;
+   long repeat;
+   unsigned long gMinStrLen=80L;  /* gMin = width */
+   char *nulval, **array;
+
+   fptr =      TCF(ftgcvs,FITSUNIT,1,0);
+   colnum =    TCF(ftgcvs,INT,2,0);
+   firstrow =  TCF(ftgcvs,LONG,3,0);
+   firstelem = TCF(ftgcvs,LONG,4,0);
+   nelem =     TCF(ftgcvs,LONG,5,0);
+   nulval =    TCF(ftgcvs,STRING,6,0);
+   /*  put off variable 7 (array) until column type is learned  */
+   anynul =    TCF(ftgcvs,PLOGICAL,8,0);
+   status =    TCF(ftgcvs,PINT,9,0);
+   
+   ffgtcl( fptr, colnum, &type, &repeat, (long *)&gMinStrLen, status );
+   if( type<0 ) velem = 1;   /*  Variable length column  */
+   else velem = nelem;
+
+   array = TCF(ftgcvs,PSTRINGV,7,0);
+
+   ffgcvs( fptr, colnum, firstrow, firstelem, nelem, nulval, array,
+           anynul, status );
+
+   RCF(FITSUNIT,1)
+   RCF(INT,2)
+   RCF(LONG,3)
+   RCF(LONG,4)
+   RCF(LONG,5)
+   RCF(STRING,6)
+   RCF(PSTRINGV,7)
+   RCF(PLOGICAL,8)
+   RCF(PINT,9)
+}
+
+#define ftgcvsll_STRV_A7 NUM_ELEMS(velem)
+CFextern VOID_cfF(FTGCVSLL,ftgcvsll)
+CFARGT14(NCF,DCF,ABSOFT_cf2(VOID),FITSUNIT,INT,LONGLONG,LONGLONG,LONG,STRING,PSTRINGV,PLOGICAL,PINT,CF_0,CF_0,CF_0,CF_0,CF_0));
+CFextern VOID_cfF(FTGCVSLL,ftgcvsll)
+CFARGT14(NCF,DCF,ABSOFT_cf2(VOID),FITSUNIT,INT,LONGLONG,LONGLONG,LONG,STRING,PSTRINGV,PLOGICAL,PINT,CF_0,CF_0,CF_0,CF_0,CF_0))
+{
+   QCF(FITSUNIT,1)
+   QCF(INT,2)
+   QCF(LONGLONG,3)
+   QCF(LONGLONG,4)
+   QCF(LONG,5)
+   QCF(STRING,6)
+   QCF(PSTRINGV,7)
+   QCF(PLOGICAL,8)
+   QCF(PINT,9)
+
+   fitsfile *fptr;
+   int colnum, *anynul, *status, velem, type;
+   LONGLONG firstrow, firstelem;
+   long nelem;
+   long repeat;
+   unsigned long gMinStrLen=80L;  /* gMin = width */
+   char *nulval, **array;
+
+   fptr =      TCF(ftgcvsll,FITSUNIT,1,0);
+   colnum =    TCF(ftgcvsll,INT,2,0);
+   firstrow =  TCF(ftgcvsll,LONGLONG,3,0);
+   firstelem = TCF(ftgcvsll,LONGLONG,4,0);
+   nelem =     TCF(ftgcvsll,LONG,5,0);
+   nulval =    TCF(ftgcvsll,STRING,6,0);
+   /*  put off variable 7 (array) until column type is learned  */
+   anynul =    TCF(ftgcvsll,PLOGICAL,8,0);
+   status =    TCF(ftgcvsll,PINT,9,0);
+   
+   ffgtcl( fptr, colnum, &type, &repeat, (long *)&gMinStrLen, status );
+   if( type<0 ) velem = 1;   /*  Variable length column  */
+   else velem = nelem;
+
+   array = TCF(ftgcvsll,PSTRINGV,7,0);
+
+   ffgcvs( fptr, colnum, firstrow, firstelem, nelem, nulval, array,
+           anynul, status );
+
+   RCF(FITSUNIT,1)
+   RCF(INT,2)
+   RCF(LONGLONG,3)
+   RCF(LONGLONG,4)
+   RCF(LONG,5)
+   RCF(STRING,6)
+   RCF(PSTRINGV,7)
+   RCF(PLOGICAL,8)
+   RCF(PINT,9)
+}
+
+
+#define ftgcl_LOGV_A6 A5
+FCALLSCSUB7(ffgcl,FTGCL,ftgcl,FITSUNIT,INT,LONG,LONG,LONG,LOGICALV,PINT)
+
+#define ftgcvl_LOGV_A7 A5
+FCALLSCSUB9(ffgcvl,FTGCVL,ftgcvl,FITSUNIT,INT,LONG,LONG,LONG,LOGICAL,LOGICALV,PLOGICAL,PINT)
+FCALLSCSUB9(ffgcvb,FTGCVB,ftgcvb,FITSUNIT,INT,LONG,LONG,LONG,BYTE,BYTEV,PLOGICAL,PINT)
+FCALLSCSUB9(ffgcvi,FTGCVI,ftgcvi,FITSUNIT,INT,LONG,LONG,LONG,SHORT,SHORTV,PLOGICAL,PINT)
+FCALLSCSUB9(ffgcvk,FTGCVJ,ftgcvj,FITSUNIT,INT,LONG,LONG,LONG,INT,INTV,PLOGICAL,PINT)
+FCALLSCSUB9(ffgcvjj,FTGCVK,ftgcvk,FITSUNIT,INT,LONG,LONG,LONG,LONGLONG,LONGLONGV,PLOGICAL,PINT)
+FCALLSCSUB9(ffgcve,FTGCVE,ftgcve,FITSUNIT,INT,LONG,LONG,LONG,FLOAT,FLOATV,PLOGICAL,PINT)
+FCALLSCSUB9(ffgcvd,FTGCVD,ftgcvd,FITSUNIT,INT,LONG,LONG,LONG,DOUBLE,DOUBLEV,PLOGICAL,PINT)
+FCALLSCSUB9(ffgcvc,FTGCVC,ftgcvc,FITSUNIT,INT,LONG,LONG,LONG,FLOAT,FLOATV,PLOGICAL,PINT)
+FCALLSCSUB9(ffgcvm,FTGCVM,ftgcvm,FITSUNIT,INT,LONG,LONG,LONG,DOUBLE,DOUBLEV,PLOGICAL,PINT)
+
+#define ftgcvlll_LOGV_A7 A5
+FCALLSCSUB9(ffgcvl,FTGCVLLL,ftgcvlll,FITSUNIT,INT,LONGLONG,LONGLONG,LONG,LOGICAL,LOGICALV,PLOGICAL,PINT)
+FCALLSCSUB9(ffgcvb,FTGCVBLL,ftgcvbll,FITSUNIT,INT,LONGLONG,LONGLONG,LONG,BYTE,BYTEV,PLOGICAL,PINT)
+FCALLSCSUB9(ffgcvi,FTGCVILL,ftgcvill,FITSUNIT,INT,LONGLONG,LONGLONG,LONG,SHORT,SHORTV,PLOGICAL,PINT)
+FCALLSCSUB9(ffgcvk,FTGCVJLL,ftgcvjll,FITSUNIT,INT,LONGLONG,LONGLONG,LONG,INT,INTV,PLOGICAL,PINT)
+FCALLSCSUB9(ffgcvjj,FTGCVKLL,ftgcvkll,FITSUNIT,INT,LONGLONG,LONGLONG,LONG,LONGLONG,LONGLONGV,PLOGICAL,PINT)
+FCALLSCSUB9(ffgcve,FTGCVELL,ftgcvell,FITSUNIT,INT,LONGLONG,LONGLONG,LONG,FLOAT,FLOATV,PLOGICAL,PINT)
+FCALLSCSUB9(ffgcvd,FTGCVDLL,ftgcvdll,FITSUNIT,INT,LONGLONG,LONGLONG,LONG,DOUBLE,DOUBLEV,PLOGICAL,PINT)
+FCALLSCSUB9(ffgcvc,FTGCVCLL,ftgcvcll,FITSUNIT,INT,LONGLONG,LONGLONG,LONG,FLOAT,FLOATV,PLOGICAL,PINT)
+FCALLSCSUB9(ffgcvm,FTGCVMLL,ftgcvmll,FITSUNIT,INT,LONGLONG,LONGLONG,LONG,DOUBLE,DOUBLEV,PLOGICAL,PINT)
+
+#define ftgcx_LOGV_A6 A5
+FCALLSCSUB7(ffgcx,FTGCX,ftgcx,FITSUNIT,INT,LONG,LONG,LONG,LOGICALV,PINT)
+
+/*   We need to worry about unsigned vs signed pointers in the following  */
+/*   two routines, so use a pair of C wrappers which cast the pointers    */
+/*   before passing them to CFITSIO.                                      */
+
+void Cffgcxui(fitsfile *fptr, int colnum, long firstrow, long nrows,
+              long firstbit, int nbits, short *array, int *status);
+void Cffgcxui(fitsfile *fptr, int colnum, long firstrow, long nrows,
+              long firstbit, int nbits, short *array, int *status)
+{
+   ffgcxui( fptr, colnum, firstrow, nrows, firstbit, nbits,
+	    (unsigned short *)array, status );
+}
+FCALLSCSUB8(Cffgcxui,FTGCXI,ftgcxi,FITSUNIT,INT,LONG,LONG,LONG,INT,SHORTV,PINT)
+
+void Cffgcxuk(fitsfile *fptr, int colnum, long firstrow, long nrows,
+              long firstbit, int nbits, int *array, int *status);
+void Cffgcxuk(fitsfile *fptr, int colnum, long firstrow, long nrows,
+              long firstbit, int nbits, int *array, int *status)
+{
+   ffgcxuk( fptr, colnum, firstrow, nrows, firstbit, nbits,
+	    (unsigned int *)array, status );
+}
+FCALLSCSUB8(Cffgcxuk,FTGCXJ,ftgcxj,FITSUNIT,INT,LONG,LONG,LONG,INT,INTV,PINT)
+
+/*   To guarantee that we allocate enough memory to hold strings within
+     a table, call FFGTCL first to obtain width of the unique string
+     and use it as the minimum string width.  Also test whether column
+     has a variable width in which case a single string is read
+     containing all its characters, so only declare a string vector
+     with 1 element.                                                     */
+
+#define ftgcfs_STRV_A6 NUM_ELEMS(velem)
+#define ftgcfs_LOGV_A7 A5
+CFextern VOID_cfF(FTGCFS,ftgcfs)
+CFARGT14(NCF,DCF,ABSOFT_cf2(VOID),FITSUNIT,INT,LONG,LONG,LONG,PSTRINGV,LOGICALV,PLOGICAL,PINT,CF_0,CF_0,CF_0,CF_0,CF_0));
+CFextern VOID_cfF(FTGCFS,ftgcfs)
+CFARGT14(NCF,DCF,ABSOFT_cf2(VOID),FITSUNIT,INT,LONG,LONG,LONG,PSTRINGV,LOGICALV,PLOGICAL,PINT,CF_0,CF_0,CF_0,CF_0,CF_0))
+{
+   QCF(FITSUNIT,1)
+   QCF(INT,2)
+   QCF(LONG,3)
+   QCF(LONG,4)
+   QCF(LONG,5)
+   QCF(PSTRINGV,6)
+   QCF(LOGICALV,7)
+   QCF(PLOGICAL,8)
+   QCF(PINT,9)
+
+   fitsfile *fptr;
+   int colnum, *anynul, *status, velem, type;
+   long firstrow, firstelem, nelem;
+   long repeat;
+   unsigned long gMinStrLen=80L;  /* gMin = width */
+   char **array, *nularray;
+ 
+   fptr =      TCF(ftgcfs,FITSUNIT,1,0);
+   colnum =    TCF(ftgcfs,INT,2,0);
+   firstrow =  TCF(ftgcfs,LONG,3,0);
+   firstelem = TCF(ftgcfs,LONG,4,0);
+   nelem =     TCF(ftgcfs,LONG,5,0);
+   /*  put off variable 6 (array) until column type is learned  */
+   nularray =  TCF(ftgcfs,LOGICALV,7,0);
+   anynul =    TCF(ftgcfs,PLOGICAL,8,0);
+   status =    TCF(ftgcfs,PINT,9,0);
+   
+   ffgtcl( fptr, colnum, &type, &repeat, (long*)&gMinStrLen, status );
+   if( type<0 ) velem = 1;   /*  Variable length column  */
+   else velem = nelem;
+
+   array = TCF(ftgcfs,PSTRINGV,6,0);
+
+   ffgcfs( fptr, colnum, firstrow, firstelem, nelem, array, nularray,
+           anynul, status);
+
+   RCF(FITSUNIT,1)
+   RCF(INT,2)
+   RCF(LONG,3)
+   RCF(LONG,4)
+   RCF(LONG,5)
+   RCF(PSTRINGV,6)
+   RCF(LOGICALV,7)
+   RCF(PLOGICAL,8)
+   RCF(PINT,9)
+}
+
+#define ftgcfl_LOGV_A6 A5
+#define ftgcfl_LOGV_A7 A5
+FCALLSCSUB9(ffgcfl,FTGCFL,ftgcfl,FITSUNIT,INT,LONG,LONG,LONG,LOGICALV,LOGICALV,PLOGICAL,PINT)
+
+#define ftgcfb_LOGV_A7 A5
+FCALLSCSUB9(ffgcfb,FTGCFB,ftgcfb,FITSUNIT,INT,LONG,LONG,LONG,BYTEV,LOGICALV,PLOGICAL,PINT)
+
+#define ftgcfi_LOGV_A7 A5
+FCALLSCSUB9(ffgcfi,FTGCFI,ftgcfi,FITSUNIT,INT,LONG,LONG,LONG,SHORTV,LOGICALV,PLOGICAL,PINT)
+
+#define ftgcfj_LOGV_A7 A5
+FCALLSCSUB9(ffgcfk,FTGCFJ,ftgcfj,FITSUNIT,INT,LONG,LONG,LONG,INTV,LOGICALV,PLOGICAL,PINT)
+
+#define ftgcfk_LOGV_A7 A5
+FCALLSCSUB9(ffgcfjj,FTGCFK,ftgcfk,FITSUNIT,INT,LONG,LONG,LONG,LONGLONGV,LOGICALV,PLOGICAL,PINT)
+
+#define ftgcfe_LOGV_A7 A5
+FCALLSCSUB9(ffgcfe,FTGCFE,ftgcfe,FITSUNIT,INT,LONG,LONG,LONG,FLOATV,LOGICALV,PLOGICAL,PINT)
+
+#define ftgcfd_LOGV_A7 A5
+FCALLSCSUB9(ffgcfd,FTGCFD,ftgcfd,FITSUNIT,INT,LONG,LONG,LONG,DOUBLEV,LOGICALV,PLOGICAL,PINT)
+
+/*   Must handle LOGICALV conversion manually   */
+void Cffgcfc( fitsfile *fptr, int colnum, long firstrow, long firstelem, long nelem, float *array, int *nularray, int *anynul, int *status );
+void Cffgcfc( fitsfile *fptr, int colnum, long firstrow, long firstelem, long nelem, float *array, int *nularray, int *anynul, int *status )
+{
+   char *Cnularray;
+ 
+   Cnularray = F2CcopyLogVect(nelem*2, nularray );
+   ffgcfc( fptr, colnum, firstrow, firstelem, nelem, array, Cnularray, anynul, status );   
+   C2FcopyLogVect(nelem*2, nularray, Cnularray );
+}
+FCALLSCSUB9(Cffgcfc,FTGCFC,ftgcfc,FITSUNIT,INT,LONG,LONG,LONG,FLOATV,INTV,PLOGICAL,PINT)
+
+/*   Must handle LOGICALV conversion manually   */
+void Cffgcfm( fitsfile *fptr, int colnum, long firstrow, long firstelem, long nelem, double *array, int *nularray, int *anynul, int *status );
+void Cffgcfm( fitsfile *fptr, int colnum, long firstrow, long firstelem, long nelem, double *array, int *nularray, int *anynul, int *status )
+{
+   char *Cnularray;
+ 
+   Cnularray = F2CcopyLogVect(nelem*2, nularray );
+   ffgcfm( fptr, colnum, firstrow, firstelem, nelem, array, Cnularray, anynul, status );   
+   C2FcopyLogVect(nelem*2, nularray, Cnularray );
+}
+FCALLSCSUB9(Cffgcfm,FTGCFM,ftgcfm,FITSUNIT,INT,LONG,LONG,LONG,DOUBLEV,INTV,PLOGICAL,PINT)
+
+
+#define ftgcfbll_LOGV_A7 A5
+FCALLSCSUB9(ffgcfb,FTGCFBLL,ftgcfbll,FITSUNIT,INT,LONGLONG,LONGLONG,LONG,BYTEV,LOGICALV,PLOGICAL,PINT)
+
+#define ftgcfill_LOGV_A7 A5
+FCALLSCSUB9(ffgcfi,FTGCFILL,ftgcfill,FITSUNIT,INT,LONGLONG,LONGLONG,LONG,SHORTV,LOGICALV,PLOGICAL,PINT)
+
+#define ftgcfjll_LOGV_A7 A5
+FCALLSCSUB9(ffgcfk,FTGCFJLL,ftgcfjll,FITSUNIT,INT,LONGLONG,LONGLONG,LONG,INTV,LOGICALV,PLOGICAL,PINT)
+
+#define ftgcfkll_LOGV_A7 A5
+FCALLSCSUB9(ffgcfjj,FTGCFKLL,ftgcfkll,FITSUNIT,INT,LONGLONG,LONGLONG,LONG,LONGLONGV,LOGICALV,PLOGICAL,PINT)
+
+#define ftgcfell_LOGV_A7 A5
+FCALLSCSUB9(ffgcfe,FTGCFELL,ftgcfell,FITSUNIT,INT,LONGLONG,LONGLONG,LONG,FLOATV,LOGICALV,PLOGICAL,PINT)
+
+#define ftgcfdll_LOGV_A7 A5
+FCALLSCSUB9(ffgcfd,FTGCFDLL,ftgcfdll,FITSUNIT,INT,LONGLONG,LONGLONG,LONG,DOUBLEV,LOGICALV,PLOGICAL,PINT)
+
+FCALLSCSUB6(ffgdes,FTGDES,ftgdes,FITSUNIT,INT,LONG,PLONG,PLONG,PINT)
+FCALLSCSUB6(ffgdesll,FTGDESLL,ftgdesll,FITSUNIT,INT,LONG,PLONGLONG,PLONGLONG,PINT)
+
+#define ftgdess_LONGV_A5 A4
+#define ftgdess_LONGV_A6 A4
+FCALLSCSUB7(ffgdess,FTGDESS,ftgdess,FITSUNIT,INT,LONG,LONG,LONGV,LONGV,PINT)
+#define ftgdessll_LONGV_A5 A4
+#define ftgdessll_LONGV_A6 A4FCALLSCSUB7(ffgdessll,FTGDESSLL,ftgdessll,FITSUNIT,INT,LONG,LONG,LONGLONGV,LONGLONGV,PINT)
+FCALLSCSUB7(ffgdessll,FTGDESSLL,ftgdessll,FITSUNIT,INT,LONG,LONG,LONGLONGV,LONGLONGV,PINT)
+
+FCALLSCSUB6(ffgtbb,FTGTBB,ftgtbb,FITSUNIT,LONG,LONG,LONG,BYTEV,PINT)
+FCALLSCSUB6(ffgtbb,FTGTBS,ftgtbs,FITSUNIT,LONG,LONG,LONG,BYTEV,PINT)
+
+/*------------ write primary array or image elements -------------*/
+FCALLSCSUB6(ffpprb,FTPPRB,ftpprb,FITSUNIT,LONG,LONG,LONG,BYTEV,PINT)
+FCALLSCSUB6(ffppri,FTPPRI,ftppri,FITSUNIT,LONG,LONG,LONG,SHORTV,PINT)
+FCALLSCSUB6(ffpprk,FTPPRJ,ftpprj,FITSUNIT,LONG,LONG,LONG,INTV,PINT)
+FCALLSCSUB6(ffpprjj,FTPPRK,ftpprk,FITSUNIT,LONG,LONG,LONG,LONGLONGV,PINT)
+FCALLSCSUB6(ffppre,FTPPRE,ftppre,FITSUNIT,LONG,LONG,LONG,FLOATV,PINT)
+FCALLSCSUB6(ffpprd,FTPPRD,ftpprd,FITSUNIT,LONG,LONG,LONG,DOUBLEV,PINT)
+
+FCALLSCSUB7(ffppnb,FTPPNB,ftppnb,FITSUNIT,LONG,LONG,LONG,BYTEV,BYTE,PINT)
+FCALLSCSUB7(ffppni,FTPPNI,ftppni,FITSUNIT,LONG,LONG,LONG,SHORTV,SHORT,PINT)
+FCALLSCSUB7(ffppnk,FTPPNJ,ftppnj,FITSUNIT,LONG,LONG,LONG,INTV,INT,PINT)
+FCALLSCSUB7(ffppnjj,FTPPNK,ftppnk,FITSUNIT,LONG,LONG,LONG,LONGLONGV,LONGLONG,PINT)
+FCALLSCSUB7(ffppne,FTPPNE,ftppne,FITSUNIT,LONG,LONG,LONG,FLOATV,FLOAT,PINT)
+FCALLSCSUB7(ffppnd,FTPPND,ftppnd,FITSUNIT,LONG,LONG,LONG,DOUBLEV,DOUBLE,PINT)
+
+FCALLSCSUB7(ffp2db,FTP2DB,ftp2db,FITSUNIT,LONG,LONG,LONG,LONG,BYTEV,PINT)
+FCALLSCSUB7(ffp2di,FTP2DI,ftp2di,FITSUNIT,LONG,LONG,LONG,LONG,SHORTV,PINT)
+FCALLSCSUB7(ffp2dk,FTP2DJ,ftp2dj,FITSUNIT,LONG,LONG,LONG,LONG,INTV,PINT)
+FCALLSCSUB7(ffp2djj,FTP2DK,ftp2dk,FITSUNIT,LONG,LONG,LONG,LONG,LONGLONGV,PINT)
+FCALLSCSUB7(ffp2de,FTP2DE,ftp2de,FITSUNIT,LONG,LONG,LONG,LONG,FLOATV,PINT)
+FCALLSCSUB7(ffp2dd,FTP2DD,ftp2dd,FITSUNIT,LONG,LONG,LONG,LONG,DOUBLEV,PINT)
+
+FCALLSCSUB9(ffp3db,FTP3DB,ftp3db,FITSUNIT,LONG,LONG,LONG,LONG,LONG,LONG,BYTEV,PINT)
+FCALLSCSUB9(ffp3di,FTP3DI,ftp3di,FITSUNIT,LONG,LONG,LONG,LONG,LONG,LONG,SHORTV,PINT)
+FCALLSCSUB9(ffp3dk,FTP3DJ,ftp3dj,FITSUNIT,LONG,LONG,LONG,LONG,LONG,LONG,INTV,PINT)
+FCALLSCSUB9(ffp3djj,FTP3DK,ftp3dk,FITSUNIT,LONG,LONG,LONG,LONG,LONG,LONG,LONGLONGV,PINT)
+FCALLSCSUB9(ffp3de,FTP3DE,ftp3de,FITSUNIT,LONG,LONG,LONG,LONG,LONG,LONG,FLOATV,PINT)
+FCALLSCSUB9(ffp3dd,FTP3DD,ftp3dd,FITSUNIT,LONG,LONG,LONG,LONG,LONG,LONG,DOUBLEV,PINT)
+
+#define ftpssb_LONGV_A4 A3
+#define ftpssb_LONGV_A5 A3
+#define ftpssb_LONGV_A6 A3
+FCALLSCSUB8(ffpssb,FTPSSB,ftpssb,FITSUNIT,LONG,LONG,LONGV,LONGV,LONGV,BYTEV,PINT)
+
+#define ftpssi_LONGV_A4 A3
+#define ftpssi_LONGV_A5 A3
+#define ftpssi_LONGV_A6 A3
+FCALLSCSUB8(ffpssi,FTPSSI,ftpssi,FITSUNIT,LONG,LONG,LONGV,LONGV,LONGV,SHORTV,PINT)
+
+#define ftpssj_LONGV_A4 A3
+#define ftpssj_LONGV_A5 A3
+#define ftpssj_LONGV_A6 A3
+FCALLSCSUB8(ffpssk,FTPSSJ,ftpssj,FITSUNIT,LONG,LONG,LONGV,LONGV,LONGV,INTV,PINT)
+
+#define ftpssk_LONGV_A4 A3
+#define ftpssk_LONGV_A5 A3
+#define ftpssk_LONGV_A6 A3
+FCALLSCSUB8(ffpssjj,FTPSSK,ftpssk,FITSUNIT,LONG,LONG,LONGV,LONGV,LONGV,LONGLONGV,PINT)
+
+#define ftpsse_LONGV_A4 A3
+#define ftpsse_LONGV_A5 A3
+#define ftpsse_LONGV_A6 A3
+FCALLSCSUB8(ffpsse,FTPSSE,ftpsse,FITSUNIT,LONG,LONG,LONGV,LONGV,LONGV,FLOATV,PINT)
+
+#define ftpssd_LONGV_A4 A3
+#define ftpssd_LONGV_A5 A3
+#define ftpssd_LONGV_A6 A3
+FCALLSCSUB8(ffpssd,FTPSSD,ftpssd,FITSUNIT,LONG,LONG,LONGV,LONGV,LONGV,DOUBLEV,PINT)
+
+FCALLSCSUB6(ffpgpb,FTPGPB,ftpgpb,FITSUNIT,LONG,LONG,LONG,BYTEV,PINT)
+FCALLSCSUB6(ffpgpi,FTPGPI,ftpgpi,FITSUNIT,LONG,LONG,LONG,SHORTV,PINT)
+FCALLSCSUB6(ffpgpk,FTPGPJ,ftpgpj,FITSUNIT,LONG,LONG,LONG,INTV,PINT)
+FCALLSCSUB6(ffpgpjj,FTPGPK,ftpgpk,FITSUNIT,LONG,LONG,LONG,LONGLONGV,PINT)
+FCALLSCSUB6(ffpgpe,FTPGPE,ftpgpe,FITSUNIT,LONG,LONG,LONG,FLOATV,PINT)
+FCALLSCSUB6(ffpgpd,FTPGPD,ftpgpd,FITSUNIT,LONG,LONG,LONG,DOUBLEV,PINT)
+
+FCALLSCSUB5(ffppru,FTPPRU,ftppru,FITSUNIT,LONG,LONG,LONG,PINT)
+FCALLSCSUB4(ffpprn,FTPPRN,ftpprn,FITSUNIT,LONG,LONG,PINT)
+
+/*--------------------- write column elements -------------*/
+#define ftpcls_STRV_A6 NUM_ELEM_ARG(5)
+FCALLSCSUB7(ffpcls,FTPCLS,ftpcls,FITSUNIT,INT,LONG,LONG,LONG,STRINGV,PINT)
+
+#define ftpcll_LOGV_A6 A5
+FCALLSCSUB7(ffpcll,FTPCLL,ftpcll,FITSUNIT,INT,LONG,LONG,LONG,LOGICALV,PINT)
+FCALLSCSUB7(ffpclb,FTPCLB,ftpclb,FITSUNIT,INT,LONG,LONG,LONG,BYTEV,PINT)
+FCALLSCSUB7(ffpcli,FTPCLI,ftpcli,FITSUNIT,INT,LONG,LONG,LONG,SHORTV,PINT)
+FCALLSCSUB7(ffpclk,FTPCLJ,ftpclj,FITSUNIT,INT,LONG,LONG,LONG,INTV,PINT)
+FCALLSCSUB7(ffpcljj,FTPCLK,ftpclk,FITSUNIT,INT,LONG,LONG,LONG,LONGLONGV,PINT)
+FCALLSCSUB7(ffpcle,FTPCLE,ftpcle,FITSUNIT,INT,LONG,LONG,LONG,FLOATV,PINT)
+FCALLSCSUB7(ffpcld,FTPCLD,ftpcld,FITSUNIT,INT,LONG,LONG,LONG,DOUBLEV,PINT)
+FCALLSCSUB7(ffpclc,FTPCLC,ftpclc,FITSUNIT,INT,LONG,LONG,LONG,FLOATV,PINT)
+FCALLSCSUB7(ffpclm,FTPCLM,ftpclm,FITSUNIT,INT,LONG,LONG,LONG,DOUBLEV,PINT)
+FCALLSCSUB6(ffpclu,FTPCLU,ftpclu,FITSUNIT,INT,LONG,LONG,LONG,PINT)
+FCALLSCSUB4(ffprwu,FTPRWU,ftprwu,FITSUNIT,LONG,LONG,PINT)
+
+#define ftpclsll_STRV_A6 NUM_ELEM_ARG(5)
+FCALLSCSUB7(ffpcls,FTPCLSLL,ftpclsll,FITSUNIT,INT,LONGLONG,LONGLONG,LONG,STRINGV,PINT)
+
+#define ftpcllll_LOGV_A6 A5
+FCALLSCSUB7(ffpcll,FTPCLLLL,ftpcllll,FITSUNIT,INT,LONGLONG,LONGLONG,LONG,LOGICALV,PINT)
+FCALLSCSUB7(ffpclb,FTPCLBLL,ftpclbll,FITSUNIT,INT,LONGLONG,LONGLONG,LONG,BYTEV,PINT)
+FCALLSCSUB7(ffpcli,FTPCLILL,ftpclill,FITSUNIT,INT,LONGLONG,LONGLONG,LONG,SHORTV,PINT)
+FCALLSCSUB7(ffpclk,FTPCLJLL,ftpcljll,FITSUNIT,INT,LONGLONG,LONGLONG,LONG,INTV,PINT)
+FCALLSCSUB7(ffpcljj,FTPCLKLL,ftpclkll,FITSUNIT,INT,LONGLONG,LONGLONG,LONG,LONGLONGV,PINT)
+FCALLSCSUB7(ffpcle,FTPCLELL,ftpclell,FITSUNIT,INT,LONGLONG,LONGLONG,LONG,FLOATV,PINT)
+FCALLSCSUB7(ffpcld,FTPCLDLL,ftpcldll,FITSUNIT,INT,LONGLONG,LONGLONG,LONG,DOUBLEV,PINT)
+FCALLSCSUB7(ffpclc,FTPCLCLL,ftpclcll,FITSUNIT,INT,LONGLONG,LONGLONG,LONG,FLOATV,PINT)
+FCALLSCSUB7(ffpclm,FTPCLMLL,ftpclmll,FITSUNIT,INT,LONGLONG,LONGLONG,LONG,DOUBLEV,PINT)
+FCALLSCSUB6(ffpclu,FTPCLULL,ftpclull,FITSUNIT,INT,LONGLONG,LONGLONG,LONG,PINT)
+
+#define ftpclx_LOGV_A6 A5
+FCALLSCSUB7(ffpclx,FTPCLX,ftpclx,FITSUNIT,INT,LONG,LONG,LONG,LOGICALV,PINT)
+
+#define ftpcns_STRV_A6 NUM_ELEM_ARG(5)
+FCALLSCSUB8(ffpcns,FTPCNS,ftpcns,FITSUNIT,INT,LONG,LONG,LONG,STRINGV,STRING,PINT)
+
+FCALLSCSUB8(ffpcnb,FTPCNB,ftpcnb,FITSUNIT,INT,LONG,LONG,LONG,BYTEV,BYTE,PINT)
+FCALLSCSUB8(ffpcni,FTPCNI,ftpcni,FITSUNIT,INT,LONG,LONG,LONG,SHORTV,SHORT,PINT)
+FCALLSCSUB8(ffpcnk,FTPCNJ,ftpcnj,FITSUNIT,INT,LONG,LONG,LONG,INTV,INT,PINT)
+FCALLSCSUB8(ffpcnjj,FTPCNK,ftpcnk,FITSUNIT,INT,LONG,LONG,LONG,LONGLONGV,LONGLONG,PINT)
+FCALLSCSUB8(ffpcne,FTPCNE,ftpcne,FITSUNIT,INT,LONG,LONG,LONG,FLOATV,FLOAT,PINT)
+FCALLSCSUB8(ffpcnd,FTPCND,ftpcnd,FITSUNIT,INT,LONG,LONG,LONG,DOUBLEV,DOUBLE,PINT)
+
+#define ftpcnsll_STRV_A6 NUM_ELEM_ARG(5)
+FCALLSCSUB8(ffpcns,FTPCNSLL,ftpcnsll,FITSUNIT,INT,LONGLONG,LONGLONG,LONG,STRINGV,STRING,PINT)
+
+FCALLSCSUB8(ffpcnb,FTPCNBLL,ftpcnbll,FITSUNIT,INT,LONGLONG,LONGLONG,LONG,BYTEV,BYTE,PINT)
+FCALLSCSUB8(ffpcni,FTPCNILL,ftpcnill,FITSUNIT,INT,LONGLONG,LONGLONG,LONG,SHORTV,SHORT,PINT)
+FCALLSCSUB8(ffpcnk,FTPCNJLL,ftpcnjll,FITSUNIT,INT,LONGLONG,LONGLONG,LONG,INTV,INT,PINT)
+FCALLSCSUB8(ffpcnjj,FTPCNKLL,ftpcnkll,FITSUNIT,INT,LONGLONG,LONGLONG,LONG,LONGLONGV,LONGLONG,PINT)
+FCALLSCSUB8(ffpcne,FTPCNELL,ftpcnell,FITSUNIT,INT,LONGLONG,LONGLONG,LONG,FLOATV,FLOAT,PINT)
+FCALLSCSUB8(ffpcnd,FTPCNDLL,ftpcndll,FITSUNIT,INT,LONGLONG,LONGLONG,LONG,DOUBLEV,DOUBLE,PINT)
+
+FCALLSCSUB6(ffpdes,FTPDES,ftpdes,FITSUNIT,INT,LONG,LONG,LONG,PINT)
+FCALLSCSUB6(ffpdes,FTPDESLL,ftpdesll,FITSUNIT,INT,LONG,LONGLONG,LONGLONG,PINT)
+
+FCALLSCSUB6(ffptbb,FTPTBB,ftptbb,FITSUNIT,LONG,LONG,LONG,BYTEV,PINT)
+   /*  Add extra entry point to ffptbb... ftptbs obsolete  */
+FCALLSCSUB6(ffptbb,FTPTBS,ftptbs,FITSUNIT,LONG,LONG,LONG,BYTEV,PINT)
+
+FCALLSCSUB4(ffirow,FTIROW,ftirow,FITSUNIT,LONG,LONG,PINT)
+FCALLSCSUB4(ffirow,FTIROWLL,ftirowll,FITSUNIT,LONGLONG,LONGLONG,PINT)
+FCALLSCSUB4(ffdrow,FTDROW,ftdrow,FITSUNIT,LONG,LONG,PINT)
+FCALLSCSUB4(ffdrow,FTDROWLL,ftdrowll,FITSUNIT,LONGLONG,LONGLONG,PINT)
+FCALLSCSUB3(ffdrrg,FTDRRG,ftdrrg,FITSUNIT,STRING,PINT)
+#define ftdrws_LONGV_A2 A3
+FCALLSCSUB4(ffdrws,FTDRWS,ftdrws,FITSUNIT,LONGV,LONG,PINT)
+FCALLSCSUB5(fficol,FTICOL,fticol,FITSUNIT,INT,STRING,STRING,PINT)
+
+#define fticls_STRV_A4 NUM_ELEM_ARG(3)
+#define fticls_STRV_A5 NUM_ELEM_ARG(3)
+FCALLSCSUB6(fficls,FTICLS,fticls,FITSUNIT,INT,INT,STRINGV,STRINGV,PINT)
+FCALLSCSUB4(ffmvec,FTMVEC,ftmvec,FITSUNIT,INT,LONG,PINT)
+FCALLSCSUB3(ffdcol,FTDCOL,ftdcol,FITSUNIT,INT,PINT)
+FCALLSCSUB6(ffcpcl,FTCPCL,ftcpcl,FITSUNIT,FITSUNIT,INT,INT,INT,PINT)
diff --git a/src/plugins/cfitsio/f77_wrap3.c b/src/plugins/cfitsio/f77_wrap3.c
new file mode 100644
index 0000000..e64ef27
--- /dev/null
+++ b/src/plugins/cfitsio/f77_wrap3.c
@@ -0,0 +1,853 @@
+/************************************************************************
+
+     f77_wrap1.c and f77_wrap2.c have now been split into 4 files to
+     prevent compile-time memory errors (from expansion of compiler commands).
+     f77_wrap1.c was split into f77_wrap1.c and f77_wrap3.c, and
+     f77_wrap2.c was split into f77_wrap2.c and f77_wrap4.c:
+
+       f77_wrap1.c contains routines operating on whole files and some
+       utility routines.
+
+       f77_wrap2.c contains routines operating on primary array, image,
+       or column elements.
+
+       f77_wrap3.c contains routines operating on headers & keywords.
+
+       f77_wrap4.c contains miscellaneous routines.
+
+     Peter's original comments:
+
+     Together, f77_wrap1.c and f77_wrap2.c contain C wrappers for all
+     the CFITSIO routines prototyped in fitsio.h, except for the
+     generic datatype routines and features not supported in fortran
+     (eg, unsigned integers), a few routines prototyped in fitsio2.h,
+     which only a handful of FTOOLS use, plus a few obsolete FITSIO
+     routines not present in CFITSIO.  This file allows Fortran code
+     to use the CFITSIO library instead of the FITSIO library without
+     modification.  It also gives access to new routines not present
+     in FITSIO.  Fortran FTOOLS must continue using the old routine
+     names from FITSIO (ie, ftxxxx), but most of the C-wrappers simply
+     redirect those calls to the corresponding CFITSIO routines (ie,
+     ffxxxx), with appropriate parameter massaging where necessary.
+     The main exception are read/write routines ending in j (ie, long
+     data) which get redirected to C routines ending in k (ie, int
+     data). This is more consistent with the default integer type in
+     Fortran. f77_wrap1.c primarily holds routines operating on whole
+     files and extension headers.  f77_wrap2.c handle routines which
+     read and write the data portion, plus miscellaneous extra routines.
+     
+        File created by Peter Wilson (HSTX), Oct-Dec. 1997
+************************************************************************/
+
+#include "fitsio2.h"
+#include "f77_wrap.h"
+
+/*----------------- write single keywords --------------*/
+FCALLSCSUB3(ffprec,FTPREC,ftprec,FITSUNIT,STRING,PINT)
+FCALLSCSUB3(ffpcom,FTPCOM,ftpcom,FITSUNIT,STRING,PINT)
+FCALLSCSUB4(ffpunt,FTPUNT,ftpunt,FITSUNIT,STRING,STRING,PINT)
+FCALLSCSUB3(ffphis,FTPHIS,ftphis,FITSUNIT,STRING,PINT)
+FCALLSCSUB2(ffpdat,FTPDAT,ftpdat,FITSUNIT,PINT)
+FCALLSCSUB3(ffgstm,FTGSTM,ftgstm,PSTRING,PINT,PINT)
+FCALLSCSUB4(ffgsdt,FTGSDT,ftgsdt,PINT,PINT,PINT,PINT)
+FCALLSCSUB5(ffdt2s,FTDT2S,ftdt2s,INT,INT,INT,PSTRING,PINT)
+FCALLSCSUB9(fftm2s,FTTM2S,fttm2s,INT,INT,INT,INT,INT,DOUBLE,INT,PSTRING,PINT)
+FCALLSCSUB5(ffs2dt,FTS2DT,fts2dt,STRING,PINT,PINT,PINT,PINT)
+FCALLSCSUB8(ffs2tm,FTS2TM,fts2tm,STRING,PINT,PINT,PINT,PINT,PINT,PDOUBLE,PINT)
+FCALLSCSUB4(ffpkyu,FTPKYU,ftpkyu,FITSUNIT,STRING,STRING,PINT)
+FCALLSCSUB5(ffpkys,FTPKYS,ftpkys,FITSUNIT,STRING,STRING,STRING,PINT)
+FCALLSCSUB5(ffpkls,FTPKLS,ftpkls,FITSUNIT,STRING,STRING,STRING,PINT)
+FCALLSCSUB2(ffplsw,FTPLSW,ftplsw,FITSUNIT,PINT)
+FCALLSCSUB5(ffpkyl,FTPKYL,ftpkyl,FITSUNIT,STRING,INT,STRING,PINT)
+FCALLSCSUB5(ffpkyj,FTPKYJ,ftpkyj,FITSUNIT,STRING,LONG,STRING,PINT)
+FCALLSCSUB5(ffpkyj,FTPKYK,ftpkyk,FITSUNIT,STRING,LONGLONG,STRING,PINT)
+FCALLSCSUB6(ffpkyf,FTPKYF,ftpkyf,FITSUNIT,STRING,FLOAT,INT,STRING,PINT)
+FCALLSCSUB6(ffpkye,FTPKYE,ftpkye,FITSUNIT,STRING,FLOAT,INT,STRING,PINT)
+FCALLSCSUB6(ffpkyg,FTPKYG,ftpkyg,FITSUNIT,STRING,DOUBLE,INT,STRING,PINT)
+FCALLSCSUB6(ffpkyd,FTPKYD,ftpkyd,FITSUNIT,STRING,DOUBLE,INT,STRING,PINT)
+FCALLSCSUB6(ffpkyc,FTPKYC,ftpkyc,FITSUNIT,STRING,FLOATV,INT,STRING,PINT)
+FCALLSCSUB6(ffpkym,FTPKYM,ftpkym,FITSUNIT,STRING,DOUBLEV,INT,STRING,PINT)
+FCALLSCSUB6(ffpkfc,FTPKFC,ftpkfc,FITSUNIT,STRING,FLOATV,INT,STRING,PINT)
+FCALLSCSUB6(ffpkfm,FTPKFM,ftpkfm,FITSUNIT,STRING,DOUBLEV,INT,STRING,PINT)
+FCALLSCSUB6(ffpkyt,FTPKYT,ftpkyt,FITSUNIT,STRING,LONG,DOUBLE,STRING,PINT)
+
+#define ftptdm_LONGV_A4 A3
+FCALLSCSUB5(ffptdm,FTPTDM,ftptdm,FITSUNIT,INT,INT,LONGV,PINT)
+
+/*----------------- write array of keywords --------------*/
+#define ftpkns_STRV_A5 NUM_ELEM_ARG(4)
+#define ftpkns_STRV_A6 NUM_ELEM_ARG(4)
+FCALLSCSUB7(ffpkns,FTPKNS,ftpkns,FITSUNIT,STRING,INT,INT,STRINGV,STRINGV,PINT)
+
+/*   Must handle LOGICALV conversion manually... ffpknl uses ints   */
+void Cffpknl( fitsfile *fptr, char *keyroot, int nstart, int nkeys,
+              int *numval, char **comment, int *status );
+void Cffpknl( fitsfile *fptr, char *keyroot, int nstart, int nkeys,
+              int *numval, char **comment, int *status )
+{
+   int i;
+ 
+   for( i=0; i<nkeys; i++ )
+      numval[i] = F2CLOGICAL(numval[i]);
+   ffpknl( fptr, keyroot, nstart, nkeys, numval, comment, status );
+   for( i=0; i<nkeys; i++ )
+      numval[i] = C2FLOGICAL(numval[i]);
+}
+#define ftpknl_STRV_A6 NUM_ELEM_ARG(4)
+FCALLSCSUB7(Cffpknl,FTPKNL,ftpknl,FITSUNIT,STRING,INT,INT,INTV,STRINGV,PINT)
+
+#define ftpknj_STRV_A6 NUM_ELEM_ARG(4)
+#define ftpknj_LONGV_A5 A4
+FCALLSCSUB7(ffpknj,FTPKNJ,ftpknj,FITSUNIT,STRING,INT,INT,LONGV,STRINGV,PINT)
+
+#define ftpknk_STRV_A6 NUM_ELEM_ARG(4)
+#define ftpknk_LONGLONGV_A5 A4
+FCALLSCSUB7(ffpknjj,FTPKNK,ftpknk,FITSUNIT,STRING,INT,INT,LONGLONGV,STRINGV,PINT)
+
+#define ftpknf_STRV_A7 NUM_ELEM_ARG(4)
+FCALLSCSUB8(ffpknf,FTPKNF,ftpknf,FITSUNIT,STRING,INT,INT,FLOATV,INT,STRINGV,PINT)
+
+#define ftpkne_STRV_A7 NUM_ELEM_ARG(4)
+FCALLSCSUB8(ffpkne,FTPKNE,ftpkne,FITSUNIT,STRING,INT,INT,FLOATV,INT,STRINGV,PINT)
+
+#define ftpkng_STRV_A7 NUM_ELEM_ARG(4)
+FCALLSCSUB8(ffpkng,FTPKNG,ftpkng,FITSUNIT,STRING,INT,INT,DOUBLEV,INT,STRINGV,PINT)
+
+#define ftpknd_STRV_A7 NUM_ELEM_ARG(4)
+FCALLSCSUB8(ffpknd,FTPKND,ftpknd,FITSUNIT,STRING,INT,INT,DOUBLEV,INT,STRINGV,PINT)
+
+FCALLSCSUB6(ffcpky,FTCPKY,ftcpky,FITSUNIT,FITSUNIT,INT,INT,STRING,PINT)
+
+/*----------------- write required header keywords --------------*/
+#define ftphps_LONGV_A4 A3
+FCALLSCSUB5(ffphps,FTPHPS,ftphps,FITSUNIT,INT,INT,LONGV,PINT)
+
+void Cffphpr( fitsfile *fptr, int simple, int bitpix, int naxis, long naxes[], long pcount, long gcount, int extend, int *status );
+void Cffphpr( fitsfile *fptr, int simple, int bitpix, int naxis, long naxes[], long pcount, long gcount, int extend, int *status )
+{
+   if( gcount==0 ) gcount=1;
+   ffphpr( fptr, simple, bitpix, naxis, naxes, pcount,
+           gcount, extend, status );
+}
+#define ftphpr_LONGV_A5 A4
+FCALLSCSUB9(Cffphpr,FTPHPR,ftphpr,FITSUNIT,LOGICAL,INT,INT,LONGV,LONG,LONG,LOGICAL,PINT)
+
+#define ftphext_LONGV_A5 A4
+FCALLSCSUB8(ffphext,FTPHEXT,ftphext,FITSUNIT,STRING,INT,INT,LONGV,LONG,LONG,PINT)
+
+
+#define ftphtb_STRV_A5 NUM_ELEM_ARG(4)
+#define ftphtb_STRV_A7 NUM_ELEM_ARG(4)
+#define ftphtb_STRV_A8 NUM_ELEM_ARG(4)
+#define ftphtb_LONGV_A6 A4
+FCALLSCSUB10(ffphtb,FTPHTB,ftphtb,FITSUNIT,LONG,LONG,INT,STRINGV,LONGV,STRINGV,STRINGV,STRING,PINT)
+
+#define ftphbn_STRV_A4 NUM_ELEM_ARG(3)
+#define ftphbn_STRV_A5 NUM_ELEM_ARG(3)
+#define ftphbn_STRV_A6 NUM_ELEM_ARG(3)
+FCALLSCSUB9(ffphbn,FTPHBN,ftphbn,FITSUNIT,LONG,INT,STRINGV,STRINGV,STRINGV,STRING,LONG,PINT)
+
+/*  Archaic names exist for preceding 3 functions...
+    continue supporting them.                           */
+
+#define ftpprh_LONGV_A5 A4
+FCALLSCSUB9(Cffphpr,FTPPRH,ftpprh,FITSUNIT,LOGICAL,INT,INT,LONGV,LONG,LONG,LOGICAL,PINT)
+
+#define ftpbnh_STRV_A4 NUM_ELEM_ARG(3)
+#define ftpbnh_STRV_A5 NUM_ELEM_ARG(3)
+#define ftpbnh_STRV_A6 NUM_ELEM_ARG(3)
+FCALLSCSUB9(ffphbn,FTPBNH,ftpbnh,FITSUNIT,LONG,INT,STRINGV,STRINGV,STRINGV,STRING,LONG,PINT)
+
+#define ftptbh_STRV_A5 NUM_ELEM_ARG(4)
+#define ftptbh_STRV_A7 NUM_ELEM_ARG(4)
+#define ftptbh_STRV_A8 NUM_ELEM_ARG(4)
+#define ftptbh_LONGV_A6 A4
+FCALLSCSUB10(ffphtb,FTPTBH,ftptbh,FITSUNIT,LONG,LONG,INT,STRINGV,LONGV,STRINGV,STRINGV,STRING,PINT)
+
+/*----------------- write template keywords --------------*/
+FCALLSCSUB3(ffpktp,FTPKTP,ftpktp,FITSUNIT,STRING,PINT)
+
+/*------------------ get header information --------------*/
+FCALLSCSUB4(ffghsp,FTGHSP,ftghsp,FITSUNIT,PINT,PINT,PINT)
+FCALLSCSUB4(ffghps,FTGHPS,ftghps,FITSUNIT,PINT,PINT,PINT)
+
+/*------------------ move position in header -------------*/
+FCALLSCSUB3(ffmaky,FTMAKY,ftmaky,FITSUNIT,INT,PINT)
+FCALLSCSUB3(ffmrky,FTMRKY,ftmrky,FITSUNIT,INT,PINT)
+
+/*------------------ read single keywords ----------------*/
+#define ftgnxk_STRV_A2 NUM_ELEM_ARG(3)
+#define ftgnxk_STRV_A4 NUM_ELEM_ARG(5)
+FCALLSCSUB7(ffgnxk,FTGNXK,ftgnxk,FITSUNIT,STRINGV,INT,STRINGV,INT,PSTRING,PINT)
+FCALLSCSUB4(ffgrec,FTGREC,ftgrec,FITSUNIT,INT,PSTRING,PINT)
+FCALLSCSUB4(ffgcrd,FTGCRD,ftgcrd,FITSUNIT,STRING,PSTRING,PINT)
+FCALLSCSUB4(ffgunt,FTGUNT,ftgunt,FITSUNIT,STRING,PSTRING,PINT)
+FCALLSCSUB6(ffgkyn,FTGKYN,ftgkyn,FITSUNIT,INT,PSTRING,PSTRING,PSTRING,PINT)
+FCALLSCSUB5(ffgkey,FTGKEY,ftgkey,FITSUNIT,STRING,PSTRING,PSTRING,PINT)
+
+/*   FTGKYS supported the long string convention but FFGKYS does not,
+     so redirect to FFGKLS.  To handle the pointer to a pointer,
+     manually expand the FCALLSC macro and modify function call.       */
+
+CFextern VOID_cfF(FTGKYS,ftgkys)
+CFARGT14(NCF,DCF,ABSOFT_cf2(VOID),FITSUNIT,STRING,PSTRING,PSTRING,PINT,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0));
+CFextern VOID_cfF(FTGKYS,ftgkys)
+CFARGT14(NCF,DCF,ABSOFT_cf2(VOID),FITSUNIT,STRING,PSTRING,PSTRING,PINT,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0))
+{
+   QCF(FITSUNIT,1)
+   QCF(STRING,2)
+   QCF(PSTRING,3)   /*  Defines a character pointer  */
+   QCF(PSTRING,4)
+   QCF(PINT,5)
+
+   ffgkls( TCF(ftgkys,FITSUNIT,1,0)
+           TCF(ftgkys,STRING,2,1)
+           , &B3                        /*  Pass address of pointer  */
+           TCF(ftgkys,PSTRING,4,1)
+           TCF(ftgkys,PINT,5,1)     );
+
+   RCF(FITSUNIT,1)
+   RCF(STRING,2)
+   RCF(PSTRING,3)      /*  Copies as much of pointer as will fit   */
+   RCF(PSTRING,4)      /*     into fortran string and frees space  */
+   RCF(PINT,5)
+}
+
+/*   This is the *real* wrapper to FFGKLS, although it is exactly the
+     same as the one for FFGKYS.  To handle the pointer to a pointer,
+     manually expand the FCALLSC macro and modify function call.       */
+
+CFextern VOID_cfF(FTGKLS,ftgkls)
+CFARGT14(NCF,DCF,ABSOFT_cf2(VOID),FITSUNIT,STRING,PSTRING,PSTRING,PINT,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0));
+CFextern VOID_cfF(FTGKLS,ftgkls)
+CFARGT14(NCF,DCF,ABSOFT_cf2(VOID),FITSUNIT,STRING,PSTRING,PSTRING,PINT,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0))
+{
+   QCF(FITSUNIT,1)
+   QCF(STRING,2)
+   QCF(PSTRING,3)   /*  Defines a character pointer  */
+   QCF(PSTRING,4)
+   QCF(PINT,5)
+
+   ffgkls( TCF(ftgkls,FITSUNIT,1,0)
+           TCF(ftgkls,STRING,2,1)
+           , &B3                        /*  Pass address of pointer  */
+           TCF(ftgkls,PSTRING,4,1)
+           TCF(ftgkls,PINT,5,1)     );
+
+   RCF(FITSUNIT,1)
+   RCF(STRING,2)
+   RCF(PSTRING,3)      /*  Copies as much of pointer as will fit   */
+   RCF(PSTRING,4)      /*     into fortran string and frees space  */
+   RCF(PINT,5)
+}
+
+FCALLSCSUB5(ffgkyl,FTGKYL,ftgkyl,FITSUNIT,STRING,PINT,PSTRING,PINT)
+FCALLSCSUB5(ffgkyj,FTGKYJ,ftgkyj,FITSUNIT,STRING,PLONG,PSTRING,PINT)
+FCALLSCSUB5(ffgkyjj,FTGKYK,ftgkyk,FITSUNIT,STRING,PLONGLONG,PSTRING,PINT)
+FCALLSCSUB5(ffgkye,FTGKYE,ftgkye,FITSUNIT,STRING,PFLOAT,PSTRING,PINT)
+FCALLSCSUB5(ffgkyd,FTGKYD,ftgkyd,FITSUNIT,STRING,PDOUBLE,PSTRING,PINT)
+FCALLSCSUB5(ffgkyc,FTGKYC,ftgkyc,FITSUNIT,STRING,PFLOAT,PSTRING,PINT)
+FCALLSCSUB5(ffgkym,FTGKYM,ftgkym,FITSUNIT,STRING,PDOUBLE,PSTRING,PINT)
+FCALLSCSUB6(ffgkyt,FTGKYT,ftgkyt,FITSUNIT,STRING,PLONG,PDOUBLE,PSTRING,PINT)
+
+#define ftgtdm_LONGV_A5 A3
+FCALLSCSUB6(ffgtdm,FTGTDM,ftgtdm,FITSUNIT,INT,INT,PINT,LONGV,PINT)
+
+/*------------------ read array of keywords -----------------*/
+
+     /*    Handle array of strings such that only the number of     */
+     /*    keywords actually found get copied back to the Fortran   */
+     /*    array.  Faster as well as won't cause array overflows    */
+     /*    if the the array is smaller than nkeys, but larger than  */
+     /*    nfound.                                                  */
+
+#define ftgkns_STRV_A5 NUM_ELEM_ARG(4)
+CFextern VOID_cfF(FTGKNS,ftgkns)
+CFARGT14(NCF,DCF,ABSOFT_cf2(VOID),FITSUNIT,STRING,INT,INT,PSTRINGV,PINT,PINT,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0));
+CFextern VOID_cfF(FTGKNS,ftgkns)
+CFARGT14(NCF,DCF,ABSOFT_cf2(VOID),FITSUNIT,STRING,INT,INT,PSTRINGV,PINT,PINT,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0))
+{
+   QCF(FITSUNIT,1)
+   QCF(STRING,2)
+   QCF(INT,3)
+   QCF(INT,4)
+   QCF(PSTRINGV,5)
+   QCF(PINT,6)
+   QCF(PINT,7)
+
+   ffgkns( TCF(ftgkns,FITSUNIT,1,0)
+           TCF(ftgkns,STRING,2,1)
+           TCF(ftgkns,INT,3,1)
+           TCF(ftgkns,INT,4,1)
+           TCF(ftgkns,PSTRINGV,5,1)   /*  Defines the number of strings  */
+                                      /*  in array, B5N                  */
+           TCF(ftgkns,PINT,6,1)
+           TCF(ftgkns,PINT,7,1)     );
+
+   if ( *A7 )       /*  Redefine number of array elements to  */
+      B5N = 0;      /*  number found, or none if error.       */
+   else
+      B5N = *A6;
+		      
+   RCF(FITSUNIT,1)
+   RCF(STRING,2)
+   RCF(INT,3)
+   RCF(INT,4)
+   RCF(PSTRINGV,5)     /*  Copies only found keywords back to Fortran    */
+   RCF(PINT,6)
+   RCF(PINT,7)
+}
+
+/*   Must handle LOGICALV conversion manually... ffgknl uses ints   */
+void Cffgknl( fitsfile *fptr, char *keyroot, int nstart, int nkeys,
+              int *numval, int *nfound, int *status );
+void Cffgknl( fitsfile *fptr, char *keyroot, int nstart, int nkeys,
+              int *numval, int *nfound, int *status )
+{
+   int i;
+ 
+   for( i=0; i<nkeys; i++ )  /*  This preserves array elements across call  */
+      numval[i] = F2CLOGICAL(numval[i]);
+   ffgknl( fptr, keyroot, nstart, nkeys, numval, nfound, status );
+   for( i=0; i<nkeys; i++ )
+      numval[i] = C2FLOGICAL(numval[i]);
+}
+FCALLSCSUB7(Cffgknl,FTGKNL,ftgknl,FITSUNIT,STRING,INT,INT,INTV,PINT,PINT)
+
+#define ftgknj_LONGV_A5 A4
+FCALLSCSUB7(ffgknj,FTGKNJ,ftgknj,FITSUNIT,STRING,INT,INT,LONGV,PINT,PINT)
+
+#define ftgknk_LONGLONGV_A5 A4
+FCALLSCSUB7(ffgknjj,FTGKNK,ftgknk,FITSUNIT,STRING,INT,INT,LONGLONGV,PINT,PINT)
+
+FCALLSCSUB7(ffgkne,FTGKNE,ftgkne,FITSUNIT,STRING,INT,INT,FLOATV,PINT,PINT)
+FCALLSCSUB7(ffgknd,FTGKND,ftgknd,FITSUNIT,STRING,INT,INT,DOUBLEV,PINT,PINT)
+
+/*----------------- read required header keywords --------------*/
+#define ftghpr_LONGV_A6 A2
+FCALLSCSUB10(ffghpr,FTGHPR,ftghpr,FITSUNIT,INT,PLOGICAL,PINT,PINT,LONGV,PLONG,PLONG,PLOGICAL,PINT)
+
+
+    /*  The following 2 routines contain 3 string vector parameters,  */
+    /*  intended to hold column information.  Normally the vectors    */
+    /*  are defined with 500-999 elements, but very rarely do tables  */
+    /*  have that many columns.  So, to prevent the allocation of     */
+    /*  240K of memory to hold all these empty strings and the waste  */
+    /*  of CPU time converting Fortran strings to C, *and* back       */
+    /*  again, get the number of columns in the table and only        */
+    /*  process that many strings (or maxdim, if it is smaller).      */
+
+#define ftghtb_STRV_A6 NUM_ELEMS(maxdim)
+#define ftghtb_STRV_A8 NUM_ELEMS(maxdim)
+#define ftghtb_STRV_A9 NUM_ELEMS(maxdim)
+#define ftghtb_LONGV_A7 A2
+CFextern VOID_cfF(FTGHTB,ftghtb)
+CFARGT14(NCF,DCF,ABSOFT_cf2(VOID),FITSUNIT,INT,PLONG,PLONG,PINT,PSTRINGV,LONGV,PSTRINGV,PSTRINGV,PSTRING,PINT,CF_0,CF_0,CF_0));
+CFextern VOID_cfF(FTGHTB,ftghtb)
+CFARGT14(NCF,DCF,ABSOFT_cf2(VOID),FITSUNIT,INT,PLONG,PLONG,PINT,PSTRINGV,LONGV,PSTRINGV,PSTRINGV,PSTRING,PINT,CF_0,CF_0,CF_0))
+{
+   QCF(FITSUNIT,1)
+   QCF(INT,2)
+   QCF(PLONG,3)
+   QCF(PLONG,4)
+   QCF(PINT,5)
+   QCF(PSTRINGV,6)
+   QCF(LONGV,7)
+   QCF(PSTRINGV,8)
+   QCF(PSTRINGV,9)
+   QCF(PSTRING,10)
+   QCF(PINT,11)
+
+   fitsfile *fptr;
+   long tfields;
+   int maxdim,*status;
+
+   fptr = TCF(ftghtb,FITSUNIT,1,0);
+   status =  TCF(ftghtb,PINT,11,0);
+   maxdim =  TCF(ftghtb,INT,2,0);
+   ffgkyj( fptr, "TFIELDS", &tfields, 0, status );
+   maxdim = (maxdim<0) ? tfields : _cfMIN(tfields,maxdim);
+
+   ffghtb(   fptr, maxdim
+             TCF(ftghtb,PLONG,3,1)
+             TCF(ftghtb,PLONG,4,1)
+             TCF(ftghtb,PINT,5,1)
+             TCF(ftghtb,PSTRINGV,6,1)
+             TCF(ftghtb,LONGV,7,1)
+             TCF(ftghtb,PSTRINGV,8,1)
+             TCF(ftghtb,PSTRINGV,9,1)
+             TCF(ftghtb,PSTRING,10,1)
+             , status );
+
+   RCF(FITSUNIT,1)
+   RCF(INT,2)
+   RCF(PLONG,3)
+   RCF(PLONG,4)
+   RCF(PINT,5)
+   RCF(PSTRINGV,6)
+   RCF(LONGV,7)
+   RCF(PSTRINGV,8)
+   RCF(PSTRINGV,9)
+   RCF(PSTRING,10)
+   RCF(PINT,11)
+}
+
+#define ftghbn_STRV_A5 NUM_ELEMS(maxdim)
+#define ftghbn_STRV_A6 NUM_ELEMS(maxdim)
+#define ftghbn_STRV_A7 NUM_ELEMS(maxdim)
+CFextern VOID_cfF(FTGHBN,ftghbn)
+CFARGT14(NCF,DCF,ABSOFT_cf2(VOID),FITSUNIT,INT,PLONG,PINT,PSTRINGV,PSTRINGV,PSTRINGV,PSTRING,PLONG,PINT,CF_0,CF_0,CF_0,CF_0));
+CFextern VOID_cfF(FTGHBN,ftghbn)
+CFARGT14(NCF,DCF,ABSOFT_cf2(VOID),FITSUNIT,INT,PLONG,PINT,PSTRINGV,PSTRINGV,PSTRINGV,PSTRING,PLONG,PINT,CF_0,CF_0,CF_0,CF_0))
+{
+   QCF(FITSUNIT,1)
+   QCF(INT,2)
+   QCF(PLONG,3)
+   QCF(PINT,4)
+   QCF(PSTRINGV,5)
+   QCF(PSTRINGV,6)
+   QCF(PSTRINGV,7)
+   QCF(PSTRING,8)
+   QCF(PLONG,9)
+   QCF(PINT,10)
+
+   fitsfile *fptr;
+   long tfields;
+   int maxdim,*status;
+
+   fptr = TCF(ftghbn,FITSUNIT,1,0);
+   status =  TCF(ftghbn,PINT,10,0);
+   maxdim =  TCF(ftghbn,INT,2,0);
+   ffgkyj( fptr, "TFIELDS", &tfields, 0, status );
+   maxdim = (maxdim<0) ? tfields : _cfMIN(tfields,maxdim);
+
+   ffghbn(   fptr, maxdim
+             TCF(ftghbn,PLONG,3,1)
+             TCF(ftghbn,PINT,4,1)
+             TCF(ftghbn,PSTRINGV,5,1)
+             TCF(ftghbn,PSTRINGV,6,1)
+             TCF(ftghbn,PSTRINGV,7,1)
+             TCF(ftghbn,PSTRING,8,1)
+             TCF(ftghbn,PLONG,9,1)
+             , status );
+
+   RCF(FITSUNIT,1)
+   RCF(INT,2)
+   RCF(PLONG,3)
+   RCF(PINT,4)
+   RCF(PSTRINGV,5)
+   RCF(PSTRINGV,6)
+   RCF(PSTRINGV,7)
+   RCF(PSTRING,8)
+   RCF(PLONG,9)
+   RCF(PINT,10)
+}
+
+    /*  LONGLONG version of the ftghbn routine: */
+
+#define ftghbnll_STRV_A5 NUM_ELEMS(maxdim)
+#define ftghbnll_STRV_A6 NUM_ELEMS(maxdim)
+#define ftghbnll_STRV_A7 NUM_ELEMS(maxdim)
+CFextern VOID_cfF(FTGHBNLL,ftghbnll)
+CFARGT14(NCF,DCF,ABSOFT_cf2(VOID),FITSUNIT,INT,PLONGLONG,PINT,PSTRINGV,PSTRINGV,PSTRINGV,PSTRING,PLONGLONG,PINT,CF_0,CF_0,CF_0,CF_0));
+CFextern VOID_cfF(FTGHBNLL,ftghbnll)
+CFARGT14(NCF,DCF,ABSOFT_cf2(VOID),FITSUNIT,INT,PLONGLONG,PINT,PSTRINGV,PSTRINGV,PSTRINGV,PSTRING,PLONGLONG,PINT,CF_0,CF_0,CF_0,CF_0))
+{
+   QCF(FITSUNIT,1)
+   QCF(INT,2)
+   QCF(PLONGLONG,3)
+   QCF(PINT,4)
+   QCF(PSTRINGV,5)
+   QCF(PSTRINGV,6)
+   QCF(PSTRINGV,7)
+   QCF(PSTRING,8)
+   QCF(PLONGLONG,9)
+   QCF(PINT,10)
+
+   fitsfile *fptr;
+   LONGLONG tfields;
+   int maxdim,*status;
+
+   fptr = TCF(ftghbnll,FITSUNIT,1,0);
+   status =  TCF(ftghbnll,PINT,10,0);
+   maxdim =  TCF(ftghbnll,INT,2,0);
+   ffgkyjj( fptr, "TFIELDS", &tfields, 0, status );
+   maxdim = (maxdim<0) ? tfields : _cfMIN(tfields,maxdim);
+
+   ffghbnll( fptr, maxdim
+             TCF(ftghbnll,PLONGLONG,3,1)
+             TCF(ftghbnll,PINT,4,1)
+             TCF(ftghbnll,PSTRINGV,5,1)
+             TCF(ftghbnll,PSTRINGV,6,1)
+             TCF(ftghbnll,PSTRINGV,7,1)
+             TCF(ftghbnll,PSTRING,8,1)
+             TCF(ftghbnll,PLONGLONG,9,1)
+             , status );
+
+   RCF(FITSUNIT,1)
+   RCF(INT,2)
+   RCF(PLONGLONG,3)
+   RCF(PINT,4)
+   RCF(PSTRINGV,5)
+   RCF(PSTRINGV,6)
+   RCF(PSTRINGV,7)
+   RCF(PSTRING,8)
+   RCF(PLONGLONG,9)
+   RCF(PINT,10)
+}
+
+    /*   The following 3 routines are obsolete and dangerous to use as       */
+    /*   there is no bounds checking with the arrays.  Call ftghxx instead.  */
+    /*   To get cfortran to work, ftgtbh and ftgbnh require information      */
+    /*   on the array size of the string vectors.  The "TFIELDS" key word    */
+    /*   is read and used as the vector size.  This *will* cause a           */
+    /*   problem if ttype, tform, and tunit are declared with fewer          */
+    /*   elements than the actual number of columns.                         */
+
+#if defined(LONG8BYTES_INT4BYTES)
+
+    /*  On platforms with 8-byte longs, we also need to worry about the */
+    /*  length of the long naxes array.  So read NAXIS manually. :(     */
+
+void Cffgprh( fitsfile *fptr, int *simple, int *bitpix, int *naxis, int naxes[],
+             long *pcount, long *gcount, int *extend, int *status );
+void Cffgprh( fitsfile *fptr, int *simple, int *bitpix, int *naxis, int naxes[],
+             long *pcount, long *gcount, int *extend, int *status )
+{
+   long *LONGnaxes, size;
+
+   ffgkyj( fptr, "NAXIS", &size, 0, status );
+   LONGnaxes = F2Clongv(size,naxes);
+   ffghpr( fptr, (int)size, simple, bitpix, naxis, LONGnaxes,
+           pcount, gcount, extend, status );
+   C2Flongv(size,naxes,LONGnaxes);
+}
+FCALLSCSUB9(Cffgprh,FTGPRH,ftgprh,FITSUNIT,PLOGICAL,PINT,PINT,INTV,PLONG,PLONG,PLOGICAL,PINT)
+
+#else
+
+void Cffgprh( fitsfile *fptr, int *simple, int *bitpix, int *naxis, long naxes[],
+             long *pcount, long *gcount, int *extend, int *status );
+void Cffgprh( fitsfile *fptr, int *simple, int *bitpix, int *naxis, long naxes[],
+             long *pcount, long *gcount, int *extend, int *status )
+{
+   ffghpr( fptr, -1, simple, bitpix, naxis, naxes,
+           pcount, gcount, extend, status );
+}
+#define ftgprh_LONGV_A5 NONE
+FCALLSCSUB9(Cffgprh,FTGPRH,ftgprh,FITSUNIT,PLOGICAL,PINT,PINT,LONGV,PLONG,PLONG,PLOGICAL,PINT)
+
+#endif
+
+#define ftgtbh_STRV_A5 NUM_ELEMS(tfields)
+#define ftgtbh_STRV_A7 NUM_ELEMS(tfields)
+#define ftgtbh_STRV_A8 NUM_ELEMS(tfields)
+CFextern VOID_cfF(FTGTBH,ftgtbh)
+CFARGT14(NCF,DCF,ABSOFT_cf2(VOID),FITSUNIT,PLONG,PLONG,PINT,PSTRINGV,PLONG,PSTRINGV,PSTRINGV,PSTRING,PINT,CF_0,CF_0,CF_0,CF_0));
+CFextern VOID_cfF(FTGTBH,ftgtbh)
+CFARGT14(NCF,DCF,ABSOFT_cf2(VOID),FITSUNIT,PLONG,PLONG,PINT,PSTRINGV,PLONG,PSTRINGV,PSTRINGV,PSTRING,PINT,CF_0,CF_0,CF_0,CF_0))
+{
+   QCF(FITSUNIT,1)
+   QCF(PLONG,2)
+   QCF(PLONG,3)
+   QCF(PINT,4)
+   QCF(PSTRINGV,5)
+   QCF(PLONG,6)
+   QCF(PSTRINGV,7)
+   QCF(PSTRINGV,8)
+   QCF(PSTRING,9)
+   QCF(PINT,10)
+
+   fitsfile *fptr;
+   long tfields;
+   int *status;
+
+   fptr = TCF(ftgtbh,FITSUNIT,1,0);
+   status =  TCF(ftgtbh,PINT,10,0);
+   ffgkyj( fptr, "TFIELDS", &tfields, 0, status );
+
+   ffghtb(   fptr, (int)tfields
+             TCF(ftgtbh,PLONG,2,1)
+             TCF(ftgtbh,PLONG,3,1)
+             TCF(ftgtbh,PINT,4,1)
+             TCF(ftgtbh,PSTRINGV,5,1)
+             TCF(ftgtbh,PLONG,6,1)
+             TCF(ftgtbh,PSTRINGV,7,1)
+             TCF(ftgtbh,PSTRINGV,8,1)
+             TCF(ftgtbh,PSTRING,9,1)
+             , status );
+
+   RCF(FITSUNIT,1)
+   RCF(PLONG,2)
+   RCF(PLONG,3)
+   RCF(PINT,4)
+   RCF(PSTRINGV,5)
+   RCF(PLONG,6)
+   RCF(PSTRINGV,7)
+   RCF(PSTRINGV,8)
+   RCF(PSTRING,9)
+   RCF(PINT,10)
+}
+
+#define ftgbnh_STRV_A4 NUM_ELEMS(tfields)
+#define ftgbnh_STRV_A5 NUM_ELEMS(tfields)
+#define ftgbnh_STRV_A6 NUM_ELEMS(tfields)
+CFextern VOID_cfF(FTGBNH,ftgbnh)
+CFARGT14(NCF,DCF,ABSOFT_cf2(VOID),FITSUNIT,PLONG,PINT,PSTRINGV,PSTRINGV,PSTRINGV,PSTRING,PLONG,PINT,CF_0,CF_0,CF_0,CF_0,CF_0));
+CFextern VOID_cfF(FTGBNH,ftgbnh)
+CFARGT14(NCF,DCF,ABSOFT_cf2(VOID),FITSUNIT,PLONG,PINT,PSTRINGV,PSTRINGV,PSTRINGV,PSTRING,PLONG,PINT,CF_0,CF_0,CF_0,CF_0,CF_0))
+{
+   QCF(FITSUNIT,1)
+   QCF(PLONG,2)
+   QCF(PINT,3)
+   QCF(PSTRINGV,4)
+   QCF(PSTRINGV,5)
+   QCF(PSTRINGV,6)
+   QCF(PSTRING,7)
+   QCF(PLONG,8)
+   QCF(PINT,9)
+
+   fitsfile *fptr;
+   long tfields;
+   int *status;
+
+   fptr = TCF(ftgbnh,FITSUNIT,1,0);
+   status =  TCF(ftgbnh,PINT,9,0);
+   ffgkyj( fptr, "TFIELDS", &tfields, 0, status );
+
+   ffghbn(   fptr, (int)tfields
+             TCF(ftgbnh,PLONG,2,1)
+             TCF(ftgbnh,PINT,3,1)
+             TCF(ftgbnh,PSTRINGV,4,1)
+             TCF(ftgbnh,PSTRINGV,5,1)
+             TCF(ftgbnh,PSTRINGV,6,1)
+             TCF(ftgbnh,PSTRING,7,1)
+             TCF(ftgbnh,PLONG,8,1)
+             , status );
+
+   RCF(FITSUNIT,1)
+   RCF(PLONG,2)
+   RCF(PINT,3)
+   RCF(PSTRINGV,4)
+   RCF(PSTRINGV,5)
+   RCF(PSTRINGV,6)
+   RCF(PSTRING,7)
+   RCF(PLONG,8)
+   RCF(PINT,9)
+}
+
+
+/*--------------------- update keywords ---------------*/
+FCALLSCSUB4(ffucrd,FTUCRD,ftucrd,FITSUNIT,STRING,STRING,PINT)
+FCALLSCSUB4(ffukyu,FTUKYU,ftukyu,FITSUNIT,STRING,STRING,PINT)
+FCALLSCSUB5(ffukys,FTUKYS,ftukys,FITSUNIT,STRING,STRING,STRING,PINT)
+FCALLSCSUB5(ffukls,FTUKLS,ftukls,FITSUNIT,STRING,STRING,STRING,PINT)
+FCALLSCSUB5(ffukyl,FTUKYL,ftukyl,FITSUNIT,STRING,INT,STRING,PINT)
+FCALLSCSUB5(ffukyj,FTUKYJ,ftukyj,FITSUNIT,STRING,LONG,STRING,PINT)
+FCALLSCSUB5(ffukyj,FTUKYK,ftukyk,FITSUNIT,STRING,LONGLONG,STRING,PINT)
+FCALLSCSUB6(ffukyf,FTUKYF,ftukyf,FITSUNIT,STRING,FLOAT,INT,STRING,PINT)
+FCALLSCSUB6(ffukye,FTUKYE,ftukye,FITSUNIT,STRING,FLOAT,INT,STRING,PINT)
+FCALLSCSUB6(ffukyg,FTUKYG,ftukyg,FITSUNIT,STRING,DOUBLE,INT,STRING,PINT)
+FCALLSCSUB6(ffukyd,FTUKYD,ftukyd,FITSUNIT,STRING,DOUBLE,INT,STRING,PINT)
+FCALLSCSUB6(ffukyc,FTUKYC,ftukyc,FITSUNIT,STRING,FLOATV,INT,STRING,PINT)
+FCALLSCSUB6(ffukym,FTUKYM,ftukym,FITSUNIT,STRING,DOUBLEV,INT,STRING,PINT)
+FCALLSCSUB6(ffukfc,FTUKFC,ftukfc,FITSUNIT,STRING,FLOATV,INT,STRING,PINT)
+FCALLSCSUB6(ffukfm,FTUKFM,ftukfm,FITSUNIT,STRING,DOUBLEV,INT,STRING,PINT)
+
+/*--------------------- modify keywords ---------------*/
+FCALLSCSUB4(ffmrec,FTMREC,ftmrec,FITSUNIT,INT,STRING,PINT)
+FCALLSCSUB4(ffmcrd,FTMCRD,ftmcrd,FITSUNIT,STRING,STRING,PINT)
+FCALLSCSUB4(ffmnam,FTMNAM,ftmnam,FITSUNIT,STRING,STRING,PINT)
+FCALLSCSUB4(ffmcom,FTMCOM,ftmcom,FITSUNIT,STRING,STRING,PINT)
+FCALLSCSUB4(ffmkyu,FTMKYU,ftmkyu,FITSUNIT,STRING,STRING,PINT)
+FCALLSCSUB5(ffmkys,FTMKYS,ftmkys,FITSUNIT,STRING,STRING,STRING,PINT)
+FCALLSCSUB5(ffmkls,FTMKLS,ftmkls,FITSUNIT,STRING,STRING,STRING,PINT)
+FCALLSCSUB5(ffmkyl,FTMKYL,ftmkyl,FITSUNIT,STRING,INT,STRING,PINT)
+FCALLSCSUB5(ffmkyj,FTMKYJ,ftmkyj,FITSUNIT,STRING,LONG,STRING,PINT)
+FCALLSCSUB5(ffmkyj,FTMKYK,ftmkyk,FITSUNIT,STRING,LONGLONG,STRING,PINT)
+FCALLSCSUB6(ffmkyf,FTMKYF,ftmkyf,FITSUNIT,STRING,FLOAT,INT,STRING,PINT)
+FCALLSCSUB6(ffmkye,FTMKYE,ftmkye,FITSUNIT,STRING,FLOAT,INT,STRING,PINT)
+FCALLSCSUB6(ffmkyg,FTMKYG,ftmkyg,FITSUNIT,STRING,DOUBLE,INT,STRING,PINT)
+FCALLSCSUB6(ffmkyd,FTMKYD,ftmkyd,FITSUNIT,STRING,DOUBLE,INT,STRING,PINT)
+FCALLSCSUB6(ffmkyc,FTMKYC,ftmkyc,FITSUNIT,STRING,FLOATV,INT,STRING,PINT)
+FCALLSCSUB6(ffmkym,FTMKYM,ftmkym,FITSUNIT,STRING,DOUBLEV,INT,STRING,PINT)
+FCALLSCSUB6(ffmkfc,FTMKFC,ftmkfc,FITSUNIT,STRING,FLOATV,INT,STRING,PINT)
+FCALLSCSUB6(ffmkfm,FTMKFM,ftmkfm,FITSUNIT,STRING,DOUBLEV,INT,STRING,PINT)
+
+/*--------------------- insert keywords ---------------*/
+FCALLSCSUB4(ffirec,FTIREC,ftirec,FITSUNIT,INT,STRING,PINT)
+FCALLSCSUB3(ffikey,FTIKEY,ftkey,FITSUNIT,STRING,PINT)
+FCALLSCSUB4(ffikyu,FTIKYU,ftikyu,FITSUNIT,STRING,STRING,PINT)
+FCALLSCSUB5(ffikys,FTIKYS,ftikys,FITSUNIT,STRING,STRING,STRING,PINT)
+FCALLSCSUB5(ffikls,FTIKLS,ftikls,FITSUNIT,STRING,STRING,STRING,PINT)
+FCALLSCSUB5(ffikyl,FTIKYL,ftikyl,FITSUNIT,STRING,INT,STRING,PINT)
+FCALLSCSUB5(ffikyj,FTIKYJ,ftikyj,FITSUNIT,STRING,LONG,STRING,PINT)
+FCALLSCSUB5(ffikyj,FTIKYK,ftikyk,FITSUNIT,STRING,LONGLONG,STRING,PINT)
+FCALLSCSUB6(ffikyf,FTIKYF,ftikyf,FITSUNIT,STRING,FLOAT,INT,STRING,PINT)
+FCALLSCSUB6(ffikye,FTIKYE,ftikye,FITSUNIT,STRING,FLOAT,INT,STRING,PINT)
+FCALLSCSUB6(ffikyg,FTIKYG,ftikyg,FITSUNIT,STRING,DOUBLE,INT,STRING,PINT)
+FCALLSCSUB6(ffikyd,FTIKYD,ftikyd,FITSUNIT,STRING,DOUBLE,INT,STRING,PINT)
+FCALLSCSUB6(ffikyc,FTIKYC,ftikyc,FITSUNIT,STRING,FLOATV,INT,STRING,PINT)
+FCALLSCSUB6(ffikym,FTIKYM,ftikym,FITSUNIT,STRING,DOUBLEV,INT,STRING,PINT)
+FCALLSCSUB6(ffikfc,FTIKFC,ftikfc,FITSUNIT,STRING,FLOATV,INT,STRING,PINT)
+FCALLSCSUB6(ffikfm,FTIKFM,ftikfm,FITSUNIT,STRING,DOUBLEV,INT,STRING,PINT)
+
+/*--------------------- delete keywords ---------------*/
+FCALLSCSUB3(ffdkey,FTDKEY,ftdkey,FITSUNIT,STRING,PINT)
+FCALLSCSUB3(ffdrec,FTDREC,ftdrec,FITSUNIT,INT,PINT)
+
+/*--------------------- get HDU information -------------*/
+FCALLSCSUB2(ffghdn,FTGHDN,ftghdn,FITSUNIT,PINT)
+FCALLSCSUB3(ffghdt,FTGHDT,ftghdt,FITSUNIT,PINT,PINT)
+
+FCALLSCSUB5(ffghad,FTGHAD,ftghad,FITSUNIT,PLONG,PLONG,PLONG,PINT)
+
+FCALLSCSUB3(ffgidt,FTGIDT,ftgidt,FITSUNIT,PINT,PINT)
+FCALLSCSUB3(ffgiet,FTGIET,ftgiet,FITSUNIT,PINT,PINT)
+FCALLSCSUB3(ffgidm,FTGIDM,ftgidm,FITSUNIT,PINT,PINT)
+
+#define ftgisz_LONGV_A3 A2
+FCALLSCSUB4(ffgisz,FTGISZ,ftgisz,FITSUNIT,INT,LONGV,PINT)
+
+#define ftgiszll_LONGLONGV_A3 A2
+FCALLSCSUB4(ffgiszll,FTGISZLL,ftgiszll,FITSUNIT,INT,LONGLONGV,PINT)
+
+#define ftgipr_LONGV_A5 A2
+FCALLSCSUB6(ffgipr,FTGIPR,ftgipr,FITSUNIT,INT,PINT,PINT,LONGV,PINT)
+
+#define ftgiprll_LONGLONGV_A5 A2
+FCALLSCSUB6(ffgiprll,FTGIPRLL,ftgiprll,FITSUNIT,INT,PINT,PINT,LONGLONGV,PINT)
+
+/*--------------------- HDU operations -------------*/
+FCALLSCSUB4(ffmahd,FTMAHD,ftmahd,FITSUNIT,INT,PINT,PINT)
+FCALLSCSUB4(ffmrhd,FTMRHD,ftmrhd,FITSUNIT,INT,PINT,PINT)
+FCALLSCSUB5(ffmnhd,FTMNHD,ftmnhd,FITSUNIT,INT,STRING,INT,PINT)
+FCALLSCSUB3(ffthdu,FTTHDU,ftthdu,FITSUNIT,PINT,PINT)
+FCALLSCSUB2(ffcrhd,FTCRHD,ftcrhd,FITSUNIT,PINT)
+
+#define ftcrim_LONGV_A4 A3
+FCALLSCSUB5(ffcrim,FTCRIM,ftcrim,FITSUNIT,INT,INT,LONGV,PINT)
+
+#define ftcrtb_STRV_A5 NUM_ELEM_ARG(4)
+#define ftcrtb_STRV_A6 NUM_ELEM_ARG(4)
+#define ftcrtb_STRV_A7 NUM_ELEM_ARG(4)
+FCALLSCSUB9(ffcrtb,FTCRTB,ftcrtb,FITSUNIT,INT,LONG,INT,STRINGV,STRINGV,STRINGV,STRING,PINT)
+
+#define ftiimg_LONGV_A4 A3
+FCALLSCSUB5(ffiimg,FTIIMG,ftiimg,FITSUNIT,INT,INT,LONGV,PINT)
+
+#define ftiimgll_LONGLONGV_A4 A3
+FCALLSCSUB5(ffiimgll,FTIIMGLL,ftiimgll,FITSUNIT,INT,INT,LONGLONGV,PINT)
+
+
+#define ftitab_STRV_A5 NUM_ELEM_ARG(4)
+#define ftitab_LONGV_A6 A4
+#define ftitab_STRV_A7 NUM_ELEM_ARG(4)
+#define ftitab_STRV_A8 NUM_ELEM_ARG(4)
+FCALLSCSUB10(ffitab,FTITAB,ftitab,FITSUNIT,LONG,LONG,INT,STRINGV,LONGV,STRINGV,STRINGV,STRING,PINT)
+
+#define ftitabll_STRV_A5 NUM_ELEM_ARG(4)
+#define ftitabll_LONGV_A6 A4
+#define ftitabll_STRV_A7 NUM_ELEM_ARG(4)
+#define ftitabll_STRV_A8 NUM_ELEM_ARG(4)
+FCALLSCSUB10(ffitab,FTITABLL,ftitabll,FITSUNIT,LONGLONG,LONGLONG,INT,STRINGV,LONGV,STRINGV,STRINGV,STRING,PINT)
+
+#define ftibin_STRV_A4 NUM_ELEM_ARG(3)
+#define ftibin_STRV_A5 NUM_ELEM_ARG(3)
+#define ftibin_STRV_A6 NUM_ELEM_ARG(3)
+FCALLSCSUB9(ffibin,FTIBIN,ftibin,FITSUNIT,LONG,INT,STRINGV,STRINGV,STRINGV,STRING,LONG,PINT)
+
+#define ftibinll_STRV_A4 NUM_ELEM_ARG(3)
+#define ftibinll_STRV_A5 NUM_ELEM_ARG(3)
+#define ftibinll_STRV_A6 NUM_ELEM_ARG(3)
+FCALLSCSUB9(ffibin,FTIBINLL,ftibinll,FITSUNIT,LONGLONG,INT,STRINGV,STRINGV,STRINGV,STRING,LONG,PINT)
+
+#define ftrsim_LONGV_A4 A3
+FCALLSCSUB5(ffrsim,FTRSIM,ftrsim,FITSUNIT,INT,INT,LONGV,PINT)
+FCALLSCSUB3(ffdhdu,FTDHDU,ftdhdu,FITSUNIT,PINT,PINT)
+FCALLSCSUB4(ffcopy,FTCOPY,ftcopy,FITSUNIT,FITSUNIT,INT,PINT)
+FCALLSCSUB6(ffcpfl,FTCPFL,ftcpfl,FITSUNIT,FITSUNIT,INT,INT,INT,PINT)
+FCALLSCSUB3(ffcphd,FTCPHD,ftcphd,FITSUNIT,FITSUNIT,PINT)
+FCALLSCSUB3(ffcpdt,FTCPDT,ftcpdt,FITSUNIT,FITSUNIT,PINT)
+FCALLSCSUB2(ffchfl,FTCHFL,ftchfl,FITSUNIT,PINT)
+FCALLSCSUB2(ffcdfl,FTCDFL,ftcdfl,FITSUNIT,PINT)
+
+FCALLSCSUB6(fits_copy_image2cell,FTIM2CELL,ftim2cell,FITSUNIT,FITSUNIT,STRING,LONG,INT,PINT)
+FCALLSCSUB5(fits_copy_cell2image,FTCELL2IM,ftcell2im,FITSUNIT,FITSUNIT,STRING,LONG,PINT)
+
+FCALLSCSUB2(ffrdef,FTRDEF,ftrdef,FITSUNIT,PINT)
+FCALLSCSUB3(ffhdef,FTHDEF,fthdef,FITSUNIT,INT,PINT)
+FCALLSCSUB3(ffpthp,FTPTHP,ftpthp,FITSUNIT,LONG,PINT)
+
+FCALLSCSUB2(ffpcks,FTPCKS,ftpcks,FITSUNIT,PINT)
+FCALLSCSUB4(ffvcks,FTVCKS,ftvcks,FITSUNIT,PINT,PINT,PINT)
+
+     /*  Checksum changed from double to long  */
+
+void Cffgcks( fitsfile *fptr, double *datasum, double *hdusum, int *status );
+void Cffgcks( fitsfile *fptr, double *datasum, double *hdusum, int *status )
+{
+   unsigned long data, hdu;
+
+   ffgcks( fptr, &data, &hdu, status );
+   *datasum = data;
+   *hdusum  = hdu;
+}
+FCALLSCSUB4(Cffgcks,FTGCKS,ftgcks,FITSUNIT,PDOUBLE,PDOUBLE,PINT)
+
+void Cffcsum( fitsfile *fptr, long nrec, double *dsum, int *status );
+void Cffcsum( fitsfile *fptr, long nrec, double *dsum, int *status )
+{
+   unsigned long sum;
+
+   ffcsum( fptr, nrec, &sum, status );
+   *dsum = sum;
+}
+FCALLSCSUB4(Cffcsum,FTCSUM,ftcsum,FITSUNIT,LONG,PDOUBLE,PINT)
+
+void Cffesum( double dsum, int complm, char *ascii );
+void Cffesum( double dsum, int complm, char *ascii )
+{
+   unsigned long sum=(unsigned long)dsum;
+
+   ffesum( sum, complm, ascii );
+}
+FCALLSCSUB3(Cffesum,FTESUM,ftesum,DOUBLE,LOGICAL,PSTRING)
+
+void Cffdsum( char *ascii, int complm, double *dsum );
+void Cffdsum( char *ascii, int complm, double *dsum )
+{
+   unsigned long sum;
+
+   ffdsum( ascii, complm, &sum );
+   *dsum = sum;
+}
+FCALLSCSUB3(Cffdsum,FTDSUM,ftdsum,PSTRING,LOGICAL,PDOUBLE)
+
+     /*   Name changed, so support both versions   */
+FCALLSCSUB2(ffupck,FTUPCK,ftupck,FITSUNIT,PINT)
+FCALLSCSUB2(ffupck,FTUCKS,ftucks,FITSUNIT,PINT)
+
+/*--------------- define scaling or null values -------------*/
+FCALLSCSUB4(ffpscl,FTPSCL,ftpscl,FITSUNIT,DOUBLE,DOUBLE,PINT)
+FCALLSCSUB3(ffpnul,FTPNUL,ftpnul,FITSUNIT,LONG,PINT)
+FCALLSCSUB3(ffpnul,FTPNULLL,ftpnulll,FITSUNIT,LONGLONG,PINT)
+FCALLSCSUB5(fftscl,FTTSCL,fttscl,FITSUNIT,INT,DOUBLE,DOUBLE,PINT)
+FCALLSCSUB4(fftnul,FTTNUL,fttnul,FITSUNIT,INT,LONG,PINT)
+FCALLSCSUB4(ffsnul,FTSNUL,ftsnul,FITSUNIT,INT,STRING,PINT)
+
+/*--------------------- get column information -------------*/
+FCALLSCSUB5(ffgcno,FTGCNO,ftgcno,FITSUNIT,LOGICAL,STRING,PINT,PINT)
+FCALLSCSUB6(ffgcnn,FTGCNN,ftgcnn,FITSUNIT,LOGICAL,STRING,PSTRING,PINT,PINT)
+FCALLSCSUB3(ffgnrw,FTGNRW,ftgnrw,FITSUNIT,PLONG,PINT)
+FCALLSCSUB3(ffgnrwll,FTGNRWLL,ftgnrwll,FITSUNIT,PLONGLONG,PINT)
+FCALLSCSUB3(ffgncl,FTGNCL,ftgncl,FITSUNIT,PINT,PINT)
+FCALLSCSUB4(ffgcdw,FTGCDW,ftgcdw,FITSUNIT,INT,PINT,PINT)
+
+FCALLSCSUB6(ffgtcl,FTGTCL,ftgtcl,FITSUNIT,INT,PINT,PLONG,PLONG,PINT)
+FCALLSCSUB6(ffeqty,FTEQTY,fteqty,FITSUNIT,INT,PINT,PLONG,PLONG,PINT)
+FCALLSCSUB11(ffgacl,FTGACL,ftgacl,FITSUNIT,INT,PSTRING,PLONG,PSTRING,PSTRING,PDOUBLE,PDOUBLE,PSTRING,PSTRING,PINT)
+FCALLSCSUB11(ffgbcl,FTGBCL,ftgbcl,FITSUNIT,INT,PSTRING,PSTRING,PSTRING,PLONG,PDOUBLE,PDOUBLE,PLONG,PSTRING,PINT)
+FCALLSCSUB3(ffgrsz,FTGRSZ,ftgrsz,FITSUNIT,PLONG,PINT)
+
+
diff --git a/src/plugins/cfitsio/f77_wrap4.c b/src/plugins/cfitsio/f77_wrap4.c
new file mode 100644
index 0000000..92668c7
--- /dev/null
+++ b/src/plugins/cfitsio/f77_wrap4.c
@@ -0,0 +1,572 @@
+/************************************************************************
+
+     f77_wrap1.c and f77_wrap2.c have now been split into 4 files to
+     prevent compile-time memory errors (from expansion of compiler commands).
+     f77_wrap1.c was split into f77_wrap1.c and f77_wrap3.c, and
+     f77_wrap2.c was split into f77_wrap2.c and f77_wrap4.c:
+       
+       f77_wrap1.c contains routines operating on whole files and some 
+       utility routines.
+     
+       f77_wrap2.c contains routines operating on primary array, image, 
+       or column elements.
+
+       f77_wrap3.c contains routines operating on headers & keywords.
+
+       f77_wrap4.c contains miscellaneous routines.
+
+     Peter's original comments:
+
+     Together, f77_wrap1.c and f77_wrap2.c contain C wrappers for all
+     the CFITSIO routines prototyped in fitsio.h, except for the
+     generic datatype routines and features not supported in fortran
+     (eg, unsigned integers), a few routines prototyped in fitsio2.h,
+     which only a handful of FTOOLS use, plus a few obsolete FITSIO
+     routines not present in CFITSIO.  This file allows Fortran code
+     to use the CFITSIO library instead of the FITSIO library without
+     modification.  It also gives access to new routines not present
+     in FITSIO.  Fortran FTOOLS must continue using the old routine
+     names from FITSIO (ie, ftxxxx), but most of the C-wrappers simply
+     redirect those calls to the corresponding CFITSIO routines (ie,
+     ffxxxx), with appropriate parameter massaging where necessary.
+     The main exception are read/write routines ending in j (ie, long
+     data) which get redirected to C routines ending in k (ie, int
+     data). This is more consistent with the default integer type in
+     Fortran. f77_wrap1.c primarily holds routines operating on whole
+     files and extension headers.  f77_wrap2.c handle routines which
+     read and write the data portion, plus miscellaneous extra routines.
+     
+        File created by Peter Wilson (HSTX), Oct-Dec. 1997
+************************************************************************/
+
+#include "fitsio2.h"
+#include "f77_wrap.h"
+
+/*********************************************************************/
+/*                     Iterator Functions                            */
+/*********************************************************************/
+
+/* Use a simple ellipse prototype for Fwork_fn to satisfy finicky compilers */
+typedef struct {
+   void *userData;
+   void (*Fwork_fn)(PLONG_cfTYPE *total_n, ...);
+} FtnUserData;
+
+/*        Declare protoypes to make C++ happy       */
+int Cwork_fn(long, long, long, long, int, iteratorCol *, void *);
+void Cffiter( int n_cols, int *units, int *colnum, char *colname[], 
+	      int *datatype, int *iotype,
+              long offset, long n_per_loop, void *Fwork_fn,
+	      void *userData, int *status);
+
+/******************************************************************/
+/*  Cffiter is the wrapper for CFITSIO's ffiter which takes most  */
+/*  of its arguments via a structure, iteratorCol.  This routine  */
+/*  takes a list of arrays and converts them into a single array  */
+/*  of type iteratorCol and passes it to CFITSIO.  Because ffiter */
+/*  will be passing control to a Fortran work function, the C     */
+/*  wrapper, Cwork_fn, must be passed in its place which then     */
+/*  calls the Fortran routine after the necessary data            */
+/*  manipulation.  The Fortran routine is passed via the user-    */
+/*  supplied parameter pointer.                                   */
+/******************************************************************/
+
+void Cffiter( int n_cols, int *units, int *colnum, char *colname[], 
+	      int *datatype, int *iotype,
+              long offset, long n_per_loop, void *Fwork_fn,
+	      void *userData, int *status)
+{
+   iteratorCol *cols;
+   int i;
+   FtnUserData FuserData;
+
+   FuserData.Fwork_fn = (void(*)(PLONG_cfTYPE *,...))Fwork_fn;
+   FuserData.userData = userData;
+
+   cols = (iteratorCol *)malloc( n_cols*sizeof(iteratorCol) );
+   if( cols==NULL ) {
+      *status = MEMORY_ALLOCATION;
+      return;
+   }
+   for(i=0;i<n_cols;i++) {
+      cols[i].fptr     = gFitsFiles[ units[i] ];
+      cols[i].colnum   = colnum[i];
+      strncpy(cols[i].colname,colname[i],70);
+      cols[i].datatype = datatype[i];
+      cols[i].iotype   = iotype[i];
+   }
+
+   ffiter( n_cols, cols, offset, n_per_loop, Cwork_fn, 
+	   (void*)&FuserData, status );
+   free(cols);
+}
+#define ftiter_STRV_A4 NUM_ELEM_ARG(1)
+FCALLSCSUB11(Cffiter,FTITER,ftiter,INT,INTV,INTV,STRINGV,INTV,INTV,LONG,LONG,PVOID,PVOID,PINT)
+
+/*-----------------------------------------------------------------*/
+/*  This function is called by CFITSIO's ffiter and serves as the  */
+/*  wrapper for the Fortran work function which is passed in the   */
+/*  extra user-supplied pointer.  It breaks up C's iteratorCol     */
+/*  into several separate arrays.  Because we cannot send an       */
+/*  array of pointers for the column data, we instead send *many*  */
+/*  arrays as final parameters.                                    */
+/*-----------------------------------------------------------------*/
+
+int Cwork_fn( long total_n, long offset,       long first_n,    long n_values,
+	      int n_cols,   iteratorCol *cols, void *FuserData )
+{
+   int  *units, *colnum, *datatype, *iotype, *repeat;
+   char **sptr;
+   void **ptrs;
+   int  i,j,k,nstr,status=0;
+   long *slen;
+
+#ifdef vmsFortran
+   /*  Passing strings under VMS require a special structure  */
+   fstringvector *vmsStrs;
+#endif
+
+   /*  Allocate memory for all the arrays.  Grab all the int's  */
+   /*  at once and divide up among parameters                   */
+
+   ptrs  = (void**)malloc(2*n_cols*sizeof(void*));
+   if( ptrs==NULL )
+      return( MEMORY_ALLOCATION );
+   units = (int*)malloc(5*n_cols*sizeof(int));
+   if( units==NULL ) {
+      free(ptrs);
+      return( MEMORY_ALLOCATION );
+   }
+   colnum   = units + 1 * n_cols;
+   datatype = units + 2 * n_cols;
+   iotype   = units + 3 * n_cols;
+   repeat   = units + 4 * n_cols;
+
+   nstr = 0;
+   slen = (long*)(ptrs+n_cols);
+#ifdef vmsFortran
+   vmsStrs = (fstringvector *)calloc(sizeof(fstringvector),n_cols);
+   if( vmsStrs==NULL ) {
+      free(ptrs);
+      free(units);
+      return( MEMORY_ALLOCATION );
+   }
+#endif
+
+   for(i=0;i<n_cols;i++) {
+      for(j=0;j<MAXFITSFILES;j++)
+	 if( cols[i].fptr==gFitsFiles[j] )
+	    units[i] = j;
+      colnum[i]   = cols[i].colnum;
+      datatype[i] = cols[i].datatype;
+      iotype[i]   = cols[i].iotype;
+      repeat[i]   = cols[i].repeat;
+
+      if( datatype[i]==TLOGICAL ) {
+	 /*  Don't forget first element is null value  */
+	 ptrs[i] = (void *)malloc( (n_values*repeat[i]+1)*4 );
+	 if( ptrs[i]==NULL ) {
+	    free(ptrs);
+	    free(units);
+	    return( MEMORY_ALLOCATION );
+	 }
+	 for( j=0;j<=n_values*repeat[i]; j++ )
+	    ((int*)ptrs[i])[j] = C2FLOGICAL( ((char*)cols[i].array)[j]);
+      } else if ( datatype[i]==TSTRING ) {
+	 sptr = (char**)cols[i].array;
+	 slen[nstr] = sptr[1] - sptr[0];
+	 for(j=0;j<=n_values;j++)
+	    for(k=strlen( sptr[j] );k<slen[nstr];k++)
+	       sptr[j][k] = ' ';
+#ifdef vmsFortran
+	 vmsStrs[nstr].dsc$a_pointer         = sptr[0];
+	 vmsStrs[nstr].dsc$w_length          = slen[nstr];
+	 vmsStrs[nstr].dsc$l_m[0]            = n_values+1;
+	 vmsStrs[nstr].dsc$l_arsize          = slen[nstr] * (n_values+1);
+	 vmsStrs[nstr].dsc$bounds[0].dsc$l_u = n_values+1;
+	 vmsStrs[nstr].dsc$a_a0              = sptr[0] - slen[nstr];
+	 ptrs[i] = (void *)(vmsStrs+nstr);
+#else
+	 ptrs[i] = (void *)sptr[0];
+#endif
+	 nstr++;
+      } else
+	 ptrs[i] = (void *)cols[i].array;
+   }
+
+   if(!status) {
+              /*  Handle Fortran function call manually...  */
+	      /*  cfortran.h cannot handle all the desired  */
+              /*  'ptrs' nor the indirect function call.    */
+
+      PLONG_cfTYPE a1,a2,a3,a4;    /* Do this in case longs are */ 
+      FtnUserData *f;              /* not the same size as ints */
+
+      a1 = total_n;
+      a2 = offset;
+      a3 = first_n;
+      a4 = n_values;
+      f  = (FtnUserData *)FuserData;
+
+      f->Fwork_fn(&a1,&a2,&a3,&a4,&n_cols,units,colnum,datatype,
+		  iotype,repeat,&status,f->userData,
+		  ptrs[ 0], ptrs[ 1], ptrs[ 2], ptrs[ 3], ptrs[ 4],
+		  ptrs[ 5], ptrs[ 6], ptrs[ 7], ptrs[ 8], ptrs[ 9],
+		  ptrs[10], ptrs[11], ptrs[12], ptrs[13], ptrs[14],
+		  ptrs[15], ptrs[16], ptrs[17], ptrs[18], ptrs[19],
+		  ptrs[20], ptrs[21], ptrs[22], ptrs[23], ptrs[24] );
+   }
+
+   /*  Check whether there are any LOGICAL or STRING columns being outputted  */
+   nstr=0;
+   for( i=0;i<n_cols;i++ ) {
+      if( iotype[i]!=InputCol ) {
+	 if( datatype[i]==TLOGICAL ) {
+	    for( j=0;j<=n_values*repeat[i];j++ )
+	       ((char*)cols[i].array)[j] = F2CLOGICAL( ((int*)ptrs[i])[j] );
+	    free(ptrs[i]);
+	 } else if( datatype[i]==TSTRING ) {
+	    for( j=0;j<=n_values;j++ )
+	       ((char**)cols[i].array)[j][slen[nstr]-1] = '\0';
+	 }
+      }
+      if( datatype[i]==TSTRING ) nstr++;
+   }
+
+   free(ptrs);
+   free(units);
+#ifdef vmsFortran
+   free(vmsStrs);
+#endif
+   return(status);
+}
+
+
+/*--------------------- WCS Utilities ----------------------------*/
+FCALLSCSUB10(ffgics, FTGICS, ftgics, FITSUNIT,     PDOUBLE,PDOUBLE,PDOUBLE,PDOUBLE,PDOUBLE,PDOUBLE,PDOUBLE,PSTRING,PINT)
+FCALLSCSUB11(ffgicsa,FTGICSA,ftgicsa,FITSUNIT,BYTE,PDOUBLE,PDOUBLE,PDOUBLE,PDOUBLE,PDOUBLE,PDOUBLE,PDOUBLE,PSTRING,PINT)
+FCALLSCSUB12(ffgtcs,FTGTCS,ftgtcs,FITSUNIT,INT,INT,PDOUBLE,PDOUBLE,PDOUBLE,PDOUBLE,PDOUBLE,PDOUBLE,PDOUBLE,PSTRING,PINT)
+FCALLSCSUB13(ffwldp,FTWLDP,ftwldp,DOUBLE,DOUBLE,DOUBLE,DOUBLE,DOUBLE,DOUBLE,DOUBLE,DOUBLE,DOUBLE,STRING,PDOUBLE,PDOUBLE,PINT)
+FCALLSCSUB13(ffxypx,FTXYPX,ftxypx,DOUBLE,DOUBLE,DOUBLE,DOUBLE,DOUBLE,DOUBLE,DOUBLE,DOUBLE,DOUBLE,STRING,PDOUBLE,PDOUBLE,PINT)
+
+/*------------------- Conversion Utilities -----------------*/
+/*                 (prototyped in fitsio2.h)                */
+/*----------------------------------------------------------*/
+
+CFextern VOID_cfF(FTI2C,fti2c)
+CFARGT14(NCF,DCF,ABSOFT_cf2(VOID),LONG,PSTRING,PINT,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0));
+CFextern VOID_cfF(FTI2C,fti2c)
+CFARGT14(NCF,DCF,ABSOFT_cf2(VOID),LONG,PSTRING,PINT,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0))
+{
+   QCF(LONG,1)
+   QCF(PSTRING,2)
+   QCF(PINT,3)
+   char str[21];
+
+   ffi2c( TCF(fti2c,LONG,1,0) 
+          TCF(fti2c,PSTRING,2,1) 
+          TCF(fti2c,PINT,3,1) );
+
+   sprintf(str,"%20s",B2);
+   strcpy(B2,str);
+
+   RCF(LONG,1)
+   RCF(PSTRING,2)
+   RCF(PINT,3)
+}
+
+CFextern VOID_cfF(FTL2C,ftl2c)
+CFARGT14(NCF,DCF,ABSOFT_cf2(VOID),LOGICAL,PSTRING,PINT,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0));
+CFextern VOID_cfF(FTL2C,ftl2c)
+CFARGT14(NCF,DCF,ABSOFT_cf2(VOID),LOGICAL,PSTRING,PINT,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0))
+{
+   QCF(LOGICAL,1)
+   QCF(PSTRING,2)
+   QCF(PINT,3)
+   char str[21];
+
+   ffl2c( TCF(ftl2c,LOGICAL,1,0) 
+          TCF(ftl2c,PSTRING,2,1) 
+          TCF(ftl2c,PINT,3,1) );
+
+   sprintf(str,"%20s",B2);
+   strcpy(B2,str);
+
+   RCF(LOGICAL,1)
+   RCF(PSTRING,2)
+   RCF(PINT,3)
+}
+
+FCALLSCSUB3(ffs2c,FTS2C,fts2c,STRING,PSTRING,PINT)
+
+CFextern VOID_cfF(FTR2F,ftr2f)
+CFARGT14(NCF,DCF,ABSOFT_cf2(VOID),FLOAT,INT,PSTRING,PINT,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0));
+CFextern VOID_cfF(FTR2F,ftr2f)
+CFARGT14(NCF,DCF,ABSOFT_cf2(VOID),FLOAT,INT,PSTRING,PINT,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0))
+{
+   QCF(FLOAT,1)
+   QCF(INT,2)
+   QCF(PSTRING,3)
+   QCF(PINT,4)
+   char str[21];
+
+   ffr2f( TCF(ftr2f,FLOAT,1,0) 
+          TCF(ftr2f,INT,2,1) 
+          TCF(ftr2f,PSTRING,3,1) 
+          TCF(ftr2f,PINT,4,1) );
+
+   sprintf(str,"%20s",B3);
+   strcpy(B3,str);
+
+   RCF(FLOAT,1)
+   RCF(INT,2)
+   RCF(PSTRING,3)
+   RCF(PINT,4)
+}
+
+CFextern VOID_cfF(FTR2E,ftr2e)
+CFARGT14(NCF,DCF,ABSOFT_cf2(VOID),FLOAT,INT,PSTRING,PINT,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0));
+CFextern VOID_cfF(FTR2E,ftr2e)
+CFARGT14(NCF,DCF,ABSOFT_cf2(VOID),FLOAT,INT,PSTRING,PINT,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0))
+{
+   QCF(FLOAT,1)
+   QCF(INT,2)
+   QCF(PSTRING,3)
+   QCF(PINT,4)
+   char str[21];
+
+   ffr2e( TCF(ftr2e,FLOAT,1,0) 
+          TCF(ftr2e,INT,2,1) 
+          TCF(ftr2e,PSTRING,3,1) 
+          TCF(ftr2e,PINT,4,1) );
+
+   sprintf(str,"%20s",B3);
+   strcpy(B3,str);
+
+   RCF(FLOAT,1)
+   RCF(INT,2)
+   RCF(PSTRING,3)
+   RCF(PINT,4)
+}
+
+CFextern VOID_cfF(FTD2F,ftd2f)
+CFARGT14(NCF,DCF,ABSOFT_cf2(VOID),DOUBLE,INT,PSTRING,PINT,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0));
+CFextern VOID_cfF(FTD2F,ftd2f)
+CFARGT14(NCF,DCF,ABSOFT_cf2(VOID),DOUBLE,INT,PSTRING,PINT,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0))
+{
+   QCF(DOUBLE,1)
+   QCF(INT,2)
+   QCF(PSTRING,3)
+   QCF(PINT,4)
+   char str[21];
+
+   ffd2f( TCF(ftd2f,DOUBLE,1,0) 
+          TCF(ftd2f,INT,2,1) 
+          TCF(ftd2f,PSTRING,3,1) 
+          TCF(ftd2f,PINT,4,1) );
+
+   sprintf(str,"%20s",B3);
+   strcpy(B3,str);
+
+   RCF(DOUBLE,1)
+   RCF(INT,2)
+   RCF(PSTRING,3)
+   RCF(PINT,4)
+}
+
+CFextern VOID_cfF(FTD2E,ftd2e)
+CFARGT14(NCF,DCF,ABSOFT_cf2(VOID),DOUBLE,INT,PSTRING,PINT,PINT,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0));
+CFextern VOID_cfF(FTD2E,ftd2e)
+CFARGT14(NCF,DCF,ABSOFT_cf2(VOID),DOUBLE,INT,PSTRING,PINT,PINT,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0,CF_0))
+{
+   QCF(DOUBLE,1)
+   QCF(INT,2)
+   QCF(PSTRING,3)
+   QCF(PINT,4)
+   QCF(PINT,5)
+   char str[21];
+   int *vlen;
+
+   vlen = TCF(ftd2e,PINT,4,0);
+
+   /*  C version of routine doesn't use the 4th parameter, vlen  */
+   ffd2e( TCF(ftd2e,DOUBLE,1,0) 
+          TCF(ftd2e,INT,2,1) 
+          TCF(ftd2e,PSTRING,3,1) 
+          TCF(ftd2e,PINT,5,1) );
+
+   *vlen = strlen(B3);
+   if ( *vlen<20 ) {
+      sprintf(str,"%20s",B3);  /* right justify if vlen<20 characters */
+      strcpy(B3,str);
+      *vlen = 20;
+   }
+
+   RCF(DOUBLE,1)
+   RCF(INT,2)
+   RCF(PSTRING,3)
+   RCF(PINT,4)
+   RCF(PINT,5)
+}
+
+FCALLSCSUB3(ffc2ii,FTC2II,ftc2ii,STRING,PLONG,PINT)
+FCALLSCSUB3(ffc2ll,FTC2LL,ftc2ll,STRING,PINT,PINT)
+FCALLSCSUB3(ffc2rr,FTC2RR,ftc2rr,STRING,PFLOAT,PINT)
+FCALLSCSUB3(ffc2dd,FTC2DD,ftc2dd,STRING,PDOUBLE,PINT)
+FCALLSCSUB7(ffc2x,FTC2X,ftc2x,STRING,PSTRING,PLONG,PINT,PSTRING,PDOUBLE,PINT)
+FCALLSCSUB3(ffc2s,FTC2S,ftc2s,STRING,PSTRING,PINT)
+FCALLSCSUB3(ffc2i,FTC2I,ftc2i,STRING,PLONG,PINT)
+FCALLSCSUB3(ffc2r,FTC2R,ftc2r,STRING,PFLOAT,PINT)
+FCALLSCSUB3(ffc2d,FTC2D,ftc2d,STRING,PDOUBLE,PINT)
+FCALLSCSUB3(ffc2l,FTC2L,ftc2l,STRING,PINT,PINT)
+
+/*------------------ Byte-level read/seek/write -----------------*/
+/*                   (prototyped in fitsio2.h)                   */
+/*---------------------------------------------------------------*/
+
+/*
+  ffmbyt should not be called by any application programs, so
+  the wrapper should not need to be defined.  If it is needed then
+  the second parameter (LONG) will need to be changed to the
+  equivalent of the C 'off_t' type, which may be 32 or 64 bits long
+  depending on the compiler.
+  -W.Pence (7/21/00)
+  
+FCALLSCSUB4(ffmbyt,FTMBYT,ftmbyt,FITSUNIT,LONG,LOGICAL,PINT)
+*/
+
+FCALLSCSUB4(ffgbyt,FTGCBF,ftgcbf,FITSUNIT,LONG,PVOID,PINT)
+FCALLSCSUB4(ffgbyt,FTGBYT,ftgbyt,FITSUNIT,LONG,PVOID,PINT)
+
+FCALLSCSUB4(ffpbyt,FTPCBF,ftpcbf,FITSUNIT,LONG,PVOID,PINT)
+FCALLSCSUB4(ffpbyt,FTPBYT,ftpbyt,FITSUNIT,LONG,PVOID,PINT)
+
+
+/*-------------- Additional missing FITSIO routines -------------*/
+/*                   (abandoned in CFITSIO)                      */
+/*---------------------------------------------------------------*/
+
+void Cffcrep( char *comm, char *comm1, int *repeat );
+void Cffcrep( char *comm, char *comm1, int *repeat )
+{
+/*
+   check if the first comment string is to be repeated for all keywords
+   (if the last non-blank character is '&', then it is to be repeated)
+
+   comm    input comment string
+   OUTPUT PARAMETERS:
+   comm1   output comment string, = COMM minus the last '&' character
+   repeat  TRUE if the last character of COMM was the '&' character
+
+   written by Wm Pence, HEASARC/GSFC, June 1991
+   translated to C by Peter Wilson, HSTX/GSFC, Oct 1997
+*/
+
+   int len;
+
+   *repeat=FALSE;
+   len=strlen(comm);
+       /* cfortran strips trailing spaces so only check last character  */
+   if( len && comm[ len-1 ]=='&' ) {
+      strncpy(comm1,comm,len-1);  /*  Don't copy '&'  */
+      comm1[len-1]='\0';
+      *repeat=TRUE;
+   }
+   return;
+}
+FCALLSCSUB3(Cffcrep,FTCREP,ftcrep,STRING,PSTRING,PLOGICAL)
+
+
+/*------------------ Test floats for NAN values -----------------*/
+/*                     (defined in fitsio2.h)                    */
+/*---------------------------------------------------------------*/
+
+int Cfnan( float *val );
+int Cfnan( float *val )
+{
+   int code;
+
+#if BYTESWAPPED
+   short *sptr = (short*)val + 1;
+#else
+   short *sptr = (short*)val;
+#endif
+
+   code = fnan(*sptr);
+   if( code==2 ) *val = 0.0;   /* Underflow */
+
+   return( code!=0 );
+}
+FCALLSCFUN1(LOGICAL,Cfnan,FTTRNN,fttrnn,PFLOAT)
+
+
+int Cdnan( double *val );
+int Cdnan( double *val )
+{
+   int code;
+
+#if BYTESWAPPED
+   short *sptr = (short*)val + 3;
+#else
+   short *sptr = (short*)val;
+#endif
+
+   code = dnan(*sptr);
+   if( code==2 ) *val = 0.0;   /* Underflow */
+
+   return( code!=0 );
+}
+FCALLSCFUN1(LOGICAL,Cdnan,FTTDNN,fttdnn,PDOUBLE)
+
+/*-------- Functions no longer supported... normally redundant -----------*/
+/*                Included only to support older code                     */
+/*------------------------------------------------------------------------*/
+
+void Cffempty(void);
+void Cffempty(void)
+{ return; }
+FCALLSCSUB0(Cffempty,FTPDEF,ftpdef)
+FCALLSCSUB0(Cffempty,FTBDEF,ftbdef)
+FCALLSCSUB0(Cffempty,FTADEF,ftadef)
+FCALLSCSUB0(Cffempty,FTDDEF,ftddef)
+
+
+/*-------- Functions which use the lex and yacc/bison parser code -----------*/
+/*---------------------------------------------------------------------------*/
+
+#define fttexp_LONGV_A7 A3
+FCALLSCSUB8(fftexp,FTTEXP,fttexp,FITSUNIT,STRING,INT,PINT,PLONG,PINT,LONGV,PINT)
+
+#define ftfrow_LOGV_A6 A4
+FCALLSCSUB7(fffrow,FTFROW,ftfrow,FITSUNIT,STRING,LONG,LONG,PLONG,LOGICALV,PINT)
+
+#define ftfrwc_LOGV_A8 A6
+FCALLSCSUB9(fffrwc,FTFRWC,ftfrwc,FITSUNIT,STRING,STRING,STRING,STRING,LONG,DOUBLEV,LOGICALV,PINT)
+FCALLSCSUB4(ffffrw,FTFFRW,ftffrw,FITSUNIT,STRING,PLONG,PINT)
+
+FCALLSCSUB4(ffsrow,FTSROW,ftsrow,FITSUNIT,FITSUNIT,STRING,PINT)
+FCALLSCSUB9(ffcrow,FTCROW,ftcrow,FITSUNIT,INT,STRING,LONG,LONG,PVOID,PVOID,PLOGICAL,PINT)
+FCALLSCSUB6(ffcalc,FTCALC,ftcalc,FITSUNIT,STRING,FITSUNIT,STRING,STRING,PINT)
+
+#define ftcalc_rng_LONGV_A7 A6
+#define ftcalc_rng_LONGV_A8 A6
+FCALLSCSUB9(ffcalc_rng,FTCALC_RNG,ftcalc_rng,FITSUNIT,STRING,FITSUNIT,STRING,STRING,INT,LONGV,LONGV,PINT)
+
+/*--------------------- grouping routines ------------------*/
+
+FCALLSCSUB4(ffgtcr,FTGTCR,ftgtcr,FITSUNIT,STRING,INT,PINT)
+FCALLSCSUB4(ffgtis,FTGTIS,ftgtis,FITSUNIT,STRING,INT,PINT)
+FCALLSCSUB3(ffgtch,FTGTCH,ftgtch,FITSUNIT,INT,PINT)
+FCALLSCSUB3(ffgtrm,FTGTRM,ftgtrm,FITSUNIT,INT,PINT)
+FCALLSCSUB4(ffgtcp,FTGTCP,ftgtcp,FITSUNIT,FITSUNIT,INT,PINT)
+FCALLSCSUB4(ffgtmg,FTGTMG,ftgtmg,FITSUNIT,FITSUNIT,INT,PINT)
+FCALLSCSUB3(ffgtcm,FTGTCM,ftgtcm,FITSUNIT,INT,PINT)
+FCALLSCSUB3(ffgtvf,FTGTVF,ftgtvf,FITSUNIT,PLONG,PINT)
+FCALLSCSUB4(ffgtop,FTGTOP,ftgtop,FITSUNIT,INT,PFITSUNIT,PINT)
+FCALLSCSUB4(ffgtam,FTGTAM,ftgtam,FITSUNIT,FITSUNIT,INT,PINT)
+FCALLSCSUB3(ffgtnm,FTGTNM,ftgtnm,FITSUNIT,PLONG,PINT)
+FCALLSCSUB3(ffgmng,FTGMNG,ftgmng,FITSUNIT,PLONG,PINT)
+FCALLSCSUB4(ffgmop,FTGMOP,ftgmop,FITSUNIT,LONG,PFITSUNIT,PINT)
+FCALLSCSUB5(ffgmcp,FTGMCP,ftgmcp,FITSUNIT,FITSUNIT,LONG,INT,PINT)
+FCALLSCSUB5(ffgmtf,FTGMTF,ftgmtf,FITSUNIT,FITSUNIT,LONG,INT,PINT)
+FCALLSCSUB4(ffgmrm,FTGMRM,ftgmrm,FITSUNIT,LONG,INT,PINT)
diff --git a/src/plugins/cfitsio/fits_hcompress.c b/src/plugins/cfitsio/fits_hcompress.c
new file mode 100644
index 0000000..96a6b12
--- /dev/null
+++ b/src/plugins/cfitsio/fits_hcompress.c
@@ -0,0 +1,1858 @@
+/*  #########################################################################
+These routines to apply the H-compress compression algorithm to a 2-D Fits
+image were written by R. White at the STScI and were obtained from the STScI at
+http://www.stsci.edu/software/hcompress.html
+
+This source file is a concatination of the following sources files in the
+original distribution 
+ htrans.c 
+ digitize.c 
+ encode.c 
+ qwrite.c 
+ doencode.c 
+ bit_output.c 
+ qtree_encode.c
+
+The following modifications have been made to the original code:
+
+  - commented out redundant "include" statements
+  - added the noutchar global variable 
+  - changed all the 'extern' declarations to 'static', since all the routines are in
+    the same source file
+  - changed the first parameter in encode (and in lower level routines from a file stream
+    to a char array
+  - modifid the encode routine to return the size of the compressed array of bytes
+  - changed calls to printf and perror to call the CFITSIO ffpmsg routine
+  - modified the mywrite routine, and lower level byte writing routines,  to copy 
+    the output bytes to a char array, instead of writing them to a file stream
+  - replace "exit" statements with "return" statements
+  - changed the function declarations to the more modern ANSI C style
+
+ ############################################################################  */
+ 
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <stdlib.h>
+#include "fitsio2.h"
+
+static long noutchar;
+static long noutmax;
+
+static int htrans(int a[],int nx,int ny);
+static void digitize(int a[], int nx, int ny, int scale);
+static int encode(char *outfile, long *nlen, int a[], int nx, int ny, int scale);
+static void shuffle(int a[], int n, int n2, int tmp[]);
+
+static int htrans64(LONGLONG a[],int nx,int ny);
+static void digitize64(LONGLONG a[], int nx, int ny, int scale);
+static int encode64(char *outfile, long *nlen, LONGLONG a[], int nx, int ny, int scale);
+static void shuffle64(LONGLONG a[], int n, int n2, LONGLONG tmp[]);
+
+static  void writeint(char *outfile, int a);
+static  void writelonglong(char *outfile, LONGLONG a);
+static  int doencode(char *outfile, int a[], int nx, int ny, unsigned char nbitplanes[3]);
+static  int doencode64(char *outfile, LONGLONG a[], int nx, int ny, unsigned char nbitplanes[3]);
+static int  qwrite(char *file, char buffer[], int n);
+
+static int qtree_encode(char *outfile, int a[], int n, int nqx, int nqy, int nbitplanes);
+static int qtree_encode64(char *outfile, LONGLONG a[], int n, int nqx, int nqy, int nbitplanes);
+static void start_outputing_bits(void);
+static void done_outputing_bits(char *outfile);
+static void output_nbits(char *outfile, int bits, int n);
+
+static void qtree_onebit(int a[], int n, int nx, int ny, unsigned char b[], int bit);
+static void qtree_onebit64(LONGLONG a[], int n, int nx, int ny, unsigned char b[], int bit);
+static void qtree_reduce(unsigned char a[], int n, int nx, int ny, unsigned char b[]);
+static int  bufcopy(unsigned char a[], int n, unsigned char buffer[], int *b, int bmax);
+static void write_bdirect(char *outfile, int a[], int n,int nqx, int nqy, unsigned char scratch[], int bit);
+static void write_bdirect64(char *outfile, LONGLONG a[], int n,int nqx, int nqy, unsigned char scratch[], int bit);
+
+/* #define output_nybble(outfile,c)	output_nbits(outfile,c,4) */
+static void output_nybble(char *outfile, int bits);
+static void output_nnybble(char *outfile, int n, unsigned char array[]);
+
+#define output_huffman(outfile,c)	output_nbits(outfile,code[c],ncode[c])
+
+/* ---------------------------------------------------------------------- */
+int fits_hcompress(int *a, int ny, int nx, int scale, char *output, 
+                  long *nbytes, int *status)
+{
+  /* 
+     compress the input image using the H-compress algorithm
+  
+   a  - input image array
+   nx - size of X axis of image
+   ny - size of Y axis of image
+   scale - quantization scale factor. Larger values results in more (lossy) compression
+           scale = 0 does lossless compression
+   output - pre-allocated array to hold the output compressed stream of bytes
+   nbyts  - input value = size of the output buffer;
+            returned value = size of the compressed byte stream, in bytes
+
+ NOTE: the nx and ny dimensions as defined within this code are reversed from
+ the usual FITS notation.  ny is the fastest varying dimension, which is
+ usually considered the X axis in the FITS image display
+
+  */
+
+  int stat;
+  
+  if (*status > 0) return(*status);
+
+  /* H-transform */
+  stat = htrans(a, nx, ny);
+  if (stat) {
+     *status = stat;
+     return(*status);
+  }
+
+  /* digitize */
+  digitize(a, nx, ny, scale);
+
+  /* encode and write to output array */
+
+  FFLOCK;
+  noutmax = *nbytes;  /* input value is the allocated size of the array */
+  *nbytes = 0;  /* reset */
+
+  stat = encode(output, nbytes, a, nx, ny, scale);
+  FFUNLOCK;
+  
+  *status = stat;
+  return(*status);
+}
+/* ---------------------------------------------------------------------- */
+int fits_hcompress64(LONGLONG *a, int ny, int nx, int scale, char *output, 
+                  long *nbytes, int *status)
+{
+  /* 
+     compress the input image using the H-compress algorithm
+  
+   a  - input image array
+   nx - size of X axis of image
+   ny - size of Y axis of image
+   scale - quantization scale factor. Larger values results in more (lossy) compression
+           scale = 0 does lossless compression
+   output - pre-allocated array to hold the output compressed stream of bytes
+   nbyts  - size of the compressed byte stream, in bytes
+
+ NOTE: the nx and ny dimensions as defined within this code are reversed from
+ the usual FITS notation.  ny is the fastest varying dimension, which is
+ usually considered the X axis in the FITS image display
+
+  */
+
+  int stat;
+  
+  if (*status > 0) return(*status);
+
+  /* H-transform */
+  stat = htrans64(a, nx, ny);
+  if (stat) {
+     *status = stat;
+     return(*status);
+  }
+
+  /* digitize */
+  digitize64(a, nx, ny, scale);
+
+  /* encode and write to output array */
+
+  FFLOCK;
+  noutmax = *nbytes;  /* input value is the allocated size of the array */
+  *nbytes = 0;  /* reset */
+
+  stat = encode64(output, nbytes, a, nx, ny, scale);
+  FFUNLOCK;
+
+  *status = stat;
+  return(*status);
+}
+
+ 
+/* Copyright (c) 1993 Association of Universities for Research 
+ * in Astronomy. All rights reserved. Produced under National   
+ * Aeronautics and Space Administration Contract No. NAS5-26555.
+ */
+/* htrans.c   H-transform of NX x NY integer image
+ *
+ * Programmer: R. White		Date: 11 May 1992
+ */
+
+/* ######################################################################### */
+static int htrans(int a[],int nx,int ny)
+{
+int nmax, log2n, h0, hx, hy, hc, nxtop, nytop, i, j, k;
+int oddx, oddy;
+int shift, mask, mask2, prnd, prnd2, nrnd2;
+int s10, s00;
+int *tmp;
+
+	/*
+	 * log2n is log2 of max(nx,ny) rounded up to next power of 2
+	 */
+	nmax = (nx>ny) ? nx : ny;
+	log2n = (int) (log((float) nmax)/log(2.0)+0.5);
+	if ( nmax > (1<<log2n) ) {
+		log2n += 1;
+	}
+	/*
+	 * get temporary storage for shuffling elements
+	 */
+	tmp = (int *) malloc(((nmax+1)/2)*sizeof(int));
+	if(tmp == (int *) NULL) {
+	        ffpmsg("htrans: insufficient memory");
+		return(DATA_COMPRESSION_ERR);
+	}
+	/*
+	 * set up rounding and shifting masks
+	 */
+	shift = 0;
+	mask  = -2;
+	mask2 = mask << 1;
+	prnd  = 1;
+	prnd2 = prnd << 1;
+	nrnd2 = prnd2 - 1;
+	/*
+	 * do log2n reductions
+	 *
+	 * We're indexing a as a 2-D array with dimensions (nx,ny).
+	 */
+	nxtop = nx;
+	nytop = ny;
+
+	for (k = 0; k<log2n; k++) {
+		oddx = nxtop % 2;
+		oddy = nytop % 2;
+		for (i = 0; i<nxtop-oddx; i += 2) {
+			s00 = i*ny;				/* s00 is index of a[i,j]	*/
+			s10 = s00+ny;			/* s10 is index of a[i+1,j]	*/
+			for (j = 0; j<nytop-oddy; j += 2) {
+				/*
+				 * Divide h0,hx,hy,hc by 2 (1 the first time through).
+				 */
+				h0 = (a[s10+1] + a[s10] + a[s00+1] + a[s00]) >> shift;
+				hx = (a[s10+1] + a[s10] - a[s00+1] - a[s00]) >> shift;
+				hy = (a[s10+1] - a[s10] + a[s00+1] - a[s00]) >> shift;
+				hc = (a[s10+1] - a[s10] - a[s00+1] + a[s00]) >> shift;
+
+				/*
+				 * Throw away the 2 bottom bits of h0, bottom bit of hx,hy.
+				 * To get rounding to be same for positive and negative
+				 * numbers, nrnd2 = prnd2 - 1.
+				 */
+				a[s10+1] = hc;
+				a[s10  ] = ( (hx>=0) ? (hx+prnd)  :  hx        ) & mask ;
+				a[s00+1] = ( (hy>=0) ? (hy+prnd)  :  hy        ) & mask ;
+				a[s00  ] = ( (h0>=0) ? (h0+prnd2) : (h0+nrnd2) ) & mask2;
+				s00 += 2;
+				s10 += 2;
+			}
+			if (oddy) {
+				/*
+				 * do last element in row if row length is odd
+				 * s00+1, s10+1 are off edge
+				 */
+				h0 = (a[s10] + a[s00]) << (1-shift);
+				hx = (a[s10] - a[s00]) << (1-shift);
+				a[s10  ] = ( (hx>=0) ? (hx+prnd)  :  hx        ) & mask ;
+				a[s00  ] = ( (h0>=0) ? (h0+prnd2) : (h0+nrnd2) ) & mask2;
+				s00 += 1;
+				s10 += 1;
+			}
+		}
+		if (oddx) {
+			/*
+			 * do last row if column length is odd
+			 * s10, s10+1 are off edge
+			 */
+			s00 = i*ny;
+			for (j = 0; j<nytop-oddy; j += 2) {
+				h0 = (a[s00+1] + a[s00]) << (1-shift);
+				hy = (a[s00+1] - a[s00]) << (1-shift);
+				a[s00+1] = ( (hy>=0) ? (hy+prnd)  :  hy        ) & mask ;
+				a[s00  ] = ( (h0>=0) ? (h0+prnd2) : (h0+nrnd2) ) & mask2;
+				s00 += 2;
+			}
+			if (oddy) {
+				/*
+				 * do corner element if both row and column lengths are odd
+				 * s00+1, s10, s10+1 are off edge
+				 */
+				h0 = a[s00] << (2-shift);
+				a[s00  ] = ( (h0>=0) ? (h0+prnd2) : (h0+nrnd2) ) & mask2;
+			}
+		}
+		/*
+		 * now shuffle in each dimension to group coefficients by order
+		 */
+		for (i = 0; i<nxtop; i++) {
+			shuffle(&a[ny*i],nytop,1,tmp);
+		}
+		for (j = 0; j<nytop; j++) {
+			shuffle(&a[j],nxtop,ny,tmp);
+		}
+		/*
+		 * image size reduced by 2 (round up if odd)
+		 */
+		nxtop = (nxtop+1)>>1;
+		nytop = (nytop+1)>>1;
+		/*
+		 * divisor doubles after first reduction
+		 */
+		shift = 1;
+		/*
+		 * masks, rounding values double after each iteration
+		 */
+		mask  = mask2;
+		prnd  = prnd2;
+		mask2 = mask2 << 1;
+		prnd2 = prnd2 << 1;
+		nrnd2 = prnd2 - 1;
+	}
+	free(tmp);
+	return(0);
+}
+/* ######################################################################### */
+
+static int htrans64(LONGLONG a[],int nx,int ny)
+{
+int nmax, log2n, nxtop, nytop, i, j, k;
+int oddx, oddy;
+int shift;
+int s10, s00;
+LONGLONG h0, hx, hy, hc, prnd, prnd2, nrnd2, mask, mask2;
+LONGLONG *tmp;
+
+	/*
+	 * log2n is log2 of max(nx,ny) rounded up to next power of 2
+	 */
+	nmax = (nx>ny) ? nx : ny;
+	log2n = (int) (log((float) nmax)/log(2.0)+0.5);
+	if ( nmax > (1<<log2n) ) {
+		log2n += 1;
+	}
+	/*
+	 * get temporary storage for shuffling elements
+	 */
+	tmp = (LONGLONG *) malloc(((nmax+1)/2)*sizeof(LONGLONG));
+	if(tmp == (LONGLONG *) NULL) {
+	        ffpmsg("htrans64: insufficient memory");
+		return(DATA_COMPRESSION_ERR);
+	}
+	/*
+	 * set up rounding and shifting masks
+	 */
+	shift = 0;
+	mask  = (LONGLONG) -2;
+	mask2 = mask << 1;
+	prnd  = (LONGLONG) 1;
+	prnd2 = prnd << 1;
+	nrnd2 = prnd2 - 1;
+	/*
+	 * do log2n reductions
+	 *
+	 * We're indexing a as a 2-D array with dimensions (nx,ny).
+	 */
+	nxtop = nx;
+	nytop = ny;
+
+	for (k = 0; k<log2n; k++) {
+		oddx = nxtop % 2;
+		oddy = nytop % 2;
+		for (i = 0; i<nxtop-oddx; i += 2) {
+			s00 = i*ny;				/* s00 is index of a[i,j]	*/
+			s10 = s00+ny;			/* s10 is index of a[i+1,j]	*/
+			for (j = 0; j<nytop-oddy; j += 2) {
+				/*
+				 * Divide h0,hx,hy,hc by 2 (1 the first time through).
+				 */
+				h0 = (a[s10+1] + a[s10] + a[s00+1] + a[s00]) >> shift;
+				hx = (a[s10+1] + a[s10] - a[s00+1] - a[s00]) >> shift;
+				hy = (a[s10+1] - a[s10] + a[s00+1] - a[s00]) >> shift;
+				hc = (a[s10+1] - a[s10] - a[s00+1] + a[s00]) >> shift;
+
+				/*
+				 * Throw away the 2 bottom bits of h0, bottom bit of hx,hy.
+				 * To get rounding to be same for positive and negative
+				 * numbers, nrnd2 = prnd2 - 1.
+				 */
+				a[s10+1] = hc;
+				a[s10  ] = ( (hx>=0) ? (hx+prnd)  :  hx        ) & mask ;
+				a[s00+1] = ( (hy>=0) ? (hy+prnd)  :  hy        ) & mask ;
+				a[s00  ] = ( (h0>=0) ? (h0+prnd2) : (h0+nrnd2) ) & mask2;
+				s00 += 2;
+				s10 += 2;
+			}
+			if (oddy) {
+				/*
+				 * do last element in row if row length is odd
+				 * s00+1, s10+1 are off edge
+				 */
+				h0 = (a[s10] + a[s00]) << (1-shift);
+				hx = (a[s10] - a[s00]) << (1-shift);
+				a[s10  ] = ( (hx>=0) ? (hx+prnd)  :  hx        ) & mask ;
+				a[s00  ] = ( (h0>=0) ? (h0+prnd2) : (h0+nrnd2) ) & mask2;
+				s00 += 1;
+				s10 += 1;
+			}
+		}
+		if (oddx) {
+			/*
+			 * do last row if column length is odd
+			 * s10, s10+1 are off edge
+			 */
+			s00 = i*ny;
+			for (j = 0; j<nytop-oddy; j += 2) {
+				h0 = (a[s00+1] + a[s00]) << (1-shift);
+				hy = (a[s00+1] - a[s00]) << (1-shift);
+				a[s00+1] = ( (hy>=0) ? (hy+prnd)  :  hy        ) & mask ;
+				a[s00  ] = ( (h0>=0) ? (h0+prnd2) : (h0+nrnd2) ) & mask2;
+				s00 += 2;
+			}
+			if (oddy) {
+				/*
+				 * do corner element if both row and column lengths are odd
+				 * s00+1, s10, s10+1 are off edge
+				 */
+				h0 = a[s00] << (2-shift);
+				a[s00  ] = ( (h0>=0) ? (h0+prnd2) : (h0+nrnd2) ) & mask2;
+			}
+		}
+		/*
+		 * now shuffle in each dimension to group coefficients by order
+		 */
+		for (i = 0; i<nxtop; i++) {
+			shuffle64(&a[ny*i],nytop,1,tmp);
+		}
+		for (j = 0; j<nytop; j++) {
+			shuffle64(&a[j],nxtop,ny,tmp);
+		}
+		/*
+		 * image size reduced by 2 (round up if odd)
+		 */
+		nxtop = (nxtop+1)>>1;
+		nytop = (nytop+1)>>1;
+		/*
+		 * divisor doubles after first reduction
+		 */
+		shift = 1;
+		/*
+		 * masks, rounding values double after each iteration
+		 */
+		mask  = mask2;
+		prnd  = prnd2;
+		mask2 = mask2 << 1;
+		prnd2 = prnd2 << 1;
+		nrnd2 = prnd2 - 1;
+	}
+	free(tmp);
+	return(0);
+}
+
+/* ######################################################################### */
+static void
+shuffle(int a[], int n, int n2, int tmp[])
+{
+
+/* 
+int a[];	 array to shuffle					
+int n;		 number of elements to shuffle	
+int n2;		 second dimension					
+int tmp[];	 scratch storage					
+*/
+
+int i;
+int *p1, *p2, *pt;
+
+	/*
+	 * copy odd elements to tmp
+	 */
+	pt = tmp;
+	p1 = &a[n2];
+	for (i=1; i < n; i += 2) {
+		*pt = *p1;
+		pt += 1;
+		p1 += (n2+n2);
+	}
+	/*
+	 * compress even elements into first half of A
+	 */
+	p1 = &a[n2];
+	p2 = &a[n2+n2];
+	for (i=2; i<n; i += 2) {
+		*p1 = *p2;
+		p1 += n2;
+		p2 += (n2+n2);
+	}
+	/*
+	 * put odd elements into 2nd half
+	 */
+	pt = tmp;
+	for (i = 1; i<n; i += 2) {
+		*p1 = *pt;
+		p1 += n2;
+		pt += 1;
+	}
+}
+/* ######################################################################### */
+static void
+shuffle64(LONGLONG a[], int n, int n2, LONGLONG tmp[])
+{
+
+/* 
+LONGLONG a[];	 array to shuffle					
+int n;		 number of elements to shuffle	
+int n2;		 second dimension					
+LONGLONG tmp[];	 scratch storage					
+*/
+
+int i;
+LONGLONG *p1, *p2, *pt;
+
+	/*
+	 * copy odd elements to tmp
+	 */
+	pt = tmp;
+	p1 = &a[n2];
+	for (i=1; i < n; i += 2) {
+		*pt = *p1;
+		pt += 1;
+		p1 += (n2+n2);
+	}
+	/*
+	 * compress even elements into first half of A
+	 */
+	p1 = &a[n2];
+	p2 = &a[n2+n2];
+	for (i=2; i<n; i += 2) {
+		*p1 = *p2;
+		p1 += n2;
+		p2 += (n2+n2);
+	}
+	/*
+	 * put odd elements into 2nd half
+	 */
+	pt = tmp;
+	for (i = 1; i<n; i += 2) {
+		*p1 = *pt;
+		p1 += n2;
+		pt += 1;
+	}
+}
+/* ######################################################################### */
+/* ######################################################################### */
+/* Copyright (c) 1993 Association of Universities for Research 
+ * in Astronomy. All rights reserved. Produced under National   
+ * Aeronautics and Space Administration Contract No. NAS5-26555.
+ */
+/* digitize.c	digitize H-transform
+ *
+ * Programmer: R. White		Date: 11 March 1991
+ */
+
+/* ######################################################################### */
+static void  
+digitize(int a[], int nx, int ny, int scale)
+{
+int d, *p;
+
+	/*
+	 * round to multiple of scale
+	 */
+	if (scale <= 1) return;
+	d=(scale+1)/2-1;
+	for (p=a; p <= &a[nx*ny-1]; p++) *p = ((*p>0) ? (*p+d) : (*p-d))/scale;
+}
+
+/* ######################################################################### */
+static void  
+digitize64(LONGLONG a[], int nx, int ny, int scale)
+{
+LONGLONG d, *p, scale64;
+
+	/*
+	 * round to multiple of scale
+	 */
+	if (scale <= 1) return;
+	d=(scale+1)/2-1;
+	scale64 = scale;  /* use a 64-bit int for efficiency in the big loop */
+
+	for (p=a; p <= &a[nx*ny-1]; p++) *p = ((*p>0) ? (*p+d) : (*p-d))/scale64;
+}
+/* ######################################################################### */
+/* ######################################################################### */
+/* Copyright (c) 1993 Association of Universities for Research 
+ * in Astronomy. All rights reserved. Produced under National   
+ * Aeronautics and Space Administration Contract No. NAS5-26555.
+ */
+/* encode.c		encode H-transform and write to outfile
+ *
+ * Programmer: R. White		Date: 2 February 1994
+ */
+
+static char code_magic[2] = { (char)0xDD, (char)0x99 };
+
+
+/* ######################################################################### */
+static int encode(char *outfile, long *nlength, int a[], int nx, int ny, int scale)
+{
+
+/* FILE *outfile;  - change outfile to a char array */  
+/*
+  long * nlength    returned length (in bytes) of the encoded array)
+  int a[];								 input H-transform array (nx,ny)
+  int nx,ny;								 size of H-transform array	
+  int scale;								 scale factor for digitization
+*/
+int nel, nx2, ny2, i, j, k, q, vmax[3], nsign, bits_to_go;
+unsigned char nbitplanes[3];
+unsigned char *signbits;
+int stat;
+
+        noutchar = 0;  /* initialize the number of compressed bytes that have been written */
+	nel = nx*ny;
+	/*
+	 * write magic value
+	 */
+	qwrite(outfile, code_magic, sizeof(code_magic));
+	writeint(outfile, nx);			/* size of image */
+	writeint(outfile, ny);
+	writeint(outfile, scale);		/* scale factor for digitization */
+	/*
+	 * write first value of A (sum of all pixels -- the only value
+	 * which does not compress well)
+	 */
+	writelonglong(outfile, (LONGLONG) a[0]);
+
+	a[0] = 0;
+	/*
+	 * allocate array for sign bits and save values, 8 per byte
+	 */
+	signbits = (unsigned char *) malloc((nel+7)/8);
+	if (signbits == (unsigned char *) NULL) {
+		ffpmsg("encode: insufficient memory");
+		return(DATA_COMPRESSION_ERR);
+	}
+	nsign = 0;
+	bits_to_go = 8;
+	signbits[0] = 0;
+	for (i=0; i<nel; i++) {
+		if (a[i] > 0) {
+			/*
+			 * positive element, put zero at end of buffer
+			 */
+			signbits[nsign] <<= 1;
+			bits_to_go -= 1;
+		} else if (a[i] < 0) {
+			/*
+			 * negative element, shift in a one
+			 */
+			signbits[nsign] <<= 1;
+			signbits[nsign] |= 1;
+			bits_to_go -= 1;
+			/*
+			 * replace a by absolute value
+			 */
+			a[i] = -a[i];
+		}
+		if (bits_to_go == 0) {
+			/*
+			 * filled up this byte, go to the next one
+			 */
+			bits_to_go = 8;
+			nsign += 1;
+			signbits[nsign] = 0;
+		}
+	}
+	if (bits_to_go != 8) {
+		/*
+		 * some bits in last element
+		 * move bits in last byte to bottom and increment nsign
+		 */
+		signbits[nsign] <<= bits_to_go;
+		nsign += 1;
+	}
+	/*
+	 * calculate number of bit planes for 3 quadrants
+	 *
+	 * quadrant 0=bottom left, 1=bottom right or top left, 2=top right, 
+	 */
+	for (q=0; q<3; q++) {
+		vmax[q] = 0;
+	}
+	/*
+	 * get maximum absolute value in each quadrant
+	 */
+	nx2 = (nx+1)/2;
+	ny2 = (ny+1)/2;
+	j=0;	/* column counter	*/
+	k=0;	/* row counter		*/
+	for (i=0; i<nel; i++) {
+		q = (j>=ny2) + (k>=nx2);
+		if (vmax[q] < a[i]) vmax[q] = a[i];
+		if (++j >= ny) {
+			j = 0;
+			k += 1;
+		}
+	}
+	/*
+	 * now calculate number of bits for each quadrant
+	 */
+
+        /* this is a more efficient way to do this, */
+ 
+ 
+        for (q = 0; q < 3; q++) {
+            for (nbitplanes[q] = 0; vmax[q]>0; vmax[q] = vmax[q]>>1, nbitplanes[q]++) ; 
+        }
+
+
+/*
+	for (q = 0; q < 3; q++) {
+		nbitplanes[q] = (int) (log((float) (vmax[q]+1))/log(2.0)+0.5);
+		if ( (vmax[q]+1) > (1<<nbitplanes[q]) ) {
+			nbitplanes[q] += 1;
+		}
+	}
+*/
+
+	/*
+	 * write nbitplanes
+	 */
+	if (0 == qwrite(outfile, (char *) nbitplanes, sizeof(nbitplanes))) {
+	        *nlength = noutchar;
+		ffpmsg("encode: output buffer too small");
+		return(DATA_COMPRESSION_ERR);
+        }
+	 
+	/*
+	 * write coded array
+	 */
+	stat = doencode(outfile, a, nx, ny, nbitplanes);
+	/*
+	 * write sign bits
+	 */
+
+	if (nsign > 0) {
+
+	   if ( 0 == qwrite(outfile, (char *) signbits, nsign)) {
+	        free(signbits);
+	        *nlength = noutchar;
+		ffpmsg("encode: output buffer too small");
+		return(DATA_COMPRESSION_ERR);
+          }
+	} 
+	
+	free(signbits);
+	*nlength = noutchar;
+
+        if (noutchar >= noutmax) {
+		ffpmsg("encode: output buffer too small");
+		return(DATA_COMPRESSION_ERR);
+        }  
+	
+	return(stat); 
+}
+/* ######################################################################### */
+static int encode64(char *outfile, long *nlength, LONGLONG a[], int nx, int ny, int scale)
+{
+
+/* FILE *outfile;  - change outfile to a char array */  
+/*
+  long * nlength    returned length (in bytes) of the encoded array)
+  LONGLONG a[];								 input H-transform array (nx,ny)
+  int nx,ny;								 size of H-transform array	
+  int scale;								 scale factor for digitization
+*/
+int nel, nx2, ny2, i, j, k, q, nsign, bits_to_go;
+LONGLONG vmax[3];
+unsigned char nbitplanes[3];
+unsigned char *signbits;
+int stat;
+
+        noutchar = 0;  /* initialize the number of compressed bytes that have been written */
+	nel = nx*ny;
+	/*
+	 * write magic value
+	 */
+	qwrite(outfile, code_magic, sizeof(code_magic));
+	writeint(outfile, nx);				/* size of image	*/
+	writeint(outfile, ny);
+	writeint(outfile, scale);			/* scale factor for digitization */
+	/*
+	 * write first value of A (sum of all pixels -- the only value
+	 * which does not compress well)
+	 */
+	writelonglong(outfile, a[0]);
+
+	a[0] = 0;
+	/*
+	 * allocate array for sign bits and save values, 8 per byte
+	 */
+	signbits = (unsigned char *) malloc((nel+7)/8);
+	if (signbits == (unsigned char *) NULL) {
+		ffpmsg("encode64: insufficient memory");
+		return(DATA_COMPRESSION_ERR);
+	}
+	nsign = 0;
+	bits_to_go = 8;
+	signbits[0] = 0;
+	for (i=0; i<nel; i++) {
+		if (a[i] > 0) {
+			/*
+			 * positive element, put zero at end of buffer
+			 */
+			signbits[nsign] <<= 1;
+			bits_to_go -= 1;
+		} else if (a[i] < 0) {
+			/*
+			 * negative element, shift in a one
+			 */
+			signbits[nsign] <<= 1;
+			signbits[nsign] |= 1;
+			bits_to_go -= 1;
+			/*
+			 * replace a by absolute value
+			 */
+			a[i] = -a[i];
+		}
+		if (bits_to_go == 0) {
+			/*
+			 * filled up this byte, go to the next one
+			 */
+			bits_to_go = 8;
+			nsign += 1;
+			signbits[nsign] = 0;
+		}
+	}
+	if (bits_to_go != 8) {
+		/*
+		 * some bits in last element
+		 * move bits in last byte to bottom and increment nsign
+		 */
+		signbits[nsign] <<= bits_to_go;
+		nsign += 1;
+	}
+	/*
+	 * calculate number of bit planes for 3 quadrants
+	 *
+	 * quadrant 0=bottom left, 1=bottom right or top left, 2=top right, 
+	 */
+	for (q=0; q<3; q++) {
+		vmax[q] = 0;
+	}
+	/*
+	 * get maximum absolute value in each quadrant
+	 */
+	nx2 = (nx+1)/2;
+	ny2 = (ny+1)/2;
+	j=0;	/* column counter	*/
+	k=0;	/* row counter		*/
+	for (i=0; i<nel; i++) {
+		q = (j>=ny2) + (k>=nx2);
+		if (vmax[q] < a[i]) vmax[q] = a[i];
+		if (++j >= ny) {
+			j = 0;
+			k += 1;
+		}
+	}
+	/*
+	 * now calculate number of bits for each quadrant
+	 */
+	 
+        /* this is a more efficient way to do this, */
+ 
+ 
+        for (q = 0; q < 3; q++) {
+            for (nbitplanes[q] = 0; vmax[q]>0; vmax[q] = vmax[q]>>1, nbitplanes[q]++) ; 
+        }
+
+
+/*
+	for (q = 0; q < 3; q++) {
+		nbitplanes[q] = log((float) (vmax[q]+1))/log(2.0)+0.5;
+		if ( (vmax[q]+1) > (((LONGLONG) 1)<<nbitplanes[q]) ) {
+			nbitplanes[q] += 1;
+		}
+	}
+*/
+
+	/*
+	 * write nbitplanes
+	 */
+
+	if (0 == qwrite(outfile, (char *) nbitplanes, sizeof(nbitplanes))) {
+	        *nlength = noutchar;
+		ffpmsg("encode: output buffer too small");
+		return(DATA_COMPRESSION_ERR);
+        }
+	 
+	/*
+	 * write coded array
+	 */
+	stat = doencode64(outfile, a, nx, ny, nbitplanes);
+	/*
+	 * write sign bits
+	 */
+
+	if (nsign > 0) {
+
+	   if ( 0 == qwrite(outfile, (char *) signbits, nsign)) {
+	        free(signbits);
+	        *nlength = noutchar;
+		ffpmsg("encode: output buffer too small");
+		return(DATA_COMPRESSION_ERR);
+          }
+	} 
+
+	free(signbits);
+	*nlength = noutchar;
+
+        if (noutchar >= noutmax) {
+		ffpmsg("encode64: output buffer too small");
+		return(DATA_COMPRESSION_ERR);
+        }
+		
+	return(stat); 
+}
+/* ######################################################################### */
+/* ######################################################################### */
+/* Copyright (c) 1993 Association of Universities for Research 
+ * in Astronomy. All rights reserved. Produced under National   
+ * Aeronautics and Space Administration Contract No. NAS5-26555.
+ */
+/* qwrite.c	Write binary data
+ *
+ * Programmer: R. White		Date: 11 March 1991
+ */
+
+/* ######################################################################### */
+static void
+writeint(char *outfile, int a)
+{
+int i;
+unsigned char b[4];
+
+	/* Write integer A one byte at a time to outfile.
+	 *
+	 * This is portable from Vax to Sun since it eliminates the
+	 * need for byte-swapping.
+	 */
+	for (i=3; i>=0; i--) {
+		b[i] = a & 0x000000ff;
+		a >>= 8;
+	}
+	for (i=0; i<4; i++) qwrite(outfile, (char *) &b[i],1);
+}
+
+/* ######################################################################### */
+static void
+writelonglong(char *outfile, LONGLONG a)
+{
+int i;
+unsigned char b[8];
+
+	/* Write integer A one byte at a time to outfile.
+	 *
+	 * This is portable from Vax to Sun since it eliminates the
+	 * need for byte-swapping.
+	 */
+	for (i=7; i>=0; i--) {
+		b[i] = (unsigned char) (a & 0x000000ff);
+		a >>= 8;
+	}
+	for (i=0; i<8; i++) qwrite(outfile, (char *) &b[i],1);
+}
+/* ######################################################################### */
+static int
+qwrite(char *file, char buffer[], int n){
+    /*
+     * write n bytes from buffer into file
+     * returns number of bytes read (=n) if successful, <=0 if not
+     */
+
+     if (noutchar + n > noutmax) return(0);  /* buffer overflow */
+     
+     memcpy(&file[noutchar], buffer, n);
+     noutchar += n;
+
+     return(n);
+}
+/* ######################################################################### */
+/* ######################################################################### */
+/* Copyright (c) 1993 Association of Universities for Research 
+ * in Astronomy. All rights reserved. Produced under National   
+ * Aeronautics and Space Administration Contract No. NAS5-26555.
+ */
+/* doencode.c	Encode 2-D array and write stream of characters on outfile
+ *
+ * This version assumes that A is positive.
+ *
+ * Programmer: R. White		Date: 7 May 1991
+ */
+
+/* ######################################################################### */
+static int
+doencode(char *outfile, int a[], int nx, int ny, unsigned char nbitplanes[3])
+{
+/* char *outfile;						 output data stream
+int a[];							 Array of values to encode			
+int nx,ny;							 Array dimensions [nx][ny]			
+unsigned char nbitplanes[3];		 Number of bit planes in quadrants	
+*/
+
+int nx2, ny2, stat;
+
+	nx2 = (nx+1)/2;
+	ny2 = (ny+1)/2;
+	/*
+	 * Initialize bit output
+	 */
+	start_outputing_bits();
+	/*
+	 * write out the bit planes for each quadrant
+	 */
+	stat = qtree_encode(outfile, &a[0],          ny, nx2,  ny2,  nbitplanes[0]);
+
+        if (!stat)
+		stat = qtree_encode(outfile, &a[ny2],        ny, nx2,  ny/2, nbitplanes[1]);
+
+        if (!stat)
+		stat = qtree_encode(outfile, &a[ny*nx2],     ny, nx/2, ny2,  nbitplanes[1]);
+
+        if (!stat)
+		stat = qtree_encode(outfile, &a[ny*nx2+ny2], ny, nx/2, ny/2, nbitplanes[2]);
+	/*
+	 * Add zero as an EOF symbol
+	 */
+	output_nybble(outfile, 0);
+	done_outputing_bits(outfile);
+	
+	return(stat);
+}
+/* ######################################################################### */
+static int
+doencode64(char *outfile, LONGLONG a[], int nx, int ny, unsigned char nbitplanes[3])
+{
+/* char *outfile;						 output data stream
+LONGLONG a[];							 Array of values to encode			
+int nx,ny;							 Array dimensions [nx][ny]			
+unsigned char nbitplanes[3];		 Number of bit planes in quadrants	
+*/
+
+int nx2, ny2, stat;
+
+	nx2 = (nx+1)/2;
+	ny2 = (ny+1)/2;
+	/*
+	 * Initialize bit output
+	 */
+	start_outputing_bits();
+	/*
+	 * write out the bit planes for each quadrant
+	 */
+	stat = qtree_encode64(outfile, &a[0],          ny, nx2,  ny2,  nbitplanes[0]);
+
+        if (!stat)
+		stat = qtree_encode64(outfile, &a[ny2],        ny, nx2,  ny/2, nbitplanes[1]);
+
+        if (!stat)
+		stat = qtree_encode64(outfile, &a[ny*nx2],     ny, nx/2, ny2,  nbitplanes[1]);
+
+        if (!stat)
+		stat = qtree_encode64(outfile, &a[ny*nx2+ny2], ny, nx/2, ny/2, nbitplanes[2]);
+	/*
+	 * Add zero as an EOF symbol
+	 */
+	output_nybble(outfile, 0);
+	done_outputing_bits(outfile);
+	
+	return(stat);
+}
+/* ######################################################################### */
+/* ######################################################################### */
+/* Copyright (c) 1993 Association of Universities for Research 
+ * in Astronomy. All rights reserved. Produced under National   
+ * Aeronautics and Space Administration Contract No. NAS5-26555.
+ */
+/* BIT OUTPUT ROUTINES */
+
+
+static LONGLONG bitcount;
+
+/* THE BIT BUFFER */
+
+static int buffer2;			/* Bits buffered for output	*/
+static int bits_to_go2;			/* Number of bits free in buffer */
+
+
+/* ######################################################################### */
+/* INITIALIZE FOR BIT OUTPUT */
+
+static void
+start_outputing_bits(void)
+{
+	buffer2 = 0;			/* Buffer is empty to start	*/
+	bits_to_go2 = 8;		/* with				*/
+	bitcount = 0;
+}
+
+/* ######################################################################### */
+/* OUTPUT N BITS (N must be <= 8) */
+
+static void
+output_nbits(char *outfile, int bits, int n)
+{
+    /* AND mask for the right-most n bits */
+    static int mask[9] = {0, 1, 3, 7, 15, 31, 63, 127, 255};
+	/*
+	 * insert bits at end of buffer
+	 */
+	buffer2 <<= n;
+/*	buffer2 |= ( bits & ((1<<n)-1) ); */
+	buffer2 |= ( bits & (*(mask+n)) );
+	bits_to_go2 -= n;
+	if (bits_to_go2 <= 0) {
+		/*
+		 * buffer2 full, put out top 8 bits
+		 */
+
+	        outfile[noutchar] = ((buffer2>>(-bits_to_go2)) & 0xff);
+
+		if (noutchar < noutmax) noutchar++;
+		
+		bits_to_go2 += 8;
+	}
+	bitcount += n;
+}
+/* ######################################################################### */
+/*  OUTPUT a 4 bit nybble */
+static void
+output_nybble(char *outfile, int bits)
+{
+	/*
+	 * insert 4 bits at end of buffer
+	 */
+	buffer2 = (buffer2<<4) | ( bits & 15 );
+	bits_to_go2 -= 4;
+	if (bits_to_go2 <= 0) {
+		/*
+		 * buffer2 full, put out top 8 bits
+		 */
+
+	        outfile[noutchar] = ((buffer2>>(-bits_to_go2)) & 0xff);
+
+		if (noutchar < noutmax) noutchar++;
+		
+		bits_to_go2 += 8;
+	}
+	bitcount += 4;
+}
+/*  ############################################################################  */
+/* OUTPUT array of 4 BITS  */
+
+static void output_nnybble(char *outfile, int n, unsigned char array[])
+{
+	/* pack the 4 lower bits in each element of the array into the outfile array */
+
+int ii, jj, kk = 0, shift;
+
+	if (n == 1) {
+		output_nybble(outfile, (int) array[0]);
+		return;
+	}
+/* forcing byte alignment doesn;t help, and even makes it go slightly slower
+if (bits_to_go2 != 8)
+   output_nbits(outfile, kk, bits_to_go2);
+*/
+	if (bits_to_go2 <= 4)
+	{
+		/* just room for 1 nybble; write it out separately */
+		output_nybble(outfile, array[0]);
+		kk++;  /* index to next array element */
+
+		if (n == 2)  /* only 1 more nybble to write out */
+		{
+			output_nybble(outfile, (int) array[1]);
+			return;
+		}
+	}
+
+
+        /* bits_to_go2 is now in the range 5 - 8 */
+	shift = 8 - bits_to_go2;  
+
+	/* now write out pairs of nybbles; this does not affect value of bits_to_go2 */
+	jj = (n - kk) / 2;
+	
+	if (bits_to_go2 == 8) {
+	    /* special case if nybbles are aligned on byte boundary */
+	    /* this actually seems to make very little differnece in speed */
+	    buffer2 = 0;
+	    for (ii = 0; ii < jj; ii++)
+	    {
+		outfile[noutchar] = ((array[kk] & 15)<<4) | (array[kk+1] & 15);
+		kk += 2;
+		noutchar++;
+	    }
+	} else {
+	    for (ii = 0; ii < jj; ii++)
+	    {
+		buffer2 = (buffer2<<8) | ((array[kk] & 15)<<4) | (array[kk+1] & 15);
+		kk += 2;
+
+		/*
+		 buffer2 full, put out top 8 bits
+		 */
+
+	        outfile[noutchar] = ((buffer2>>shift) & 0xff);
+		noutchar++;
+	    }
+	}
+
+	bitcount += (8 * (ii - 1));
+
+	/* write out last odd nybble, if present */
+	if (kk != n) output_nybble(outfile, (int) array[n - 1]); 
+
+	return; 
+}
+
+
+/* ######################################################################### */
+/* FLUSH OUT THE LAST BITS */
+
+static void
+done_outputing_bits(char *outfile)
+{
+	if(bits_to_go2 < 8) {
+/*		putc(buffer2<<bits_to_go2,outfile); */
+
+	        outfile[noutchar] = (buffer2<<bits_to_go2);
+		if (noutchar < noutmax) noutchar++;
+
+		/* count the garbage bits too */
+		bitcount += bits_to_go2;
+	}
+}
+/* ######################################################################### */
+/* ######################################################################### */
+/* Copyright (c) 1993 Association of Universities for Research 
+ * in Astronomy. All rights reserved. Produced under National   
+ * Aeronautics and Space Administration Contract No. NAS5-26555.
+ */
+/* qtree_encode.c	Encode values in quadrant of 2-D array using binary
+ *					quadtree coding for each bit plane.  Assumes array is
+ *					positive.
+ *
+ * Programmer: R. White		Date: 15 May 1991
+ */
+
+/*
+ * Huffman code values and number of bits in each code
+ */
+static int code[16] =
+	{
+	0x3e, 0x00, 0x01, 0x08, 0x02, 0x09, 0x1a, 0x1b,
+	0x03, 0x1c, 0x0a, 0x1d, 0x0b, 0x1e, 0x3f, 0x0c
+	};
+static int ncode[16] =
+	{
+	6,    3,    3,    4,    3,    4,    5,    5,
+	3,    5,    4,    5,    4,    5,    6,    4
+	};
+
+/*
+ * variables for bit output to buffer when Huffman coding
+ */
+static int bitbuffer, bits_to_go3;
+
+/*
+ * macros to write out 4-bit nybble, Huffman code for this value
+ */
+
+
+/* ######################################################################### */
+static int
+qtree_encode(char *outfile, int a[], int n, int nqx, int nqy, int nbitplanes)
+{
+
+/*
+int a[];
+int n;								 physical dimension of row in a		
+int nqx;							 length of row			
+int nqy;							 length of column (<=n)				
+int nbitplanes;						 number of bit planes to output	
+*/
+	
+int log2n, i, k, bit, b, bmax, nqmax, nqx2, nqy2, nx, ny;
+unsigned char *scratch, *buffer;
+
+	/*
+	 * log2n is log2 of max(nqx,nqy) rounded up to next power of 2
+	 */
+	nqmax = (nqx>nqy) ? nqx : nqy;
+	log2n = (int) (log((float) nqmax)/log(2.0)+0.5);
+	if (nqmax > (1<<log2n)) {
+		log2n += 1;
+	}
+	/*
+	 * initialize buffer point, max buffer size
+	 */
+	nqx2 = (nqx+1)/2;
+	nqy2 = (nqy+1)/2;
+	bmax = (nqx2*nqy2+1)/2;
+	/*
+	 * We're indexing A as a 2-D array with dimensions (nqx,nqy).
+	 * Scratch is 2-D with dimensions (nqx/2,nqy/2) rounded up.
+	 * Buffer is used to store string of codes for output.
+	 */
+	scratch = (unsigned char *) malloc(2*bmax);
+	buffer = (unsigned char *) malloc(bmax);
+	if ((scratch == (unsigned char *) NULL) ||
+		(buffer  == (unsigned char *) NULL)) {		
+		ffpmsg("qtree_encode: insufficient memory");
+		return(DATA_COMPRESSION_ERR);
+	}
+	/*
+	 * now encode each bit plane, starting with the top
+	 */
+	for (bit=nbitplanes-1; bit >= 0; bit--) {
+		/*
+		 * initial bit buffer
+		 */
+		b = 0;
+		bitbuffer = 0;
+		bits_to_go3 = 0;
+		/*
+		 * on first pass copy A to scratch array
+		 */
+		qtree_onebit(a,n,nqx,nqy,scratch,bit);
+		nx = (nqx+1)>>1;
+		ny = (nqy+1)>>1;
+		/*
+		 * copy non-zero values to output buffer, which will be written
+		 * in reverse order
+		 */
+		if (bufcopy(scratch,nx*ny,buffer,&b,bmax)) {
+			/*
+			 * quadtree is expanding data,
+			 * change warning code and just fill buffer with bit-map
+			 */
+			write_bdirect(outfile,a,n,nqx,nqy,scratch,bit);
+			goto bitplane_done;
+		}
+		/*
+		 * do log2n reductions
+		 */
+		for (k = 1; k<log2n; k++) {
+			qtree_reduce(scratch,ny,nx,ny,scratch);
+			nx = (nx+1)>>1;
+			ny = (ny+1)>>1;
+			if (bufcopy(scratch,nx*ny,buffer,&b,bmax)) {
+				write_bdirect(outfile,a,n,nqx,nqy,scratch,bit);
+				goto bitplane_done;
+			}
+		}
+		/*
+		 * OK, we've got the code in buffer
+		 * Write quadtree warning code, then write buffer in reverse order
+		 */
+		output_nybble(outfile,0xF);
+		if (b==0) {
+			if (bits_to_go3>0) {
+				/*
+				 * put out the last few bits
+				 */
+				output_nbits(outfile, bitbuffer & ((1<<bits_to_go3)-1),
+					bits_to_go3);
+			} else {
+				/*
+				 * have to write a zero nybble if there are no 1's in array
+				 */
+				output_huffman(outfile,0);
+			}
+		} else {
+			if (bits_to_go3>0) {
+				/*
+				 * put out the last few bits
+				 */
+				output_nbits(outfile, bitbuffer & ((1<<bits_to_go3)-1),
+					bits_to_go3);
+			}
+			for (i=b-1; i>=0; i--) {
+				output_nbits(outfile,buffer[i],8);
+			}
+		}
+		bitplane_done: ;
+	}
+	free(buffer);
+	free(scratch);
+	return(0);
+}
+/* ######################################################################### */
+static int
+qtree_encode64(char *outfile, LONGLONG a[], int n, int nqx, int nqy, int nbitplanes)
+{
+
+/*
+LONGLONG a[];
+int n;								 physical dimension of row in a		
+int nqx;							 length of row			
+int nqy;							 length of column (<=n)				
+int nbitplanes;						 number of bit planes to output	
+*/
+	
+int log2n, i, k, bit, b, nqmax, nqx2, nqy2, nx, ny;
+int bmax;  /* this potentially needs to be made a 64-bit int to support large arrays */
+unsigned char *scratch, *buffer;
+
+	/*
+	 * log2n is log2 of max(nqx,nqy) rounded up to next power of 2
+	 */
+	nqmax = (nqx>nqy) ? nqx : nqy;
+	log2n = (int) (log((float) nqmax)/log(2.0)+0.5);
+	if (nqmax > (1<<log2n)) {
+		log2n += 1;
+	}
+	/*
+	 * initialize buffer point, max buffer size
+	 */
+	nqx2 = (nqx+1)/2;
+	nqy2 = (nqy+1)/2;
+	bmax = (( nqx2)* ( nqy2)+1)/2;
+	/*
+	 * We're indexing A as a 2-D array with dimensions (nqx,nqy).
+	 * Scratch is 2-D with dimensions (nqx/2,nqy/2) rounded up.
+	 * Buffer is used to store string of codes for output.
+	 */
+	scratch = (unsigned char *) malloc(2*bmax);
+	buffer = (unsigned char *) malloc(bmax);
+	if ((scratch == (unsigned char *) NULL) ||
+		(buffer  == (unsigned char *) NULL)) {
+		ffpmsg("qtree_encode64: insufficient memory");
+		return(DATA_COMPRESSION_ERR);
+	}
+	/*
+	 * now encode each bit plane, starting with the top
+	 */
+	for (bit=nbitplanes-1; bit >= 0; bit--) {
+		/*
+		 * initial bit buffer
+		 */
+		b = 0;
+		bitbuffer = 0;
+		bits_to_go3 = 0;
+		/*
+		 * on first pass copy A to scratch array
+		 */
+		qtree_onebit64(a,n,nqx,nqy,scratch,bit);
+		nx = (nqx+1)>>1;
+		ny = (nqy+1)>>1;
+		/*
+		 * copy non-zero values to output buffer, which will be written
+		 * in reverse order
+		 */
+		if (bufcopy(scratch,nx*ny,buffer,&b,bmax)) {
+			/*
+			 * quadtree is expanding data,
+			 * change warning code and just fill buffer with bit-map
+			 */
+			write_bdirect64(outfile,a,n,nqx,nqy,scratch,bit);
+			goto bitplane_done;
+		}
+		/*
+		 * do log2n reductions
+		 */
+		for (k = 1; k<log2n; k++) {
+			qtree_reduce(scratch,ny,nx,ny,scratch);
+			nx = (nx+1)>>1;
+			ny = (ny+1)>>1;
+			if (bufcopy(scratch,nx*ny,buffer,&b,bmax)) {
+				write_bdirect64(outfile,a,n,nqx,nqy,scratch,bit);
+				goto bitplane_done;
+			}
+		}
+		/*
+		 * OK, we've got the code in buffer
+		 * Write quadtree warning code, then write buffer in reverse order
+		 */
+		output_nybble(outfile,0xF);
+		if (b==0) {
+			if (bits_to_go3>0) {
+				/*
+				 * put out the last few bits
+				 */
+				output_nbits(outfile, bitbuffer & ((1<<bits_to_go3)-1),
+					bits_to_go3);
+			} else {
+				/*
+				 * have to write a zero nybble if there are no 1's in array
+				 */
+				output_huffman(outfile,0);
+			}
+		} else {
+			if (bits_to_go3>0) {
+				/*
+				 * put out the last few bits
+				 */
+				output_nbits(outfile, bitbuffer & ((1<<bits_to_go3)-1),
+					bits_to_go3);
+			}
+			for (i=b-1; i>=0; i--) {
+				output_nbits(outfile,buffer[i],8);
+			}
+		}
+		bitplane_done: ;
+	}
+	free(buffer);
+	free(scratch);
+	return(0);
+}
+
+/* ######################################################################### */
+/*
+ * copy non-zero codes from array to buffer
+ */
+static int
+bufcopy(unsigned char a[], int n, unsigned char buffer[], int *b, int bmax)
+{
+int i;
+
+	for (i = 0; i < n; i++) {
+		if (a[i] != 0) {
+			/*
+			 * add Huffman code for a[i] to buffer
+			 */
+			bitbuffer |= code[a[i]] << bits_to_go3;
+			bits_to_go3 += ncode[a[i]];
+			if (bits_to_go3 >= 8) {
+				buffer[*b] = bitbuffer & 0xFF;
+				*b += 1;
+				/*
+				 * return warning code if we fill buffer
+				 */
+				if (*b >= bmax) return(1);
+				bitbuffer >>= 8;
+				bits_to_go3 -= 8;
+			}
+		}
+	}
+	return(0);
+}
+
+/* ######################################################################### */
+/*
+ * Do first quadtree reduction step on bit BIT of array A.
+ * Results put into B.
+ * 
+ */
+static void
+qtree_onebit(int a[], int n, int nx, int ny, unsigned char b[], int bit)
+{
+int i, j, k;
+int b0, b1, b2, b3;
+int s10, s00;
+
+	/*
+	 * use selected bit to get amount to shift
+	 */
+	b0 = 1<<bit;
+	b1 = b0<<1;
+	b2 = b0<<2;
+	b3 = b0<<3;
+	k = 0;							/* k is index of b[i/2,j/2]	*/
+	for (i = 0; i<nx-1; i += 2) {
+		s00 = n*i;					/* s00 is index of a[i,j]	*/
+/* tried using s00+n directly in the statements, but this had no effect on performance */
+		s10 = s00+n;				/* s10 is index of a[i+1,j]	*/
+		for (j = 0; j<ny-1; j += 2) {
+
+/*
+ this was not any faster..
+ 
+         b[k] = (a[s00]  & b0) ? 
+	            (a[s00+1] & b0) ?
+	                (a[s10] & b0)   ?
+		            (a[s10+1] & b0) ? 15 : 14
+                         :  (a[s10+1] & b0) ? 13 : 12
+		      : (a[s10] & b0)   ?
+		            (a[s10+1] & b0) ? 11 : 10
+                         :  (a[s10+1] & b0) ?  9 :  8
+	          : (a[s00+1] & b0) ?
+	                (a[s10] & b0)   ?
+		            (a[s10+1] & b0) ? 7 : 6
+                         :  (a[s10+1] & b0) ? 5 : 4
+
+		      : (a[s10] & b0)   ?
+		            (a[s10+1] & b0) ? 3 : 2
+                         :  (a[s10+1] & b0) ? 1 : 0;
+*/
+
+/*
+this alternative way of calculating b[k] was slowwer than the original code
+		    if ( a[s00]     & b0)
+			if ( a[s00+1]     & b0)
+			    if ( a[s10]     & b0)
+				if ( a[s10+1]     & b0)
+					b[k] = 15;
+				else
+					b[k] = 14;
+			    else
+				if ( a[s10+1]     & b0)
+					b[k] = 13;
+				else
+					b[k] = 12;
+			else
+			    if ( a[s10]     & b0)
+				if ( a[s10+1]     & b0)
+					b[k] = 11;
+				else
+					b[k] = 10;
+			    else
+				if ( a[s10+1]     & b0)
+					b[k] = 9;
+				else
+					b[k] = 8;
+		    else
+			if ( a[s00+1]     & b0)
+			    if ( a[s10]     & b0)
+				if ( a[s10+1]     & b0)
+					b[k] = 7;
+				else
+					b[k] = 6;
+			    else
+				if ( a[s10+1]     & b0)
+					b[k] = 5;
+				else
+					b[k] = 4;
+			else
+			    if ( a[s10]     & b0)
+				if ( a[s10+1]     & b0)
+					b[k] = 3;
+				else
+					b[k] = 2;
+			    else
+				if ( a[s10+1]     & b0)
+					b[k] = 1;
+				else
+					b[k] = 0;
+*/
+			
+
+
+			b[k] = ( ( a[s10+1]     & b0)
+				   | ((a[s10  ]<<1) & b1)
+				   | ((a[s00+1]<<2) & b2)
+				   | ((a[s00  ]<<3) & b3) ) >> bit;
+
+			k += 1;
+			s00 += 2;
+			s10 += 2;
+		}
+		if (j < ny) {
+			/*
+			 * row size is odd, do last element in row
+			 * s00+1,s10+1 are off edge
+			 */
+			b[k] = ( ((a[s10  ]<<1) & b1)
+				   | ((a[s00  ]<<3) & b3) ) >> bit;
+			k += 1;
+		}
+	}
+	if (i < nx) {
+		/*
+		 * column size is odd, do last row
+		 * s10,s10+1 are off edge
+		 */
+		s00 = n*i;
+		for (j = 0; j<ny-1; j += 2) {
+			b[k] = ( ((a[s00+1]<<2) & b2)
+				   | ((a[s00  ]<<3) & b3) ) >> bit;
+			k += 1;
+			s00 += 2;
+		}
+		if (j < ny) {
+			/*
+			 * both row and column size are odd, do corner element
+			 * s00+1, s10, s10+1 are off edge
+			 */
+			b[k] = ( ((a[s00  ]<<3) & b3) ) >> bit;
+			k += 1;
+		}
+	}
+}
+/* ######################################################################### */
+/*
+ * Do first quadtree reduction step on bit BIT of array A.
+ * Results put into B.
+ * 
+ */
+static void
+qtree_onebit64(LONGLONG a[], int n, int nx, int ny, unsigned char b[], int bit)
+{
+int i, j, k;
+LONGLONG b0, b1, b2, b3;
+int s10, s00;
+
+	/*
+	 * use selected bit to get amount to shift
+	 */
+	b0 = ((LONGLONG) 1)<<bit;
+	b1 = b0<<1;
+	b2 = b0<<2;
+	b3 = b0<<3;
+	k = 0;							/* k is index of b[i/2,j/2]	*/
+	for (i = 0; i<nx-1; i += 2) {
+		s00 = n*i;					/* s00 is index of a[i,j]	*/
+		s10 = s00+n;				/* s10 is index of a[i+1,j]	*/
+		for (j = 0; j<ny-1; j += 2) {
+			b[k] = (unsigned char) (( ( a[s10+1]     & b0)
+				   | ((a[s10  ]<<1) & b1)
+				   | ((a[s00+1]<<2) & b2)
+				   | ((a[s00  ]<<3) & b3) ) >> bit);
+			k += 1;
+			s00 += 2;
+			s10 += 2;
+		}
+		if (j < ny) {
+			/*
+			 * row size is odd, do last element in row
+			 * s00+1,s10+1 are off edge
+			 */
+			b[k] = (unsigned char) (( ((a[s10  ]<<1) & b1)
+				   | ((a[s00  ]<<3) & b3) ) >> bit);
+			k += 1;
+		}
+	}
+	if (i < nx) {
+		/*
+		 * column size is odd, do last row
+		 * s10,s10+1 are off edge
+		 */
+		s00 = n*i;
+		for (j = 0; j<ny-1; j += 2) {
+			b[k] = (unsigned char) (( ((a[s00+1]<<2) & b2)
+				   | ((a[s00  ]<<3) & b3) ) >> bit);
+			k += 1;
+			s00 += 2;
+		}
+		if (j < ny) {
+			/*
+			 * both row and column size are odd, do corner element
+			 * s00+1, s10, s10+1 are off edge
+			 */
+			b[k] = (unsigned char) (( ((a[s00  ]<<3) & b3) ) >> bit);
+			k += 1;
+		}
+	}
+}
+
+/* ######################################################################### */
+/*
+ * do one quadtree reduction step on array a
+ * results put into b (which may be the same as a)
+ */
+static void
+qtree_reduce(unsigned char a[], int n, int nx, int ny, unsigned char b[])
+{
+int i, j, k;
+int s10, s00;
+
+	k = 0;							/* k is index of b[i/2,j/2]	*/
+	for (i = 0; i<nx-1; i += 2) {
+		s00 = n*i;					/* s00 is index of a[i,j]	*/
+		s10 = s00+n;				/* s10 is index of a[i+1,j]	*/
+		for (j = 0; j<ny-1; j += 2) {
+			b[k] =	(a[s10+1] != 0)
+				| ( (a[s10  ] != 0) << 1)
+				| ( (a[s00+1] != 0) << 2)
+				| ( (a[s00  ] != 0) << 3);
+			k += 1;
+			s00 += 2;
+			s10 += 2;
+		}
+		if (j < ny) {
+			/*
+			 * row size is odd, do last element in row
+			 * s00+1,s10+1 are off edge
+			 */
+			b[k] =  ( (a[s10  ] != 0) << 1)
+				  | ( (a[s00  ] != 0) << 3);
+			k += 1;
+		}
+	}
+	if (i < nx) {
+		/*
+		 * column size is odd, do last row
+		 * s10,s10+1 are off edge
+		 */
+		s00 = n*i;
+		for (j = 0; j<ny-1; j += 2) {
+			b[k] =  ( (a[s00+1] != 0) << 2)
+				  | ( (a[s00  ] != 0) << 3);
+			k += 1;
+			s00 += 2;
+		}
+		if (j < ny) {
+			/*
+			 * both row and column size are odd, do corner element
+			 * s00+1, s10, s10+1 are off edge
+			 */
+			b[k] = ( (a[s00  ] != 0) << 3);
+			k += 1;
+		}
+	}
+}
+
+/* ######################################################################### */
+static void
+write_bdirect(char *outfile, int a[], int n,int nqx, int nqy, unsigned char scratch[], int bit)
+{
+
+	/*
+	 * Write the direct bitmap warning code
+	 */
+	output_nybble(outfile,0x0);
+	/*
+	 * Copy A to scratch array (again!), packing 4 bits/nybble
+	 */
+	qtree_onebit(a,n,nqx,nqy,scratch,bit);
+	/*
+	 * write to outfile
+	 */
+/*
+int i;
+	for (i = 0; i < ((nqx+1)/2) * ((nqy+1)/2); i++) {
+		output_nybble(outfile,scratch[i]);
+	}
+*/
+	output_nnybble(outfile, ((nqx+1)/2) * ((nqy+1)/2), scratch);
+
+}
+/* ######################################################################### */
+static void
+write_bdirect64(char *outfile, LONGLONG a[], int n,int nqx, int nqy, unsigned char scratch[], int bit)
+{
+
+	/*
+	 * Write the direct bitmap warning code
+	 */
+	output_nybble(outfile,0x0);
+	/*
+	 * Copy A to scratch array (again!), packing 4 bits/nybble
+	 */
+	qtree_onebit64(a,n,nqx,nqy,scratch,bit);
+	/*
+	 * write to outfile
+	 */
+/*
+int i;
+	for (i = 0; i < ((nqx+1)/2) * ((nqy+1)/2); i++) {
+		output_nybble(outfile,scratch[i]);
+	}
+*/
+	output_nnybble(outfile, ((nqx+1)/2) * ((nqy+1)/2), scratch);
+}
diff --git a/src/plugins/cfitsio/fits_hdecompress.c b/src/plugins/cfitsio/fits_hdecompress.c
new file mode 100644
index 0000000..2086d38
--- /dev/null
+++ b/src/plugins/cfitsio/fits_hdecompress.c
@@ -0,0 +1,2618 @@
+/*  #########################################################################
+These routines to apply the H-compress decompression algorithm to a 2-D Fits
+image were written by R. White at the STScI and were obtained from the STScI at
+http://www.stsci.edu/software/hcompress.html
+
+This source file is a concatination of the following sources files in the
+original distribution 
+  hinv.c 
+  hsmooth.c 
+  undigitize.c 
+  decode.c 
+  dodecode.c 
+  qtree_decode.c 
+  qread.c 
+  bit_input.c
+
+
+The following modifications have been made to the original code:
+
+  - commented out redundant "include" statements
+  - added the nextchar global variable 
+  - changed all the 'extern' declarations to 'static', since all the routines are in
+    the same source file
+  - changed the first parameter in decode (and in lower level routines from a file stream
+    to a char array
+  - modified the myread routine, and lower level byte reading routines,  to copy 
+    the input bytes to a char array, instead of reading them from a file stream
+  - changed the function declarations to the more modern ANSI C style
+  - changed calls to printf and perror to call the CFITSIO ffpmsg routine
+  - replace "exit" statements with "return" statements
+
+ ############################################################################  */
+ 
+#include <stdio.h>
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+#include "fitsio2.h"
+
+/* WDP added test to see if min and max are already defined */
+#ifndef min
+#define min(a,b)        (((a)<(b))?(a):(b))
+#endif
+#ifndef max
+#define max(a,b)        (((a)>(b))?(a):(b))
+#endif
+
+static long nextchar;
+
+static int decode(unsigned char *infile, int *a, int *nx, int *ny, int *scale);
+static int decode64(unsigned char *infile, LONGLONG *a, int *nx, int *ny, int *scale);
+static int hinv(int a[], int nx, int ny, int smooth ,int scale);
+static int hinv64(LONGLONG a[], int nx, int ny, int smooth ,int scale);
+static void undigitize(int a[], int nx, int ny, int scale);
+static void undigitize64(LONGLONG a[], int nx, int ny, int scale);
+static void unshuffle(int a[], int n, int n2, int tmp[]);
+static void unshuffle64(LONGLONG a[], int n, int n2, LONGLONG tmp[]);
+static void hsmooth(int a[], int nxtop, int nytop, int ny, int scale);
+static void hsmooth64(LONGLONG a[], int nxtop, int nytop, int ny, int scale);
+static void qread(unsigned char *infile,char *a, int n);
+static int  readint(unsigned char *infile);
+static LONGLONG readlonglong(unsigned char *infile);
+static int dodecode(unsigned char *infile, int a[], int nx, int ny, unsigned char nbitplanes[3]);
+static int dodecode64(unsigned char *infile, LONGLONG a[], int nx, int ny, unsigned char nbitplanes[3]);
+static int qtree_decode(unsigned char *infile, int a[], int n, int nqx, int nqy, int nbitplanes);
+static int qtree_decode64(unsigned char *infile, LONGLONG a[], int n, int nqx, int nqy, int nbitplanes);
+static void start_inputing_bits(void);
+static int input_bit(unsigned char *infile);
+static int input_nbits(unsigned char *infile, int n);
+/*  make input_nybble a separate routine, for added effiency */
+/* #define input_nybble(infile)	input_nbits(infile,4) */
+static int input_nybble(unsigned char *infile);
+static int input_nnybble(unsigned char *infile, int n, unsigned char *array);
+
+static void qtree_expand(unsigned char *infile, unsigned char a[], int nx, int ny, unsigned char b[]);
+static void qtree_bitins(unsigned char a[], int nx, int ny, int b[], int n, int bit);
+static void qtree_bitins64(unsigned char a[], int nx, int ny, LONGLONG b[], int n, int bit);
+static void qtree_copy(unsigned char a[], int nx, int ny, unsigned char b[], int n);
+static void read_bdirect(unsigned char *infile, int a[], int n, int nqx, int nqy, unsigned char scratch[], int bit);
+static void read_bdirect64(unsigned char *infile, LONGLONG a[], int n, int nqx, int nqy, unsigned char scratch[], int bit);
+static int  input_huffman(unsigned char *infile);
+
+/* ---------------------------------------------------------------------- */
+int fits_hdecompress(unsigned char *input, int smooth, int *a, int *ny, int *nx, 
+                     int *scale, int *status)
+{
+  /* 
+     decompress the input byte stream using the H-compress algorithm
+  
+   input  - input array of compressed bytes
+   a - pre-allocated array to hold the output uncompressed image
+   nx - returned X axis size
+   ny - returned Y axis size
+
+ NOTE: the nx and ny dimensions as defined within this code are reversed from
+ the usual FITS notation.  ny is the fastest varying dimension, which is
+ usually considered the X axis in the FITS image display
+
+  */
+int stat;
+
+  if (*status > 0) return(*status);
+
+	/* decode the input array */
+
+        FFLOCK;  /* decode uses the nextchar global variable */
+	stat = decode(input, a, nx, ny, scale);
+        FFUNLOCK;
+
+        *status = stat;
+	if (stat) return(*status);
+	
+	/*
+	 * Un-Digitize
+	 */
+	undigitize(a, *nx, *ny, *scale);
+
+	/*
+	 * Inverse H-transform
+	 */
+	stat = hinv(a, *nx, *ny, smooth, *scale);
+        *status = stat;
+	
+  return(*status);
+}
+/* ---------------------------------------------------------------------- */
+int fits_hdecompress64(unsigned char *input, int smooth, LONGLONG *a, int *ny, int *nx, 
+                     int *scale, int *status)
+{
+  /* 
+     decompress the input byte stream using the H-compress algorithm
+  
+   input  - input array of compressed bytes
+   a - pre-allocated array to hold the output uncompressed image
+   nx - returned X axis size
+   ny - returned Y axis size
+
+ NOTE: the nx and ny dimensions as defined within this code are reversed from
+ the usual FITS notation.  ny is the fastest varying dimension, which is
+ usually considered the X axis in the FITS image display
+
+  */
+  int stat, *iarray, ii, nval;
+
+  if (*status > 0) return(*status);
+
+	/* decode the input array */
+
+        FFLOCK;  /* decode uses the nextchar global variable */
+	stat = decode64(input, a, nx, ny, scale);
+        FFUNLOCK;
+
+        *status = stat;
+	if (stat) return(*status);
+	
+	/*
+	 * Un-Digitize
+	 */
+	undigitize64(a, *nx, *ny, *scale);
+
+	/*
+	 * Inverse H-transform
+	 */
+	stat = hinv64(a, *nx, *ny, smooth, *scale);
+
+        *status = stat;
+	
+         /* pack the I*8 values back into an I*4 array */
+        iarray = (int *) a;
+	nval = (*nx) * (*ny);
+
+	for (ii = 0; ii < nval; ii++)
+	   iarray[ii] = (int) a[ii];	
+
+  return(*status);
+}
+
+/*  ############################################################################  */
+/*  ############################################################################  */
+
+/* Copyright (c) 1993 Association of Universities for Research 
+ * in Astronomy. All rights reserved. Produced under National   
+ * Aeronautics and Space Administration Contract No. NAS5-26555.
+ */
+/* hinv.c   Inverse H-transform of NX x NY integer image
+ *
+ * Programmer: R. White		Date: 23 July 1993
+ */
+
+/*  ############################################################################  */
+static int 
+hinv(int a[], int nx, int ny, int smooth ,int scale)
+/*
+int smooth;    0 for no smoothing, else smooth during inversion 
+int scale;     used if smoothing is specified 
+*/
+{
+int nmax, log2n, i, j, k;
+int nxtop,nytop,nxf,nyf,c;
+int oddx,oddy;
+int shift, bit0, bit1, bit2, mask0, mask1, mask2,
+	prnd0, prnd1, prnd2, nrnd0, nrnd1, nrnd2, lowbit0, lowbit1;
+int h0, hx, hy, hc;
+int s10, s00;
+int *tmp;
+
+	/*
+	 * log2n is log2 of max(nx,ny) rounded up to next power of 2
+	 */
+	nmax = (nx>ny) ? nx : ny;
+	log2n = (int) (log((float) nmax)/log(2.0)+0.5);
+	if ( nmax > (1<<log2n) ) {
+		log2n += 1;
+	}
+	/*
+	 * get temporary storage for shuffling elements
+	 */  
+	tmp = (int *) malloc(((nmax+1)/2)*sizeof(int));
+	if (tmp == (int *) NULL) {
+		ffpmsg("hinv: insufficient memory");
+		return(DATA_DECOMPRESSION_ERR);
+	}
+	/*
+	 * set up masks, rounding parameters
+	 */
+	shift  = 1;
+	bit0   = 1 << (log2n - 1);
+	bit1   = bit0 << 1;
+	bit2   = bit0 << 2;
+	mask0  = -bit0;
+	mask1  = mask0 << 1;
+	mask2  = mask0 << 2;
+	prnd0  = bit0 >> 1;
+	prnd1  = bit1 >> 1;
+	prnd2  = bit2 >> 1;
+	nrnd0  = prnd0 - 1;
+	nrnd1  = prnd1 - 1;
+	nrnd2  = prnd2 - 1;
+	/*
+	 * round h0 to multiple of bit2
+	 */
+	a[0] = (a[0] + ((a[0] >= 0) ? prnd2 : nrnd2)) & mask2;
+	/*
+	 * do log2n expansions
+	 *
+	 * We're indexing a as a 2-D array with dimensions (nx,ny).
+	 */
+	nxtop = 1;
+	nytop = 1;
+	nxf = nx;
+	nyf = ny;
+	c = 1<<log2n;
+	for (k = log2n-1; k>=0; k--) {
+		/*
+		 * this somewhat cryptic code generates the sequence
+		 * ntop[k-1] = (ntop[k]+1)/2, where ntop[log2n] = n
+		 */
+		c = c>>1;
+		nxtop = nxtop<<1;
+		nytop = nytop<<1;
+		if (nxf <= c) { nxtop -= 1; } else { nxf -= c; }
+		if (nyf <= c) { nytop -= 1; } else { nyf -= c; }
+		/*
+		 * double shift and fix nrnd0 (because prnd0=0) on last pass
+		 */
+		if (k == 0) {
+			nrnd0 = 0;
+			shift = 2;
+		}
+		/*
+		 * unshuffle in each dimension to interleave coefficients
+		 */
+		for (i = 0; i<nxtop; i++) {
+			unshuffle(&a[ny*i],nytop,1,tmp);
+		}
+		for (j = 0; j<nytop; j++) {
+			unshuffle(&a[j],nxtop,ny,tmp);
+		}
+		/*
+		 * smooth by interpolating coefficients if SMOOTH != 0
+		 */
+		if (smooth) hsmooth(a,nxtop,nytop,ny,scale);
+		oddx = nxtop % 2;
+		oddy = nytop % 2;
+		for (i = 0; i<nxtop-oddx; i += 2) {
+			s00 = ny*i;				/* s00 is index of a[i,j]	*/
+			s10 = s00+ny;			/* s10 is index of a[i+1,j]	*/
+			for (j = 0; j<nytop-oddy; j += 2) {
+				h0 = a[s00  ];
+				hx = a[s10  ];
+				hy = a[s00+1];
+				hc = a[s10+1];
+				/*
+				 * round hx and hy to multiple of bit1, hc to multiple of bit0
+				 * h0 is already a multiple of bit2
+				 */
+				hx = (hx + ((hx >= 0) ? prnd1 : nrnd1)) & mask1;
+				hy = (hy + ((hy >= 0) ? prnd1 : nrnd1)) & mask1;
+				hc = (hc + ((hc >= 0) ? prnd0 : nrnd0)) & mask0;
+				/*
+				 * propagate bit0 of hc to hx,hy
+				 */
+				lowbit0 = hc & bit0;
+				hx = (hx >= 0) ? (hx - lowbit0) : (hx + lowbit0);
+				hy = (hy >= 0) ? (hy - lowbit0) : (hy + lowbit0);
+				/*
+				 * Propagate bits 0 and 1 of hc,hx,hy to h0.
+				 * This could be simplified if we assume h0>0, but then
+				 * the inversion would not be lossless for images with
+				 * negative pixels.
+				 */
+				lowbit1 = (hc ^ hx ^ hy) & bit1;
+				h0 = (h0 >= 0)
+					? (h0 + lowbit0 - lowbit1)
+					: (h0 + ((lowbit0 == 0) ? lowbit1 : (lowbit0-lowbit1)));
+				/*
+				 * Divide sums by 2 (4 last time)
+				 */
+				a[s10+1] = (h0 + hx + hy + hc) >> shift;
+				a[s10  ] = (h0 + hx - hy - hc) >> shift;
+				a[s00+1] = (h0 - hx + hy - hc) >> shift;
+				a[s00  ] = (h0 - hx - hy + hc) >> shift;
+				s00 += 2;
+				s10 += 2;
+			}
+			if (oddy) {
+				/*
+				 * do last element in row if row length is odd
+				 * s00+1, s10+1 are off edge
+				 */
+				h0 = a[s00  ];
+				hx = a[s10  ];
+				hx = ((hx >= 0) ? (hx+prnd1) : (hx+nrnd1)) & mask1;
+				lowbit1 = hx & bit1;
+				h0 = (h0 >= 0) ? (h0 - lowbit1) : (h0 + lowbit1);
+				a[s10  ] = (h0 + hx) >> shift;
+				a[s00  ] = (h0 - hx) >> shift;
+			}
+		}
+		if (oddx) {
+			/*
+			 * do last row if column length is odd
+			 * s10, s10+1 are off edge
+			 */
+			s00 = ny*i;
+			for (j = 0; j<nytop-oddy; j += 2) {
+				h0 = a[s00  ];
+				hy = a[s00+1];
+				hy = ((hy >= 0) ? (hy+prnd1) : (hy+nrnd1)) & mask1;
+				lowbit1 = hy & bit1;
+				h0 = (h0 >= 0) ? (h0 - lowbit1) : (h0 + lowbit1);
+				a[s00+1] = (h0 + hy) >> shift;
+				a[s00  ] = (h0 - hy) >> shift;
+				s00 += 2;
+			}
+			if (oddy) {
+				/*
+				 * do corner element if both row and column lengths are odd
+				 * s00+1, s10, s10+1 are off edge
+				 */
+				h0 = a[s00  ];
+				a[s00  ] = h0 >> shift;
+			}
+		}
+		/*
+		 * divide all the masks and rounding values by 2
+		 */
+		bit2 = bit1;
+		bit1 = bit0;
+		bit0 = bit0 >> 1;
+		mask1 = mask0;
+		mask0 = mask0 >> 1;
+		prnd1 = prnd0;
+		prnd0 = prnd0 >> 1;
+		nrnd1 = nrnd0;
+		nrnd0 = prnd0 - 1;
+	}
+	free(tmp);
+	return(0);
+}
+/*  ############################################################################  */
+static int 
+hinv64(LONGLONG a[], int nx, int ny, int smooth ,int scale)
+/*
+int smooth;    0 for no smoothing, else smooth during inversion 
+int scale;     used if smoothing is specified 
+*/
+{
+int nmax, log2n, i, j, k;
+int nxtop,nytop,nxf,nyf,c;
+int oddx,oddy;
+int shift;
+LONGLONG mask0, mask1, mask2, prnd0, prnd1, prnd2, bit0, bit1, bit2;
+LONGLONG  nrnd0, nrnd1, nrnd2, lowbit0, lowbit1;
+LONGLONG h0, hx, hy, hc;
+int s10, s00;
+LONGLONG *tmp;
+
+	/*
+	 * log2n is log2 of max(nx,ny) rounded up to next power of 2
+	 */
+	nmax = (nx>ny) ? nx : ny;
+	log2n = (int) (log((float) nmax)/log(2.0)+0.5);
+	if ( nmax > (1<<log2n) ) {
+		log2n += 1;
+	}
+	/*
+	 * get temporary storage for shuffling elements
+	 */  
+	tmp = (LONGLONG *) malloc(((nmax+1)/2)*sizeof(LONGLONG));
+	if (tmp == (LONGLONG *) NULL) {
+		ffpmsg("hinv64: insufficient memory");
+		return(DATA_DECOMPRESSION_ERR);
+	}
+	/*
+	 * set up masks, rounding parameters
+	 */
+	shift  = 1;
+	bit0   = ((LONGLONG) 1) << (log2n - 1);
+	bit1   = bit0 << 1;
+	bit2   = bit0 << 2;
+	mask0  = -bit0;
+	mask1  = mask0 << 1;
+	mask2  = mask0 << 2;
+	prnd0  = bit0 >> 1;
+	prnd1  = bit1 >> 1;
+	prnd2  = bit2 >> 1;
+	nrnd0  = prnd0 - 1;
+	nrnd1  = prnd1 - 1;
+	nrnd2  = prnd2 - 1;
+	/*
+	 * round h0 to multiple of bit2
+	 */
+	a[0] = (a[0] + ((a[0] >= 0) ? prnd2 : nrnd2)) & mask2;
+	/*
+	 * do log2n expansions
+	 *
+	 * We're indexing a as a 2-D array with dimensions (nx,ny).
+	 */
+	nxtop = 1;
+	nytop = 1;
+	nxf = nx;
+	nyf = ny;
+	c = 1<<log2n;
+	for (k = log2n-1; k>=0; k--) {
+		/*
+		 * this somewhat cryptic code generates the sequence
+		 * ntop[k-1] = (ntop[k]+1)/2, where ntop[log2n] = n
+		 */
+		c = c>>1;
+		nxtop = nxtop<<1;
+		nytop = nytop<<1;
+		if (nxf <= c) { nxtop -= 1; } else { nxf -= c; }
+		if (nyf <= c) { nytop -= 1; } else { nyf -= c; }
+		/*
+		 * double shift and fix nrnd0 (because prnd0=0) on last pass
+		 */
+		if (k == 0) {
+			nrnd0 = 0;
+			shift = 2;
+		}
+		/*
+		 * unshuffle in each dimension to interleave coefficients
+		 */
+		for (i = 0; i<nxtop; i++) {
+			unshuffle64(&a[ny*i],nytop,1,tmp);
+		}
+		for (j = 0; j<nytop; j++) {
+			unshuffle64(&a[j],nxtop,ny,tmp);
+		}
+		/*
+		 * smooth by interpolating coefficients if SMOOTH != 0
+		 */
+		if (smooth) hsmooth64(a,nxtop,nytop,ny,scale);
+		oddx = nxtop % 2;
+		oddy = nytop % 2;
+		for (i = 0; i<nxtop-oddx; i += 2) {
+			s00 = ny*i;				/* s00 is index of a[i,j]	*/
+			s10 = s00+ny;			/* s10 is index of a[i+1,j]	*/
+			for (j = 0; j<nytop-oddy; j += 2) {
+				h0 = a[s00  ];
+				hx = a[s10  ];
+				hy = a[s00+1];
+				hc = a[s10+1];
+				/*
+				 * round hx and hy to multiple of bit1, hc to multiple of bit0
+				 * h0 is already a multiple of bit2
+				 */
+				hx = (hx + ((hx >= 0) ? prnd1 : nrnd1)) & mask1;
+				hy = (hy + ((hy >= 0) ? prnd1 : nrnd1)) & mask1;
+				hc = (hc + ((hc >= 0) ? prnd0 : nrnd0)) & mask0;
+				/*
+				 * propagate bit0 of hc to hx,hy
+				 */
+				lowbit0 = hc & bit0;
+				hx = (hx >= 0) ? (hx - lowbit0) : (hx + lowbit0);
+				hy = (hy >= 0) ? (hy - lowbit0) : (hy + lowbit0);
+				/*
+				 * Propagate bits 0 and 1 of hc,hx,hy to h0.
+				 * This could be simplified if we assume h0>0, but then
+				 * the inversion would not be lossless for images with
+				 * negative pixels.
+				 */
+				lowbit1 = (hc ^ hx ^ hy) & bit1;
+				h0 = (h0 >= 0)
+					? (h0 + lowbit0 - lowbit1)
+					: (h0 + ((lowbit0 == 0) ? lowbit1 : (lowbit0-lowbit1)));
+				/*
+				 * Divide sums by 2 (4 last time)
+				 */
+				a[s10+1] = (h0 + hx + hy + hc) >> shift;
+				a[s10  ] = (h0 + hx - hy - hc) >> shift;
+				a[s00+1] = (h0 - hx + hy - hc) >> shift;
+				a[s00  ] = (h0 - hx - hy + hc) >> shift;
+				s00 += 2;
+				s10 += 2;
+			}
+			if (oddy) {
+				/*
+				 * do last element in row if row length is odd
+				 * s00+1, s10+1 are off edge
+				 */
+				h0 = a[s00  ];
+				hx = a[s10  ];
+				hx = ((hx >= 0) ? (hx+prnd1) : (hx+nrnd1)) & mask1;
+				lowbit1 = hx & bit1;
+				h0 = (h0 >= 0) ? (h0 - lowbit1) : (h0 + lowbit1);
+				a[s10  ] = (h0 + hx) >> shift;
+				a[s00  ] = (h0 - hx) >> shift;
+			}
+		}
+		if (oddx) {
+			/*
+			 * do last row if column length is odd
+			 * s10, s10+1 are off edge
+			 */
+			s00 = ny*i;
+			for (j = 0; j<nytop-oddy; j += 2) {
+				h0 = a[s00  ];
+				hy = a[s00+1];
+				hy = ((hy >= 0) ? (hy+prnd1) : (hy+nrnd1)) & mask1;
+				lowbit1 = hy & bit1;
+				h0 = (h0 >= 0) ? (h0 - lowbit1) : (h0 + lowbit1);
+				a[s00+1] = (h0 + hy) >> shift;
+				a[s00  ] = (h0 - hy) >> shift;
+				s00 += 2;
+			}
+			if (oddy) {
+				/*
+				 * do corner element if both row and column lengths are odd
+				 * s00+1, s10, s10+1 are off edge
+				 */
+				h0 = a[s00  ];
+				a[s00  ] = h0 >> shift;
+			}
+		}
+		/*
+		 * divide all the masks and rounding values by 2
+		 */
+		bit2 = bit1;
+		bit1 = bit0;
+		bit0 = bit0 >> 1;
+		mask1 = mask0;
+		mask0 = mask0 >> 1;
+		prnd1 = prnd0;
+		prnd0 = prnd0 >> 1;
+		nrnd1 = nrnd0;
+		nrnd0 = prnd0 - 1;
+	}
+	free(tmp);
+	return(0);
+}
+
+/*  ############################################################################  */
+static void
+unshuffle(int a[], int n, int n2, int tmp[])
+/*
+int a[];	 array to shuffle					
+int n;		 number of elements to shuffle	
+int n2;		 second dimension					
+int tmp[];	 scratch storage					
+*/
+{
+int i;
+int nhalf;
+int *p1, *p2, *pt;
+ 
+	/*
+	 * copy 2nd half of array to tmp
+	 */
+	nhalf = (n+1)>>1;
+	pt = tmp;
+	p1 = &a[n2*nhalf];				/* pointer to a[i]			*/
+	for (i=nhalf; i<n; i++) {
+		*pt = *p1;
+		p1 += n2;
+		pt += 1;
+	}
+	/*
+	 * distribute 1st half of array to even elements
+	 */
+	p2 = &a[ n2*(nhalf-1) ];		/* pointer to a[i]			*/
+	p1 = &a[(n2*(nhalf-1))<<1];		/* pointer to a[2*i]		*/
+	for (i=nhalf-1; i >= 0; i--) {
+		*p1 = *p2;
+		p2 -= n2;
+		p1 -= (n2+n2);
+	}
+	/*
+	 * now distribute 2nd half of array (in tmp) to odd elements
+	 */
+	pt = tmp;
+	p1 = &a[n2];					/* pointer to a[i]			*/
+	for (i=1; i<n; i += 2) {
+		*p1 = *pt;
+		p1 += (n2+n2);
+		pt += 1;
+	}
+}
+/*  ############################################################################  */
+static void
+unshuffle64(LONGLONG a[], int n, int n2, LONGLONG tmp[])
+/*
+LONGLONG a[];	 array to shuffle					
+int n;		 number of elements to shuffle	
+int n2;		 second dimension					
+LONGLONG tmp[];	 scratch storage					
+*/
+{
+int i;
+int nhalf;
+LONGLONG *p1, *p2, *pt;
+ 
+	/*
+	 * copy 2nd half of array to tmp
+	 */
+	nhalf = (n+1)>>1;
+	pt = tmp;
+	p1 = &a[n2*nhalf];				/* pointer to a[i]			*/
+	for (i=nhalf; i<n; i++) {
+		*pt = *p1;
+		p1 += n2;
+		pt += 1;
+	}
+	/*
+	 * distribute 1st half of array to even elements
+	 */
+	p2 = &a[ n2*(nhalf-1) ];		/* pointer to a[i]			*/
+	p1 = &a[(n2*(nhalf-1))<<1];		/* pointer to a[2*i]		*/
+	for (i=nhalf-1; i >= 0; i--) {
+		*p1 = *p2;
+		p2 -= n2;
+		p1 -= (n2+n2);
+	}
+	/*
+	 * now distribute 2nd half of array (in tmp) to odd elements
+	 */
+	pt = tmp;
+	p1 = &a[n2];					/* pointer to a[i]			*/
+	for (i=1; i<n; i += 2) {
+		*p1 = *pt;
+		p1 += (n2+n2);
+		pt += 1;
+	}
+}
+
+/*  ############################################################################  */
+/*  ############################################################################  */
+
+/* Copyright (c) 1993 Association of Universities for Research 
+ * in Astronomy. All rights reserved. Produced under National   
+ * Aeronautics and Space Administration Contract No. NAS5-26555.
+ */
+/* hsmooth.c	Smooth H-transform image by adjusting coefficients toward
+ *				interpolated values
+ *
+ * Programmer: R. White		Date: 13 April 1992
+ */
+
+/*  ############################################################################  */
+static void 
+hsmooth(int a[], int nxtop, int nytop, int ny, int scale)
+/*
+int a[];			 array of H-transform coefficients		
+int nxtop,nytop;	 size of coefficient block to use			
+int ny;				 actual 1st dimension of array			
+int scale;			 truncation scale factor that was used	
+*/
+{
+int i, j;
+int ny2, s10, s00, diff, dmax, dmin, s, smax;
+int hm, h0, hp, hmm, hpm, hmp, hpp, hx2, hy2;
+int m1,m2;
+
+	/*
+	 * Maximum change in coefficients is determined by scale factor.
+	 * Since we rounded during division (see digitize.c), the biggest
+	 * permitted change is scale/2.
+	 */
+	smax = (scale >> 1);
+	if (smax <= 0) return;
+	ny2 = ny << 1;
+	/*
+	 * We're indexing a as a 2-D array with dimensions (nxtop,ny) of which
+	 * only (nxtop,nytop) are used.  The coefficients on the edge of the
+	 * array are not adjusted (which is why the loops below start at 2
+	 * instead of 0 and end at nxtop-2 instead of nxtop.)
+	 */
+	/*
+	 * Adjust x difference hx
+	 */
+	for (i = 2; i<nxtop-2; i += 2) {
+		s00 = ny*i;				/* s00 is index of a[i,j]	*/
+		s10 = s00+ny;			/* s10 is index of a[i+1,j]	*/
+		for (j = 0; j<nytop; j += 2) {
+			/*
+			 * hp is h0 (mean value) in next x zone, hm is h0 in previous x zone
+			 */
+			hm = a[s00-ny2];
+			h0 = a[s00];
+			hp = a[s00+ny2];
+			/*
+			 * diff = 8 * hx slope that would match h0 in neighboring zones
+			 */
+			diff = hp-hm;
+			/*
+			 * monotonicity constraints on diff
+			 */
+			dmax = max( min( (hp-h0), (h0-hm) ), 0 ) << 2;
+			dmin = min( max( (hp-h0), (h0-hm) ), 0 ) << 2;
+			/*
+			 * if monotonicity would set slope = 0 then don't change hx.
+			 * note dmax>=0, dmin<=0.
+			 */
+			if (dmin < dmax) {
+				diff = max( min(diff, dmax), dmin);
+				/*
+				 * Compute change in slope limited to range +/- smax.
+				 * Careful with rounding negative numbers when using
+				 * shift for divide by 8.
+				 */
+				s = diff-(a[s10]<<3);
+				s = (s>=0) ? (s>>3) : ((s+7)>>3) ;
+				s = max( min(s, smax), -smax);
+				a[s10] = a[s10]+s;
+			}
+			s00 += 2;
+			s10 += 2;
+		}
+	}
+	/*
+	 * Adjust y difference hy
+	 */
+	for (i = 0; i<nxtop; i += 2) {
+		s00 = ny*i+2;
+		s10 = s00+ny;
+		for (j = 2; j<nytop-2; j += 2) {
+			hm = a[s00-2];
+			h0 = a[s00];
+			hp = a[s00+2];
+			diff = hp-hm;
+			dmax = max( min( (hp-h0), (h0-hm) ), 0 ) << 2;
+			dmin = min( max( (hp-h0), (h0-hm) ), 0 ) << 2;
+			if (dmin < dmax) {
+				diff = max( min(diff, dmax), dmin);
+				s = diff-(a[s00+1]<<3);
+				s = (s>=0) ? (s>>3) : ((s+7)>>3) ;
+				s = max( min(s, smax), -smax);
+				a[s00+1] = a[s00+1]+s;
+			}
+			s00 += 2;
+			s10 += 2;
+		}
+	}
+	/*
+	 * Adjust curvature difference hc
+	 */
+	for (i = 2; i<nxtop-2; i += 2) {
+		s00 = ny*i+2;
+		s10 = s00+ny;
+		for (j = 2; j<nytop-2; j += 2) {
+			/*
+			 * ------------------    y
+			 * | hmp |    | hpp |    |
+			 * ------------------    |
+			 * |     | h0 |     |    |
+			 * ------------------    -------x
+			 * | hmm |    | hpm |
+			 * ------------------
+			 */
+			hmm = a[s00-ny2-2];
+			hpm = a[s00+ny2-2];
+			hmp = a[s00-ny2+2];
+			hpp = a[s00+ny2+2];
+			h0  = a[s00];
+			/*
+			 * diff = 64 * hc value that would match h0 in neighboring zones
+			 */
+			diff = hpp + hmm - hmp - hpm;
+			/*
+			 * 2 times x,y slopes in this zone
+			 */
+			hx2 = a[s10  ]<<1;
+			hy2 = a[s00+1]<<1;
+			/*
+			 * monotonicity constraints on diff
+			 */
+			m1 = min(max(hpp-h0,0)-hx2-hy2, max(h0-hpm,0)+hx2-hy2);
+			m2 = min(max(h0-hmp,0)-hx2+hy2, max(hmm-h0,0)+hx2+hy2);
+			dmax = min(m1,m2) << 4;
+			m1 = max(min(hpp-h0,0)-hx2-hy2, min(h0-hpm,0)+hx2-hy2);
+			m2 = max(min(h0-hmp,0)-hx2+hy2, min(hmm-h0,0)+hx2+hy2);
+			dmin = max(m1,m2) << 4;
+			/*
+			 * if monotonicity would set slope = 0 then don't change hc.
+			 * note dmax>=0, dmin<=0.
+			 */
+			if (dmin < dmax) {
+				diff = max( min(diff, dmax), dmin);
+				/*
+				 * Compute change in slope limited to range +/- smax.
+				 * Careful with rounding negative numbers when using
+				 * shift for divide by 64.
+				 */
+				s = diff-(a[s10+1]<<6);
+				s = (s>=0) ? (s>>6) : ((s+63)>>6) ;
+				s = max( min(s, smax), -smax);
+				a[s10+1] = a[s10+1]+s;
+			}
+			s00 += 2;
+			s10 += 2;
+		}
+	}
+}
+/*  ############################################################################  */
+static void 
+hsmooth64(LONGLONG a[], int nxtop, int nytop, int ny, int scale)
+/*
+LONGLONG a[];			 array of H-transform coefficients		
+int nxtop,nytop;	 size of coefficient block to use			
+int ny;				 actual 1st dimension of array			
+int scale;			 truncation scale factor that was used	
+*/
+{
+int i, j;
+int ny2, s10, s00;
+LONGLONG hm, h0, hp, hmm, hpm, hmp, hpp, hx2, hy2, diff, dmax, dmin, s, smax, m1, m2;
+
+	/*
+	 * Maximum change in coefficients is determined by scale factor.
+	 * Since we rounded during division (see digitize.c), the biggest
+	 * permitted change is scale/2.
+	 */
+	smax = (scale >> 1);
+	if (smax <= 0) return;
+	ny2 = ny << 1;
+	/*
+	 * We're indexing a as a 2-D array with dimensions (nxtop,ny) of which
+	 * only (nxtop,nytop) are used.  The coefficients on the edge of the
+	 * array are not adjusted (which is why the loops below start at 2
+	 * instead of 0 and end at nxtop-2 instead of nxtop.)
+	 */
+	/*
+	 * Adjust x difference hx
+	 */
+	for (i = 2; i<nxtop-2; i += 2) {
+		s00 = ny*i;				/* s00 is index of a[i,j]	*/
+		s10 = s00+ny;			/* s10 is index of a[i+1,j]	*/
+		for (j = 0; j<nytop; j += 2) {
+			/*
+			 * hp is h0 (mean value) in next x zone, hm is h0 in previous x zone
+			 */
+			hm = a[s00-ny2];
+			h0 = a[s00];
+			hp = a[s00+ny2];
+			/*
+			 * diff = 8 * hx slope that would match h0 in neighboring zones
+			 */
+			diff = hp-hm;
+			/*
+			 * monotonicity constraints on diff
+			 */
+			dmax = max( min( (hp-h0), (h0-hm) ), 0 ) << 2;
+			dmin = min( max( (hp-h0), (h0-hm) ), 0 ) << 2;
+			/*
+			 * if monotonicity would set slope = 0 then don't change hx.
+			 * note dmax>=0, dmin<=0.
+			 */
+			if (dmin < dmax) {
+				diff = max( min(diff, dmax), dmin);
+				/*
+				 * Compute change in slope limited to range +/- smax.
+				 * Careful with rounding negative numbers when using
+				 * shift for divide by 8.
+				 */
+				s = diff-(a[s10]<<3);
+				s = (s>=0) ? (s>>3) : ((s+7)>>3) ;
+				s = max( min(s, smax), -smax);
+				a[s10] = a[s10]+s;
+			}
+			s00 += 2;
+			s10 += 2;
+		}
+	}
+	/*
+	 * Adjust y difference hy
+	 */
+	for (i = 0; i<nxtop; i += 2) {
+		s00 = ny*i+2;
+		s10 = s00+ny;
+		for (j = 2; j<nytop-2; j += 2) {
+			hm = a[s00-2];
+			h0 = a[s00];
+			hp = a[s00+2];
+			diff = hp-hm;
+			dmax = max( min( (hp-h0), (h0-hm) ), 0 ) << 2;
+			dmin = min( max( (hp-h0), (h0-hm) ), 0 ) << 2;
+			if (dmin < dmax) {
+				diff = max( min(diff, dmax), dmin);
+				s = diff-(a[s00+1]<<3);
+				s = (s>=0) ? (s>>3) : ((s+7)>>3) ;
+				s = max( min(s, smax), -smax);
+				a[s00+1] = a[s00+1]+s;
+			}
+			s00 += 2;
+			s10 += 2;
+		}
+	}
+	/*
+	 * Adjust curvature difference hc
+	 */
+	for (i = 2; i<nxtop-2; i += 2) {
+		s00 = ny*i+2;
+		s10 = s00+ny;
+		for (j = 2; j<nytop-2; j += 2) {
+			/*
+			 * ------------------    y
+			 * | hmp |    | hpp |    |
+			 * ------------------    |
+			 * |     | h0 |     |    |
+			 * ------------------    -------x
+			 * | hmm |    | hpm |
+			 * ------------------
+			 */
+			hmm = a[s00-ny2-2];
+			hpm = a[s00+ny2-2];
+			hmp = a[s00-ny2+2];
+			hpp = a[s00+ny2+2];
+			h0  = a[s00];
+			/*
+			 * diff = 64 * hc value that would match h0 in neighboring zones
+			 */
+			diff = hpp + hmm - hmp - hpm;
+			/*
+			 * 2 times x,y slopes in this zone
+			 */
+			hx2 = a[s10  ]<<1;
+			hy2 = a[s00+1]<<1;
+			/*
+			 * monotonicity constraints on diff
+			 */
+			m1 = min(max(hpp-h0,0)-hx2-hy2, max(h0-hpm,0)+hx2-hy2);
+			m2 = min(max(h0-hmp,0)-hx2+hy2, max(hmm-h0,0)+hx2+hy2);
+			dmax = min(m1,m2) << 4;
+			m1 = max(min(hpp-h0,0)-hx2-hy2, min(h0-hpm,0)+hx2-hy2);
+			m2 = max(min(h0-hmp,0)-hx2+hy2, min(hmm-h0,0)+hx2+hy2);
+			dmin = max(m1,m2) << 4;
+			/*
+			 * if monotonicity would set slope = 0 then don't change hc.
+			 * note dmax>=0, dmin<=0.
+			 */
+			if (dmin < dmax) {
+				diff = max( min(diff, dmax), dmin);
+				/*
+				 * Compute change in slope limited to range +/- smax.
+				 * Careful with rounding negative numbers when using
+				 * shift for divide by 64.
+				 */
+				s = diff-(a[s10+1]<<6);
+				s = (s>=0) ? (s>>6) : ((s+63)>>6) ;
+				s = max( min(s, smax), -smax);
+				a[s10+1] = a[s10+1]+s;
+			}
+			s00 += 2;
+			s10 += 2;
+		}
+	}
+}
+
+
+/*  ############################################################################  */
+/*  ############################################################################  */
+/* Copyright (c) 1993 Association of Universities for Research 
+ * in Astronomy. All rights reserved. Produced under National   
+ * Aeronautics and Space Administration Contract No. NAS5-26555.
+ */
+/* undigitize.c		undigitize H-transform
+ *
+ * Programmer: R. White		Date: 9 May 1991
+ */
+
+/*  ############################################################################  */
+static void
+undigitize(int a[], int nx, int ny, int scale)
+{
+int *p;
+
+	/*
+	 * multiply by scale
+	 */
+	if (scale <= 1) return;
+	for (p=a; p <= &a[nx*ny-1]; p++) *p = (*p)*scale;
+}
+/*  ############################################################################  */
+static void
+undigitize64(LONGLONG a[], int nx, int ny, int scale)
+{
+LONGLONG *p, scale64;
+
+	/*
+	 * multiply by scale
+	 */
+	if (scale <= 1) return;
+	scale64 = (LONGLONG) scale;   /* use a 64-bit int for efficiency in the big loop */
+	
+	for (p=a; p <= &a[nx*ny-1]; p++) *p = (*p)*scale64;
+}
+
+/*  ############################################################################  */
+/*  ############################################################################  */
+/* Copyright (c) 1993 Association of Universities for Research 
+ * in Astronomy. All rights reserved. Produced under National   
+ * Aeronautics and Space Administration Contract No. NAS5-26555.
+ */
+/* decode.c		read codes from infile and construct array
+ *
+ * Programmer: R. White		Date: 2 February 1994
+ */
+
+
+static char code_magic[2] = { (char)0xDD, (char)0x99 };
+
+/*  ############################################################################  */
+static int decode(unsigned char *infile, int *a, int *nx, int *ny, int *scale)
+/*
+char *infile;				 input file							
+int  *a;				 address of output array [nx][ny]		
+int  *nx,*ny;				 size of output array					
+int  *scale;				 scale factor for digitization		
+*/
+{
+LONGLONG sumall;
+int nel, stat;
+unsigned char nbitplanes[3];
+char tmagic[2];
+
+	/* initialize the byte read position to the beginning of the array */;
+	nextchar = 0;
+	
+	/*
+	 * File starts either with special 2-byte magic code or with
+	 * FITS keyword "SIMPLE  ="
+	 */
+	qread(infile, tmagic, sizeof(tmagic));
+	/*
+	 * check for correct magic code value
+	 */
+	if (memcmp(tmagic,code_magic,sizeof(code_magic)) != 0) {
+		ffpmsg("bad file format");
+		return(DATA_DECOMPRESSION_ERR);
+	}
+	*nx =readint(infile);				/* x size of image			*/
+	*ny =readint(infile);				/* y size of image			*/
+	*scale=readint(infile);				/* scale factor for digitization	*/
+	
+	nel = (*nx) * (*ny);
+
+	/* sum of all pixels	*/
+	sumall=readlonglong(infile);
+	/* # bits in quadrants	*/
+
+	qread(infile, (char *) nbitplanes, sizeof(nbitplanes));
+
+	stat = dodecode(infile, a, *nx, *ny, nbitplanes);
+	/*
+	 * put sum of all pixels back into pixel 0
+	 */
+	a[0] = (int) sumall;
+	return(stat);
+}
+/*  ############################################################################  */
+static int decode64(unsigned char *infile, LONGLONG *a, int *nx, int *ny, int *scale)
+/*
+char *infile;				 input file							
+LONGLONG  *a;				 address of output array [nx][ny]		
+int  *nx,*ny;				 size of output array					
+int  *scale;				 scale factor for digitization		
+*/
+{
+int nel, stat;
+LONGLONG sumall;
+unsigned char nbitplanes[3];
+char tmagic[2];
+
+	/* initialize the byte read position to the beginning of the array */;
+	nextchar = 0;
+	
+	/*
+	 * File starts either with special 2-byte magic code or with
+	 * FITS keyword "SIMPLE  ="
+	 */
+	qread(infile, tmagic, sizeof(tmagic));
+	/*
+	 * check for correct magic code value
+	 */
+	if (memcmp(tmagic,code_magic,sizeof(code_magic)) != 0) {
+		ffpmsg("bad file format");
+		return(DATA_DECOMPRESSION_ERR);
+	}
+	*nx =readint(infile);				/* x size of image			*/
+	*ny =readint(infile);				/* y size of image			*/
+	*scale=readint(infile);				/* scale factor for digitization	*/
+	
+	nel = (*nx) * (*ny);
+
+	/* sum of all pixels	*/
+	sumall=readlonglong(infile);
+	/* # bits in quadrants	*/
+
+	qread(infile, (char *) nbitplanes, sizeof(nbitplanes));
+
+	stat = dodecode64(infile, a, *nx, *ny, nbitplanes);
+	/*
+	 * put sum of all pixels back into pixel 0
+	 */
+	a[0] = sumall;
+
+	return(stat);
+}
+
+
+/*  ############################################################################  */
+/*  ############################################################################  */
+/* Copyright (c) 1993 Association of Universities for Research 
+ * in Astronomy. All rights reserved. Produced under National   
+ * Aeronautics and Space Administration Contract No. NAS5-26555.
+ */
+/* dodecode.c	Decode stream of characters on infile and return array
+ *
+ * This version encodes the different quadrants separately
+ *
+ * Programmer: R. White		Date: 9 May 1991
+ */
+
+/*  ############################################################################  */
+static int
+dodecode(unsigned char *infile, int a[], int nx, int ny, unsigned char nbitplanes[3])
+
+/* int a[];					 			
+   int nx,ny;					 Array dimensions are [nx][ny]		
+   unsigned char nbitplanes[3];		 Number of bit planes in quadrants
+*/
+{
+int i, nel, nx2, ny2, stat;
+
+	nel = nx*ny;
+	nx2 = (nx+1)/2;
+	ny2 = (ny+1)/2;
+
+	/*
+	 * initialize a to zero
+	 */
+	for (i=0; i<nel; i++) a[i] = 0;
+	/*
+	 * Initialize bit input
+	 */
+	start_inputing_bits();
+	/*
+	 * read bit planes for each quadrant
+	 */
+	stat = qtree_decode(infile, &a[0],          ny, nx2,  ny2,  nbitplanes[0]);
+        if (stat) return(stat);
+	
+	stat = qtree_decode(infile, &a[ny2],        ny, nx2,  ny/2, nbitplanes[1]);
+        if (stat) return(stat);
+	
+	stat = qtree_decode(infile, &a[ny*nx2],     ny, nx/2, ny2,  nbitplanes[1]);
+        if (stat) return(stat);
+	
+	stat = qtree_decode(infile, &a[ny*nx2+ny2], ny, nx/2, ny/2, nbitplanes[2]);
+        if (stat) return(stat);
+	
+	/*
+	 * make sure there is an EOF symbol (nybble=0) at end
+	 */
+	if (input_nybble(infile) != 0) {
+		ffpmsg("dodecode: bad bit plane values");
+		return(DATA_DECOMPRESSION_ERR);
+	}
+	/*
+	 * now get the sign bits
+	 * Re-initialize bit input
+	 */
+	start_inputing_bits();
+	for (i=0; i<nel; i++) {
+		if (a[i]) {
+			/* tried putting the input_bit code in-line here, instead of */
+			/* calling the function, but it made no difference in the speed */
+			if (input_bit(infile)) a[i] = -a[i];
+		}
+	}
+	return(0);
+}
+/*  ############################################################################  */
+static int
+dodecode64(unsigned char *infile, LONGLONG a[], int nx, int ny, unsigned char nbitplanes[3])
+
+/* LONGLONG a[];					 			
+   int nx,ny;					 Array dimensions are [nx][ny]		
+   unsigned char nbitplanes[3];		 Number of bit planes in quadrants
+*/
+{
+int i, nel, nx2, ny2, stat;
+
+	nel = nx*ny;
+	nx2 = (nx+1)/2;
+	ny2 = (ny+1)/2;
+
+	/*
+	 * initialize a to zero
+	 */
+	for (i=0; i<nel; i++) a[i] = 0;
+	/*
+	 * Initialize bit input
+	 */
+	start_inputing_bits();
+	/*
+	 * read bit planes for each quadrant
+	 */
+	stat = qtree_decode64(infile, &a[0],          ny, nx2,  ny2,  nbitplanes[0]);
+        if (stat) return(stat);
+	
+	stat = qtree_decode64(infile, &a[ny2],        ny, nx2,  ny/2, nbitplanes[1]);
+        if (stat) return(stat);
+	
+	stat = qtree_decode64(infile, &a[ny*nx2],     ny, nx/2, ny2,  nbitplanes[1]);
+        if (stat) return(stat);
+	
+	stat = qtree_decode64(infile, &a[ny*nx2+ny2], ny, nx/2, ny/2, nbitplanes[2]);
+        if (stat) return(stat);
+	
+	/*
+	 * make sure there is an EOF symbol (nybble=0) at end
+	 */
+	if (input_nybble(infile) != 0) {
+		ffpmsg("dodecode64: bad bit plane values");
+		return(DATA_DECOMPRESSION_ERR);
+	}
+	/*
+	 * now get the sign bits
+	 * Re-initialize bit input
+	 */
+	start_inputing_bits();
+	for (i=0; i<nel; i++) {
+		if (a[i]) {
+			if (input_bit(infile) != 0) a[i] = -a[i];
+		}
+	}
+	return(0);
+}
+
+/*  ############################################################################  */
+/*  ############################################################################  */
+/* Copyright (c) 1993 Association of Universities for Research 
+ * in Astronomy. All rights reserved. Produced under National   
+ * Aeronautics and Space Administration Contract No. NAS5-26555.
+ */
+/* qtree_decode.c	Read stream of codes from infile and construct bit planes
+ *					in quadrant of 2-D array using binary quadtree coding
+ *
+ * Programmer: R. White		Date: 7 May 1991
+ */
+
+/*  ############################################################################  */
+static int
+qtree_decode(unsigned char *infile, int a[], int n, int nqx, int nqy, int nbitplanes)
+
+/*
+char *infile;
+int a[];				 a is 2-D array with dimensions (n,n)	
+int n;					 length of full row in a				
+int nqx;				 partial length of row to decode		
+int nqy;				 partial length of column (<=n)		
+int nbitplanes;				 number of bitplanes to decode		
+*/
+{
+int log2n, k, bit, b, nqmax;
+int nx,ny,nfx,nfy,c;
+int nqx2, nqy2;
+unsigned char *scratch;
+
+	/*
+	 * log2n is log2 of max(nqx,nqy) rounded up to next power of 2
+	 */
+	nqmax = (nqx>nqy) ? nqx : nqy;
+	log2n = (int) (log((float) nqmax)/log(2.0)+0.5);
+	if (nqmax > (1<<log2n)) {
+		log2n += 1;
+	}
+	/*
+	 * allocate scratch array for working space
+	 */
+	nqx2=(nqx+1)/2;
+	nqy2=(nqy+1)/2;
+	scratch = (unsigned char *) malloc(nqx2*nqy2);
+	if (scratch == (unsigned char *) NULL) {
+		ffpmsg("qtree_decode: insufficient memory");
+		return(DATA_DECOMPRESSION_ERR);
+	}
+	/*
+	 * now decode each bit plane, starting at the top
+	 * A is assumed to be initialized to zero
+	 */
+	for (bit = nbitplanes-1; bit >= 0; bit--) {
+		/*
+		 * Was bitplane was quadtree-coded or written directly?
+		 */
+		b = input_nybble(infile);
+
+		if(b == 0) {
+			/*
+			 * bit map was written directly
+			 */
+			read_bdirect(infile,a,n,nqx,nqy,scratch,bit);
+		} else if (b != 0xf) {
+			ffpmsg("qtree_decode: bad format code");
+			return(DATA_DECOMPRESSION_ERR);
+		} else {
+			/*
+			 * bitmap was quadtree-coded, do log2n expansions
+			 *
+			 * read first code
+			 */
+			scratch[0] = input_huffman(infile);
+			/*
+			 * now do log2n expansions, reading codes from file as necessary
+			 */
+			nx = 1;
+			ny = 1;
+			nfx = nqx;
+			nfy = nqy;
+			c = 1<<log2n;
+			for (k = 1; k<log2n; k++) {
+				/*
+				 * this somewhat cryptic code generates the sequence
+				 * n[k-1] = (n[k]+1)/2 where n[log2n]=nqx or nqy
+				 */
+				c = c>>1;
+				nx = nx<<1;
+				ny = ny<<1;
+				if (nfx <= c) { nx -= 1; } else { nfx -= c; }
+				if (nfy <= c) { ny -= 1; } else { nfy -= c; }
+				qtree_expand(infile,scratch,nx,ny,scratch);
+			}
+			/*
+			 * now copy last set of 4-bit codes to bitplane bit of array a
+			 */
+			qtree_bitins(scratch,nqx,nqy,a,n,bit);
+		}
+	}
+	free(scratch);
+	return(0);
+}
+/*  ############################################################################  */
+static int
+qtree_decode64(unsigned char *infile, LONGLONG a[], int n, int nqx, int nqy, int nbitplanes)
+
+/*
+char *infile;
+LONGLONG a[];				 a is 2-D array with dimensions (n,n)	
+int n;					 length of full row in a				
+int nqx;				 partial length of row to decode		
+int nqy;				 partial length of column (<=n)		
+int nbitplanes;				 number of bitplanes to decode		
+*/
+{
+int log2n, k, bit, b, nqmax;
+int nx,ny,nfx,nfy,c;
+int nqx2, nqy2;
+unsigned char *scratch;
+
+	/*
+	 * log2n is log2 of max(nqx,nqy) rounded up to next power of 2
+	 */
+	nqmax = (nqx>nqy) ? nqx : nqy;
+	log2n = (int) (log((float) nqmax)/log(2.0)+0.5);
+	if (nqmax > (1<<log2n)) {
+		log2n += 1;
+	}
+	/*
+	 * allocate scratch array for working space
+	 */
+	nqx2=(nqx+1)/2;
+	nqy2=(nqy+1)/2;
+	scratch = (unsigned char *) malloc(nqx2*nqy2);
+	if (scratch == (unsigned char *) NULL) {
+		ffpmsg("qtree_decode64: insufficient memory");
+		return(DATA_DECOMPRESSION_ERR);
+	}
+	/*
+	 * now decode each bit plane, starting at the top
+	 * A is assumed to be initialized to zero
+	 */
+	for (bit = nbitplanes-1; bit >= 0; bit--) {
+		/*
+		 * Was bitplane was quadtree-coded or written directly?
+		 */
+		b = input_nybble(infile);
+
+		if(b == 0) {
+			/*
+			 * bit map was written directly
+			 */
+			read_bdirect64(infile,a,n,nqx,nqy,scratch,bit);
+		} else if (b != 0xf) {
+			ffpmsg("qtree_decode64: bad format code");
+			return(DATA_DECOMPRESSION_ERR);
+		} else {
+			/*
+			 * bitmap was quadtree-coded, do log2n expansions
+			 *
+			 * read first code
+			 */
+			scratch[0] = input_huffman(infile);
+			/*
+			 * now do log2n expansions, reading codes from file as necessary
+			 */
+			nx = 1;
+			ny = 1;
+			nfx = nqx;
+			nfy = nqy;
+			c = 1<<log2n;
+			for (k = 1; k<log2n; k++) {
+				/*
+				 * this somewhat cryptic code generates the sequence
+				 * n[k-1] = (n[k]+1)/2 where n[log2n]=nqx or nqy
+				 */
+				c = c>>1;
+				nx = nx<<1;
+				ny = ny<<1;
+				if (nfx <= c) { nx -= 1; } else { nfx -= c; }
+				if (nfy <= c) { ny -= 1; } else { nfy -= c; }
+				qtree_expand(infile,scratch,nx,ny,scratch);
+			}
+			/*
+			 * now copy last set of 4-bit codes to bitplane bit of array a
+			 */
+			qtree_bitins64(scratch,nqx,nqy,a,n,bit);
+		}
+	}
+	free(scratch);
+	return(0);
+}
+
+
+/*  ############################################################################  */
+/*
+ * do one quadtree expansion step on array a[(nqx+1)/2,(nqy+1)/2]
+ * results put into b[nqx,nqy] (which may be the same as a)
+ */
+static void
+qtree_expand(unsigned char *infile, unsigned char a[], int nx, int ny, unsigned char b[])
+{
+int i;
+
+	/*
+	 * first copy a to b, expanding each 4-bit value
+	 */
+	qtree_copy(a,nx,ny,b,ny);
+	/*
+	 * now read new 4-bit values into b for each non-zero element
+	 */
+	for (i = nx*ny-1; i >= 0; i--) {
+		if (b[i]) b[i] = input_huffman(infile);
+	}
+}
+
+/*  ############################################################################  */
+/*
+ * copy 4-bit values from a[(nx+1)/2,(ny+1)/2] to b[nx,ny], expanding
+ * each value to 2x2 pixels
+ * a,b may be same array
+ */
+static void
+qtree_copy(unsigned char a[], int nx, int ny, unsigned char b[], int n)
+/*   int n;		declared y dimension of b */
+{
+int i, j, k, nx2, ny2;
+int s00, s10;
+
+	/*
+	 * first copy 4-bit values to b
+	 * start at end in case a,b are same array
+	 */
+	nx2 = (nx+1)/2;
+	ny2 = (ny+1)/2;
+	k = ny2*(nx2-1)+ny2-1;			/* k   is index of a[i,j]		*/
+	for (i = nx2-1; i >= 0; i--) {
+		s00 = 2*(n*i+ny2-1);		/* s00 is index of b[2*i,2*j]		*/
+		for (j = ny2-1; j >= 0; j--) {
+			b[s00] = a[k];
+			k -= 1;
+			s00 -= 2;
+		}
+	}
+	/*
+	 * now expand each 2x2 block
+	 */
+	for (i = 0; i<nx-1; i += 2) {
+
+  /* Note:
+     Unlike the case in qtree_bitins, this code runs faster on a 32-bit linux
+     machine using the s10 intermediate variable, rather that using s00+n. 
+     Go figure!
+  */
+		s00 = n*i;				/* s00 is index of b[i,j]	*/
+		s10 = s00+n;				/* s10 is index of b[i+1,j]	*/
+
+		for (j = 0; j<ny-1; j += 2) {
+
+		    switch (b[s00]) {
+		    case(0):
+			b[s10+1] = 0;
+			b[s10  ] = 0;
+			b[s00+1] = 0;
+			b[s00  ] = 0;
+
+			break;
+		    case(1):
+			b[s10+1] = 1;
+			b[s10  ] = 0;
+			b[s00+1] = 0;
+			b[s00  ] = 0;
+
+			break;
+		    case(2):
+			b[s10+1] = 0;
+			b[s10  ] = 1;
+			b[s00+1] = 0;
+			b[s00  ] = 0;
+
+			break;
+		    case(3):
+			b[s10+1] = 1;
+			b[s10  ] = 1;
+			b[s00+1] = 0;
+			b[s00  ] = 0;
+
+			break;
+		    case(4):
+			b[s10+1] = 0;
+			b[s10  ] = 0;
+			b[s00+1] = 1;
+			b[s00  ] = 0;
+
+			break;
+		    case(5):
+			b[s10+1] = 1;
+			b[s10  ] = 0;
+			b[s00+1] = 1;
+			b[s00  ] = 0;
+
+			break;
+		    case(6):
+			b[s10+1] = 0;
+			b[s10  ] = 1;
+			b[s00+1] = 1;
+			b[s00  ] = 0;
+
+			break;
+		    case(7):
+			b[s10+1] = 1;
+			b[s10  ] = 1;
+			b[s00+1] = 1;
+			b[s00  ] = 0;
+
+			break;
+		    case(8):
+			b[s10+1] = 0;
+			b[s10  ] = 0;
+			b[s00+1] = 0;
+			b[s00  ] = 1;
+
+			break;
+		    case(9):
+			b[s10+1] = 1;
+			b[s10  ] = 0;
+			b[s00+1] = 0;
+			b[s00  ] = 1;
+			break;
+		    case(10):
+			b[s10+1] = 0;
+			b[s10  ] = 1;
+			b[s00+1] = 0;
+			b[s00  ] = 1;
+
+			break;
+		    case(11):
+			b[s10+1] = 1;
+			b[s10  ] = 1;
+			b[s00+1] = 0;
+			b[s00  ] = 1;
+
+			break;
+		    case(12):
+			b[s10+1] = 0;
+			b[s10  ] = 0;
+			b[s00+1] = 1;
+			b[s00  ] = 1;
+
+			break;
+		    case(13):
+			b[s10+1] = 1;
+			b[s10  ] = 0;
+			b[s00+1] = 1;
+			b[s00  ] = 1;
+
+			break;
+		    case(14):
+			b[s10+1] = 0;
+			b[s10  ] = 1;
+			b[s00+1] = 1;
+			b[s00  ] = 1;
+
+			break;
+		    case(15):
+			b[s10+1] = 1;
+			b[s10  ] = 1;
+			b[s00+1] = 1;
+			b[s00  ] = 1;
+
+			break;
+		    }
+/*
+			b[s10+1] =  b[s00]     & 1;
+			b[s10  ] = (b[s00]>>1) & 1;
+			b[s00+1] = (b[s00]>>2) & 1;
+			b[s00  ] = (b[s00]>>3) & 1;
+*/
+
+			s00 += 2;
+			s10 += 2;
+		}
+
+		if (j < ny) {
+			/*
+			 * row size is odd, do last element in row
+			 * s00+1, s10+1 are off edge
+			 */
+                        /* not worth converting this to use 16 case statements */
+			b[s10  ] = (b[s00]>>1) & 1;
+			b[s00  ] = (b[s00]>>3) & 1;
+		}
+	}
+	if (i < nx) {
+		/*
+		 * column size is odd, do last row
+		 * s10, s10+1 are off edge
+		 */
+		s00 = n*i;
+		for (j = 0; j<ny-1; j += 2) {
+                        /* not worth converting this to use 16 case statements */
+			b[s00+1] = (b[s00]>>2) & 1;
+			b[s00  ] = (b[s00]>>3) & 1;
+			s00 += 2;
+		}
+		if (j < ny) {
+			/*
+			 * both row and column size are odd, do corner element
+			 * s00+1, s10, s10+1 are off edge
+			 */
+                        /* not worth converting this to use 16 case statements */
+			b[s00  ] = (b[s00]>>3) & 1;
+		}
+	}
+}
+
+/*  ############################################################################  */
+/*
+ * Copy 4-bit values from a[(nx+1)/2,(ny+1)/2] to b[nx,ny], expanding
+ * each value to 2x2 pixels and inserting into bitplane BIT of B.
+ * A,B may NOT be same array (it wouldn't make sense to be inserting
+ * bits into the same array anyway.)
+ */
+static void
+qtree_bitins(unsigned char a[], int nx, int ny, int b[], int n, int bit)
+/*
+   int n;		declared y dimension of b
+*/
+{
+int i, j, k;
+int s00;
+int plane_val;
+
+	plane_val = 1 << bit;
+	
+	/*
+	 * expand each 2x2 block
+	 */
+	k = 0;						/* k   is index of a[i/2,j/2]	*/
+	for (i = 0; i<nx-1; i += 2) {
+		s00 = n*i;				/* s00 is index of b[i,j]	*/
+
+  /* Note:
+     this code appears to run very slightly faster on a 32-bit linux
+     machine using s00+n rather than the s10 intermediate variable
+  */
+  /*		s10 = s00+n;	*/			/* s10 is index of b[i+1,j]	*/
+		for (j = 0; j<ny-1; j += 2) {
+
+		    switch (a[k]) {
+		    case(0):
+			break;
+		    case(1):
+			b[s00+n+1] |= plane_val;
+			break;
+		    case(2):
+			b[s00+n  ] |= plane_val;
+			break;
+		    case(3):
+			b[s00+n+1] |= plane_val;
+			b[s00+n  ] |= plane_val;
+			break;
+		    case(4):
+			b[s00+1] |= plane_val;
+			break;
+		    case(5):
+			b[s00+n+1] |= plane_val;
+			b[s00+1] |= plane_val;
+			break;
+		    case(6):
+			b[s00+n  ] |= plane_val;
+			b[s00+1] |= plane_val;
+			break;
+		    case(7):
+			b[s00+n+1] |= plane_val;
+			b[s00+n  ] |= plane_val;
+			b[s00+1] |= plane_val;
+			break;
+		    case(8):
+			b[s00  ] |= plane_val;
+			break;
+		    case(9):
+			b[s00+n+1] |= plane_val;
+			b[s00  ] |= plane_val;
+			break;
+		    case(10):
+			b[s00+n  ] |= plane_val;
+			b[s00  ] |= plane_val;
+			break;
+		    case(11):
+			b[s00+n+1] |= plane_val;
+			b[s00+n  ] |= plane_val;
+			b[s00  ] |= plane_val;
+			break;
+		    case(12):
+			b[s00+1] |= plane_val;
+			b[s00  ] |= plane_val;
+			break;
+		    case(13):
+			b[s00+n+1] |= plane_val;
+			b[s00+1] |= plane_val;
+			b[s00  ] |= plane_val;
+			break;
+		    case(14):
+			b[s00+n  ] |= plane_val;
+			b[s00+1] |= plane_val;
+			b[s00  ] |= plane_val;
+			break;
+		    case(15):
+			b[s00+n+1] |= plane_val;
+			b[s00+n  ] |= plane_val;
+			b[s00+1] |= plane_val;
+			b[s00  ] |= plane_val;
+			break;
+		    }
+
+/*
+			b[s10+1] |= ( a[k]     & 1) << bit;
+			b[s10  ] |= ((a[k]>>1) & 1) << bit;
+			b[s00+1] |= ((a[k]>>2) & 1) << bit;
+			b[s00  ] |= ((a[k]>>3) & 1) << bit;
+*/
+			s00 += 2;
+/*			s10 += 2; */
+			k += 1;
+		}
+		if (j < ny) {
+			/*
+			 * row size is odd, do last element in row
+			 * s00+1, s10+1 are off edge
+			 */
+
+		    switch (a[k]) {
+		    case(0):
+			break;
+		    case(1):
+			break;
+		    case(2):
+			b[s00+n  ] |= plane_val;
+			break;
+		    case(3):
+			b[s00+n  ] |= plane_val;
+			break;
+		    case(4):
+			break;
+		    case(5):
+			break;
+		    case(6):
+			b[s00+n  ] |= plane_val;
+			break;
+		    case(7):
+			b[s00+n  ] |= plane_val;
+			break;
+		    case(8):
+			b[s00  ] |= plane_val;
+			break;
+		    case(9):
+			b[s00  ] |= plane_val;
+			break;
+		    case(10):
+			b[s00+n  ] |= plane_val;
+			b[s00  ] |= plane_val;
+			break;
+		    case(11):
+			b[s00+n  ] |= plane_val;
+			b[s00  ] |= plane_val;
+			break;
+		    case(12):
+			b[s00  ] |= plane_val;
+			break;
+		    case(13):
+			b[s00  ] |= plane_val;
+			break;
+		    case(14):
+			b[s00+n  ] |= plane_val;
+			b[s00  ] |= plane_val;
+			break;
+		    case(15):
+			b[s00+n  ] |= plane_val;
+			b[s00  ] |= plane_val;
+			break;
+		    }
+
+/*
+			b[s10  ] |= ((a[k]>>1) & 1) << bit;
+			b[s00  ] |= ((a[k]>>3) & 1) << bit;
+*/
+			k += 1;
+		}
+	}
+	if (i < nx) {
+		/*
+		 * column size is odd, do last row
+		 * s10, s10+1 are off edge
+		 */
+		s00 = n*i;
+		for (j = 0; j<ny-1; j += 2) {
+
+		    switch (a[k]) {
+		    case(0):
+			break;
+		    case(1):
+			break;
+		    case(2):
+			break;
+		    case(3):
+			break;
+		    case(4):
+			b[s00+1] |= plane_val;
+			break;
+		    case(5):
+			b[s00+1] |= plane_val;
+			break;
+		    case(6):
+			b[s00+1] |= plane_val;
+			break;
+		    case(7):
+			b[s00+1] |= plane_val;
+			break;
+		    case(8):
+			b[s00  ] |= plane_val;
+			break;
+		    case(9):
+			b[s00  ] |= plane_val;
+			break;
+		    case(10):
+			b[s00  ] |= plane_val;
+			break;
+		    case(11):
+			b[s00  ] |= plane_val;
+			break;
+		    case(12):
+			b[s00+1] |= plane_val;
+			b[s00  ] |= plane_val;
+			break;
+		    case(13):
+			b[s00+1] |= plane_val;
+			b[s00  ] |= plane_val;
+			break;
+		    case(14):
+			b[s00+1] |= plane_val;
+			b[s00  ] |= plane_val;
+			break;
+		    case(15):
+			b[s00+1] |= plane_val;
+			b[s00  ] |= plane_val;
+			break;
+		    }
+
+/*
+			b[s00+1] |= ((a[k]>>2) & 1) << bit;
+			b[s00  ] |= ((a[k]>>3) & 1) << bit;
+*/
+
+			s00 += 2;
+			k += 1;
+		}
+		if (j < ny) {
+			/*
+			 * both row and column size are odd, do corner element
+			 * s00+1, s10, s10+1 are off edge
+			 */
+
+		    switch (a[k]) {
+		    case(0):
+			break;
+		    case(1):
+			break;
+		    case(2):
+			break;
+		    case(3):
+			break;
+		    case(4):
+			break;
+		    case(5):
+			break;
+		    case(6):
+			break;
+		    case(7):
+			break;
+		    case(8):
+			b[s00  ] |= plane_val;
+			break;
+		    case(9):
+			b[s00  ] |= plane_val;
+			break;
+		    case(10):
+			b[s00  ] |= plane_val;
+			break;
+		    case(11):
+			b[s00  ] |= plane_val;
+			break;
+		    case(12):
+			b[s00  ] |= plane_val;
+			break;
+		    case(13):
+			b[s00  ] |= plane_val;
+			break;
+		    case(14):
+			b[s00  ] |= plane_val;
+			break;
+		    case(15):
+			b[s00  ] |= plane_val;
+			break;
+		    }
+
+/*
+			b[s00  ] |= ((a[k]>>3) & 1) << bit;
+*/
+			k += 1;
+		}
+	}
+}
+/*  ############################################################################  */
+/*
+ * Copy 4-bit values from a[(nx+1)/2,(ny+1)/2] to b[nx,ny], expanding
+ * each value to 2x2 pixels and inserting into bitplane BIT of B.
+ * A,B may NOT be same array (it wouldn't make sense to be inserting
+ * bits into the same array anyway.)
+ */
+static void
+qtree_bitins64(unsigned char a[], int nx, int ny, LONGLONG b[], int n, int bit)
+/*
+   int n;		declared y dimension of b
+*/
+{
+int i, j, k;
+int s00;
+int plane_val;
+
+	plane_val = 1 << bit;
+
+	/*
+	 * expand each 2x2 block
+	 */
+	k = 0;							/* k   is index of a[i/2,j/2]	*/
+	for (i = 0; i<nx-1; i += 2) {
+		s00 = n*i;					/* s00 is index of b[i,j]		*/
+
+  /* Note:
+     this code appears to run very slightly faster on a 32-bit linux
+     machine using s00+n rather than the s10 intermediate variable
+  */
+  /*		s10 = s00+n;	*/			/* s10 is index of b[i+1,j]	*/
+		for (j = 0; j<ny-1; j += 2) {
+
+		    switch (a[k]) {
+		    case(0):
+			break;
+		    case(1):
+			b[s00+n+1] |= plane_val;
+			break;
+		    case(2):
+			b[s00+n  ] |= plane_val;
+			break;
+		    case(3):
+			b[s00+n+1] |= plane_val;
+			b[s00+n  ] |= plane_val;
+			break;
+		    case(4):
+			b[s00+1] |= plane_val;
+			break;
+		    case(5):
+			b[s00+n+1] |= plane_val;
+			b[s00+1] |= plane_val;
+			break;
+		    case(6):
+			b[s00+n  ] |= plane_val;
+			b[s00+1] |= plane_val;
+			break;
+		    case(7):
+			b[s00+n+1] |= plane_val;
+			b[s00+n  ] |= plane_val;
+			b[s00+1] |= plane_val;
+			break;
+		    case(8):
+			b[s00  ] |= plane_val;
+			break;
+		    case(9):
+			b[s00+n+1] |= plane_val;
+			b[s00  ] |= plane_val;
+			break;
+		    case(10):
+			b[s00+n  ] |= plane_val;
+			b[s00  ] |= plane_val;
+			break;
+		    case(11):
+			b[s00+n+1] |= plane_val;
+			b[s00+n  ] |= plane_val;
+			b[s00  ] |= plane_val;
+			break;
+		    case(12):
+			b[s00+1] |= plane_val;
+			b[s00  ] |= plane_val;
+			break;
+		    case(13):
+			b[s00+n+1] |= plane_val;
+			b[s00+1] |= plane_val;
+			b[s00  ] |= plane_val;
+			break;
+		    case(14):
+			b[s00+n  ] |= plane_val;
+			b[s00+1] |= plane_val;
+			b[s00  ] |= plane_val;
+			break;
+		    case(15):
+			b[s00+n+1] |= plane_val;
+			b[s00+n  ] |= plane_val;
+			b[s00+1] |= plane_val;
+			b[s00  ] |= plane_val;
+			break;
+		    }
+
+/*
+			b[s10+1] |= ((LONGLONG) ( a[k]     & 1)) << bit;
+			b[s10  ] |= ((((LONGLONG)a[k])>>1) & 1) << bit;
+			b[s00+1] |= ((((LONGLONG)a[k])>>2) & 1) << bit;
+			b[s00  ] |= ((((LONGLONG)a[k])>>3) & 1) << bit;
+*/
+			s00 += 2;
+/*			s10 += 2;  */
+			k += 1;
+		}
+		if (j < ny) {
+			/*
+			 * row size is odd, do last element in row
+			 * s00+1, s10+1 are off edge
+			 */
+
+		    switch (a[k]) {
+		    case(0):
+			break;
+		    case(1):
+			break;
+		    case(2):
+			b[s00+n  ] |= plane_val;
+			break;
+		    case(3):
+			b[s00+n  ] |= plane_val;
+			break;
+		    case(4):
+			break;
+		    case(5):
+			break;
+		    case(6):
+			b[s00+n  ] |= plane_val;
+			break;
+		    case(7):
+			b[s00+n  ] |= plane_val;
+			break;
+		    case(8):
+			b[s00  ] |= plane_val;
+			break;
+		    case(9):
+			b[s00  ] |= plane_val;
+			break;
+		    case(10):
+			b[s00+n  ] |= plane_val;
+			b[s00  ] |= plane_val;
+			break;
+		    case(11):
+			b[s00+n  ] |= plane_val;
+			b[s00  ] |= plane_val;
+			break;
+		    case(12):
+			b[s00  ] |= plane_val;
+			break;
+		    case(13):
+			b[s00  ] |= plane_val;
+			break;
+		    case(14):
+			b[s00+n  ] |= plane_val;
+			b[s00  ] |= plane_val;
+			break;
+		    case(15):
+			b[s00+n  ] |= plane_val;
+			b[s00  ] |= plane_val;
+			break;
+		    }
+/*
+			b[s10  ] |= ((((LONGLONG)a[k])>>1) & 1) << bit;
+			b[s00  ] |= ((((LONGLONG)a[k])>>3) & 1) << bit;
+*/
+			k += 1;
+		}
+	}
+	if (i < nx) {
+		/*
+		 * column size is odd, do last row
+		 * s10, s10+1 are off edge
+		 */
+		s00 = n*i;
+		for (j = 0; j<ny-1; j += 2) {
+
+		    switch (a[k]) {
+		    case(0):
+			break;
+		    case(1):
+			break;
+		    case(2):
+			break;
+		    case(3):
+			break;
+		    case(4):
+			b[s00+1] |= plane_val;
+			break;
+		    case(5):
+			b[s00+1] |= plane_val;
+			break;
+		    case(6):
+			b[s00+1] |= plane_val;
+			break;
+		    case(7):
+			b[s00+1] |= plane_val;
+			break;
+		    case(8):
+			b[s00  ] |= plane_val;
+			break;
+		    case(9):
+			b[s00  ] |= plane_val;
+			break;
+		    case(10):
+			b[s00  ] |= plane_val;
+			break;
+		    case(11):
+			b[s00  ] |= plane_val;
+			break;
+		    case(12):
+			b[s00+1] |= plane_val;
+			b[s00  ] |= plane_val;
+			break;
+		    case(13):
+			b[s00+1] |= plane_val;
+			b[s00  ] |= plane_val;
+			break;
+		    case(14):
+			b[s00+1] |= plane_val;
+			b[s00  ] |= plane_val;
+			break;
+		    case(15):
+			b[s00+1] |= plane_val;
+			b[s00  ] |= plane_val;
+			break;
+		    }
+
+/*
+			b[s00+1] |= ((((LONGLONG)a[k])>>2) & 1) << bit;
+			b[s00  ] |= ((((LONGLONG)a[k])>>3) & 1) << bit;
+*/
+			s00 += 2;
+			k += 1;
+		}
+		if (j < ny) {
+			/*
+			 * both row and column size are odd, do corner element
+			 * s00+1, s10, s10+1 are off edge
+			 */
+
+		    switch (a[k]) {
+		    case(0):
+			break;
+		    case(1):
+			break;
+		    case(2):
+			break;
+		    case(3):
+			break;
+		    case(4):
+			break;
+		    case(5):
+			break;
+		    case(6):
+			break;
+		    case(7):
+			break;
+		    case(8):
+			b[s00  ] |= plane_val;
+			break;
+		    case(9):
+			b[s00  ] |= plane_val;
+			break;
+		    case(10):
+			b[s00  ] |= plane_val;
+			break;
+		    case(11):
+			b[s00  ] |= plane_val;
+			break;
+		    case(12):
+			b[s00  ] |= plane_val;
+			break;
+		    case(13):
+			b[s00  ] |= plane_val;
+			break;
+		    case(14):
+			b[s00  ] |= plane_val;
+			break;
+		    case(15):
+			b[s00  ] |= plane_val;
+			break;
+		    }
+/*
+			b[s00  ] |= ((((LONGLONG)a[k])>>3) & 1) << bit;
+*/
+			k += 1;
+		}
+	}
+}
+
+/*  ############################################################################  */
+static void
+read_bdirect(unsigned char *infile, int a[], int n, int nqx, int nqy, unsigned char scratch[], int bit)
+{
+	/*
+	 * read bit image packed 4 pixels/nybble
+	 */
+/*
+int i;
+	for (i = 0; i < ((nqx+1)/2) * ((nqy+1)/2); i++) {
+		scratch[i] = input_nybble(infile);
+	}
+*/
+        input_nnybble(infile, ((nqx+1)/2) * ((nqy+1)/2), scratch);
+	
+	/*
+	 * insert in bitplane BIT of image A
+	 */
+	qtree_bitins(scratch,nqx,nqy,a,n,bit);
+}
+/*  ############################################################################  */
+static void
+read_bdirect64(unsigned char *infile, LONGLONG a[], int n, int nqx, int nqy, unsigned char scratch[], int bit)
+{
+	/*
+	 * read bit image packed 4 pixels/nybble
+	 */
+/*
+int i;
+	for (i = 0; i < ((nqx+1)/2) * ((nqy+1)/2); i++) {
+		scratch[i] = input_nybble(infile);
+	}
+*/
+        input_nnybble(infile, ((nqx+1)/2) * ((nqy+1)/2), scratch);
+
+	/*
+	 * insert in bitplane BIT of image A
+	 */
+	qtree_bitins64(scratch,nqx,nqy,a,n,bit);
+}
+
+/*  ############################################################################  */
+/*
+ * Huffman decoding for fixed codes
+ *
+ * Coded values range from 0-15
+ *
+ * Huffman code values (hex):
+ *
+ *	3e, 00, 01, 08, 02, 09, 1a, 1b,
+ *	03, 1c, 0a, 1d, 0b, 1e, 3f, 0c
+ *
+ * and number of bits in each code:
+ *
+ *	6,  3,  3,  4,  3,  4,  5,  5,
+ *	3,  5,  4,  5,  4,  5,  6,  4
+ */
+static int input_huffman(unsigned char *infile)
+{
+int c;
+
+	/*
+	 * get first 3 bits to start
+	 */
+	c = input_nbits(infile,3);
+	if (c < 4) {
+		/*
+		 * this is all we need
+		 * return 1,2,4,8 for c=0,1,2,3
+		 */
+		return(1<<c);
+	}
+	/*
+	 * get the next bit
+	 */
+	c = input_bit(infile) | (c<<1);
+	if (c < 13) {
+		/*
+		 * OK, 4 bits is enough
+		 */
+		switch (c) {
+			case  8 : return(3);
+			case  9 : return(5);
+			case 10 : return(10);
+			case 11 : return(12);
+			case 12 : return(15);
+		}
+	}
+	/*
+	 * get yet another bit
+	 */
+	c = input_bit(infile) | (c<<1);
+	if (c < 31) {
+		/*
+		 * OK, 5 bits is enough
+		 */
+		switch (c) {
+			case 26 : return(6);
+			case 27 : return(7);
+			case 28 : return(9);
+			case 29 : return(11);
+			case 30 : return(13);
+		}
+	}
+	/*
+	 * need the 6th bit
+	 */
+	c = input_bit(infile) | (c<<1);
+	if (c == 62) {
+		return(0);
+	} else {
+		return(14);
+	}
+}
+
+/*  ############################################################################  */
+/*  ############################################################################  */
+/* Copyright (c) 1993 Association of Universities for Research 
+ * in Astronomy. All rights reserved. Produced under National   
+ * Aeronautics and Space Administration Contract No. NAS5-26555.
+ */
+/* qread.c	Read binary data
+ *
+ * Programmer: R. White		Date: 11 March 1991
+ */
+
+static int readint(unsigned char *infile)
+{
+int a,i;
+unsigned char b[4];
+
+	/* Read integer A one byte at a time from infile.
+	 *
+	 * This is portable from Vax to Sun since it eliminates the
+	 * need for byte-swapping.
+	 *
+         *  This routine is only called to read the first 3 values
+	 *  in the compressed file, so it doesn't have to be 
+	 *  super-efficient
+	 */
+	for (i=0; i<4; i++) qread(infile,(char *) &b[i],1);
+	a = b[0];
+	for (i=1; i<4; i++) a = (a<<8) + b[i];
+	return(a);
+}
+
+/*  ############################################################################  */
+static LONGLONG readlonglong(unsigned char *infile)
+{
+int i;
+LONGLONG a;
+unsigned char b[8];
+
+	/* Read integer A one byte at a time from infile.
+	 *
+	 * This is portable from Vax to Sun since it eliminates the
+	 * need for byte-swapping.
+	 *
+         *  This routine is only called to read the first 3 values
+	 *  in the compressed file, so it doesn't have to be 
+	 *  super-efficient
+	 */
+	for (i=0; i<8; i++) qread(infile,(char *) &b[i],1);
+	a = b[0];
+	for (i=1; i<8; i++) a = (a<<8) + b[i];
+	return(a);
+}
+
+/*  ############################################################################  */
+static void qread(unsigned char *file, char buffer[], int n)
+{
+    /*
+     * read n bytes from file into buffer
+     *
+     */
+
+    memcpy(buffer, &file[nextchar], n);
+    nextchar += n;
+}
+
+/*  ############################################################################  */
+/*  ############################################################################  */
+/* Copyright (c) 1993 Association of Universities for Research
+ * in Astronomy. All rights reserved. Produced under National
+ * Aeronautics and Space Administration Contract No. NAS5-26555.
+ */
+
+/* BIT INPUT ROUTINES */
+
+/* THE BIT BUFFER */
+
+static int buffer2;			/* Bits waiting to be input	*/
+static int bits_to_go;			/* Number of bits still in buffer */
+
+/* INITIALIZE BIT INPUT */
+
+/*  ############################################################################  */
+static void start_inputing_bits(void)
+{
+	/*
+	 * Buffer starts out with no bits in it
+	 */
+	bits_to_go = 0;
+}
+
+/*  ############################################################################  */
+/* INPUT A BIT */
+
+static int input_bit(unsigned char *infile)
+{
+	if (bits_to_go == 0) {			/* Read the next byte if no	*/
+
+		buffer2 = infile[nextchar];
+		nextchar++;
+		
+		bits_to_go = 8;
+	}
+	/*
+	 * Return the next bit
+	 */
+	bits_to_go -= 1;
+	return((buffer2>>bits_to_go) & 1);
+}
+
+/*  ############################################################################  */
+/* INPUT N BITS (N must be <= 8) */
+
+static int input_nbits(unsigned char *infile, int n)
+{
+    /* AND mask for retreiving the right-most n bits */
+    static int mask[9] = {0, 1, 3, 7, 15, 31, 63, 127, 255};
+
+	if (bits_to_go < n) {
+		/*
+		 * need another byte's worth of bits
+		 */
+
+		buffer2 = (buffer2<<8) | (int) infile[nextchar];
+		nextchar++;
+		bits_to_go += 8;
+	}
+	/*
+	 * now pick off the first n bits
+	 */
+	bits_to_go -= n;
+
+        /* there was a slight gain in speed by replacing the following line */
+/*	return( (buffer2>>bits_to_go) & ((1<<n)-1) ); */
+	return( (buffer2>>bits_to_go) & (*(mask+n)) ); 
+}
+/*  ############################################################################  */
+/* INPUT 4 BITS  */
+
+static int input_nybble(unsigned char *infile)
+{
+	if (bits_to_go < 4) {
+		/*
+		 * need another byte's worth of bits
+		 */
+
+		buffer2 = (buffer2<<8) | (int) infile[nextchar];
+		nextchar++;
+		bits_to_go += 8;
+	}
+	/*
+	 * now pick off the first 4 bits
+	 */
+	bits_to_go -= 4;
+
+	return( (buffer2>>bits_to_go) & 15 ); 
+}
+/*  ############################################################################  */
+/* INPUT array of 4 BITS  */
+
+static int input_nnybble(unsigned char *infile, int n, unsigned char array[])
+{
+	/* copy n 4-bit nybbles from infile to the lower 4 bits of array */
+
+int ii, kk, shift1, shift2;
+
+/*  forcing byte alignment doesn;t help, and even makes it go slightly slower
+if (bits_to_go != 8) input_nbits(infile, bits_to_go);
+*/
+	if (n == 1) {
+		array[0] = input_nybble(infile);
+		return(0);
+	}
+	
+	if (bits_to_go == 8) {
+		/*
+		   already have 2 full nybbles in buffer2, so 
+		   backspace the infile array to reuse last char
+		*/
+		nextchar--;
+		bits_to_go = 0;
+	}
+	
+	/* bits_to_go now has a value in the range 0 - 7.  After adding  */
+	/* another byte, bits_to_go effectively will be in range 8 - 15 */	
+
+	shift1 = bits_to_go + 4;   /* shift1 will be in range 4 - 11 */
+	shift2 = bits_to_go;	   /* shift2 will be in range 0 -  7 */
+	kk = 0;
+
+	/* special case */
+	if (bits_to_go == 0) 
+	{
+	    for (ii = 0; ii < n/2; ii++) {
+		/*
+		 * refill the buffer with next byte
+		 */
+		buffer2 = (buffer2<<8) | (int) infile[nextchar];
+		nextchar++;
+		array[kk]     = (int) ((buffer2>>4) & 15);
+		array[kk + 1] = (int) ((buffer2) & 15);    /* no shift required */
+		kk += 2;
+	    }
+	}
+	else
+	{
+	    for (ii = 0; ii < n/2; ii++) {
+		/*
+		 * refill the buffer with next byte
+		 */
+		buffer2 = (buffer2<<8) | (int) infile[nextchar];
+		nextchar++;
+		array[kk]     = (int) ((buffer2>>shift1) & 15);
+		array[kk + 1] = (int) ((buffer2>>shift2) & 15);
+		kk += 2;
+	    }
+	}
+
+
+	if (ii * 2 != n) {  /* have to read last odd byte */
+		array[n-1] = input_nybble(infile);
+	}
+
+	return( (buffer2>>bits_to_go) & 15 ); 
+}
diff --git a/src/plugins/cfitsio/fitscore.c b/src/plugins/cfitsio/fitscore.c
new file mode 100644
index 0000000..cf82614
--- /dev/null
+++ b/src/plugins/cfitsio/fitscore.c
@@ -0,0 +1,9242 @@
+/*  This file, fitscore.c, contains the core set of FITSIO routines.       */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+/*
+
+Copyright (Unpublished--all rights reserved under the copyright laws of
+the United States), U.S. Government as represented by the Administrator
+of the National Aeronautics and Space Administration.  No copyright is
+claimed in the United States under Title 17, U.S. Code.
+
+Permission to freely use, copy, modify, and distribute this software
+and its documentation without fee is hereby granted, provided that this
+copyright notice and disclaimer of warranty appears in all copies.
+
+DISCLAIMER:
+
+THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND,
+EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO,
+ANY WARRANTY THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY
+IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+PURPOSE, AND FREEDOM FROM INFRINGEMENT, AND ANY WARRANTY THAT THE
+DOCUMENTATION WILL CONFORM TO THE SOFTWARE, OR ANY WARRANTY THAT THE
+SOFTWARE WILL BE ERROR FREE.  IN NO EVENT SHALL NASA BE LIABLE FOR ANY
+DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT, INDIRECT, SPECIAL OR
+CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM, OR IN ANY WAY
+CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
+CONTRACT, TORT , OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY
+PERSONS OR PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED
+FROM, OR AROSE OUT OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR
+SERVICES PROVIDED HEREUNDER."
+
+*/
+
+
+#include <string.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <math.h>
+#include <ctype.h>
+#include <errno.h>
+/* stddef.h is apparently needed to define size_t with some compilers ?? */
+#include <stddef.h>
+#include <locale.h>
+#include "fitsio2.h"
+
+#define errmsgsiz 25
+#define ESMARKER 27  /* Escape character is used as error stack marker */
+
+#define DelAll     1 /* delete all messages on the error stack */
+#define DelMark    2 /* delete newest messages back to and including marker */
+#define DelNewest  3 /* delete the newest message from the stack */
+#define GetMesg    4 /* pop and return oldest message, ignoring marks */
+#define PutMesg    5 /* add a new message to the stack */
+#define PutMark    6 /* add a marker to the stack */
+
+#ifdef _REENTRANT
+/*
+    Fitsio_Lock and Fitsio_Pthread_Status are declared in fitsio2.h. 
+*/
+pthread_mutex_t Fitsio_Lock;
+int Fitsio_Pthread_Status = 0;
+
+#endif
+
+int STREAM_DRIVER = 0;
+struct lconv *lcxxx;
+
+/*--------------------------------------------------------------------------*/
+float ffvers(float *version)  /* IO - version number */
+/*
+  return the current version number of the FITSIO software
+*/
+{
+      *version = (float) 3.30;
+
+/*       11 Apr 2012
+
+   Previous releases:
+      *version = 3.29    22 Sep 2011
+      *version = 3.28    12 May 2011
+      *version = 3.27     3 Mar 2011
+      *version = 3.26    30 Dec 2010
+      *version = 3.25    9 June 2010
+      *version = 3.24    26 Jan 2010
+      *version = 3.23     7 Jan 2010
+      *version = 3.22    28 Oct 2009
+      *version = 3.21    24 Sep 2009
+      *version = 3.20    31 Aug 2009
+      *version = 3.18    12 May 2009 (beta version)
+      *version = 3.14    18 Mar 2009 
+      *version = 3.13     5 Jan 2009 
+      *version = 3.12     8 Oct 2008 
+      *version = 3.11    19 Sep 2008 
+      *version = 3.10    20 Aug 2008 
+      *version = 3.09     3 Jun 2008 
+      *version = 3.08    15 Apr 2007  (internal release)
+      *version = 3.07     5 Nov 2007  (internal release)
+      *version = 3.06    27 Aug 2007  
+      *version = 3.05    12 Jul 2007  (internal release)
+      *version = 3.03    11 Dec 2006
+      *version = 3.02    18 Sep 2006
+      *version = 3.01       May 2006 included in FTOOLS 6.1 release
+      *version = 3.006   20 Feb 2006 
+      *version = 3.005   20 Dec 2005 (beta, in heasoft swift release
+      *version = 3.004   16 Sep 2005 (beta, in heasoft swift release
+      *version = 3.003   28 Jul 2005 (beta, in heasoft swift release
+      *version = 3.002   15 Apr 2005 (beta)
+      *version = 3.001   15 Mar 2005 (beta) released with heasoft 6.0
+      *version = 3.000   1 Mar 2005 (internal release only)
+      *version = 2.51     2 Dec 2004
+      *version = 2.50    28 Jul 2004
+      *version = 2.49    11 Feb 2004
+      *version = 2.48    28 Jan 2004
+      *version = 2.470   18 Aug 2003
+      *version = 2.460   20 May 2003
+      *version = 2.450   30 Apr 2003  (internal release only)
+      *version = 2.440    8 Jan 2003
+      *version = 2.430;   4 Nov 2002
+      *version = 2.420;  19 Jul 2002
+      *version = 2.410;  22 Apr 2002 used in ftools v5.2
+      *version = 2.401;  28 Jan 2002
+      *version = 2.400;  18 Jan 2002
+      *version = 2.301;   7 Dec 2001
+      *version = 2.300;  23 Oct 2001
+      *version = 2.204;  26 Jul 2001
+      *version = 2.203;  19 Jul 2001 used in ftools v5.1
+      *version = 2.202;  22 May 2001
+      *version = 2.201;  15 Mar 2001
+      *version = 2.200;  26 Jan 2001
+      *version = 2.100;  26 Sep 2000
+      *version = 2.037;   6 Jul 2000
+      *version = 2.036;   1 Feb 2000
+      *version = 2.035;   7 Dec 1999 (internal release only)
+      *version = 2.034;  23 Nov 1999
+      *version = 2.033;  17 Sep 1999
+      *version = 2.032;  25 May 1999
+      *version = 2.031;  31 Mar 1999
+      *version = 2.030;  24 Feb 1999
+      *version = 2.029;  11 Feb 1999
+      *version = 2.028;  26 Jan 1999
+      *version = 2.027;  12 Jan 1999
+      *version = 2.026;  23 Dec 1998
+      *version = 2.025;   1 Dec 1998
+      *version = 2.024;   9 Nov 1998
+      *version = 2.023;   1 Nov 1998 first full release of V2.0
+      *version = 1.42;   30 Apr 1998
+      *version = 1.40;    6 Feb 1998
+      *version = 1.33;   16 Dec 1997 (internal release only)
+      *version = 1.32;   21 Nov 1997 (internal release only)
+      *version = 1.31;    4 Nov 1997 (internal release only)
+      *version = 1.30;   11 Sep 1997
+      *version = 1.27;    3 Sep 1997 (internal release only)
+      *version = 1.25;    2 Jul 1997
+      *version = 1.24;    2 May 1997
+      *version = 1.23;   24 Apr 1997
+      *version = 1.22;   18 Apr 1997
+      *version = 1.21;   26 Mar 1997
+      *version = 1.2;    29 Jan 1997
+      *version = 1.11;   04 Dec 1996
+      *version = 1.101;  13 Nov 1996
+      *version = 1.1;     6 Nov 1996
+      *version = 1.04;   17 Sep 1996
+      *version = 1.03;   20 Aug 1996
+      *version = 1.02;   15 Aug 1996
+      *version = 1.01;   12 Aug 1996
+*/
+
+    return(*version);
+}
+/*--------------------------------------------------------------------------*/
+int ffflnm(fitsfile *fptr,    /* I - FITS file pointer  */
+           char *filename,    /* O - name of the file   */
+           int *status)       /* IO - error status      */
+/*
+  return the name of the FITS file
+*/
+{
+    strcpy(filename,(fptr->Fptr)->filename);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffflmd(fitsfile *fptr,    /* I - FITS file pointer  */
+           int *filemode,     /* O - open mode of the file  */
+           int *status)       /* IO - error status      */
+/*
+  return the access mode of the FITS file
+*/
+{
+    *filemode = (fptr->Fptr)->writemode;
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+void ffgerr(int status,     /* I - error status value */
+            char *errtext)  /* O - error message (max 30 char long + null) */
+/*
+  Return a short descriptive error message that corresponds to the input
+  error status value.  The message may be up to 30 characters long, plus
+  the terminating null character.
+*/
+{
+  errtext[0] = '\0';
+
+  if (status >= 0 && status < 300)
+  {
+    switch (status) {
+
+    case 0:
+       strcpy(errtext, "OK - no error");
+       break;
+    case 1:
+       strcpy(errtext, "non-CFITSIO program error");
+       break;
+    case 101:
+       strcpy(errtext, "same input and output files");
+       break;
+    case 103:
+       strcpy(errtext, "attempt to open too many files");
+       break;
+    case 104:
+       strcpy(errtext, "could not open the named file");
+       break;
+    case 105:
+       strcpy(errtext, "couldn't create the named file");
+       break;
+    case 106:
+       strcpy(errtext, "error writing to FITS file");
+       break;
+    case 107:
+       strcpy(errtext, "tried to move past end of file");
+       break;
+    case 108:
+       strcpy(errtext, "error reading from FITS file");
+       break;
+    case 110:
+       strcpy(errtext, "could not close the file");
+       break;
+    case 111:
+       strcpy(errtext, "array dimensions too big");
+       break;
+    case 112:
+       strcpy(errtext, "cannot write to readonly file");
+       break;
+    case 113:
+       strcpy(errtext, "could not allocate memory");
+       break;
+    case 114:
+       strcpy(errtext, "invalid fitsfile pointer");
+       break;
+    case 115:
+       strcpy(errtext, "NULL input pointer");
+       break;
+    case 116:
+       strcpy(errtext, "error seeking file position");
+       break;
+    case 121:
+       strcpy(errtext, "invalid URL prefix");
+       break;
+    case 122:
+       strcpy(errtext, "too many I/O drivers");
+       break;
+    case 123:
+       strcpy(errtext, "I/O driver init failed");
+       break;
+    case 124:
+       strcpy(errtext, "no I/O driver for this URLtype");
+       break;
+    case 125:
+       strcpy(errtext, "parse error in input file URL");
+       break;
+    case 126:
+       strcpy(errtext, "parse error in range list");
+       break;
+    case 151:
+       strcpy(errtext, "bad argument (shared mem drvr)");
+       break;
+    case 152:
+       strcpy(errtext, "null ptr arg (shared mem drvr)");
+       break;
+    case 153:
+       strcpy(errtext, "no free shared memory handles");
+       break;
+    case 154:
+       strcpy(errtext, "share mem drvr not initialized");
+       break;
+    case 155:
+       strcpy(errtext, "IPC system error (shared mem)");
+       break;
+    case 156:
+       strcpy(errtext, "no memory (shared mem drvr)");
+       break;
+    case 157:
+       strcpy(errtext, "share mem resource deadlock");
+       break;
+    case 158:
+       strcpy(errtext, "lock file open/create failed");
+       break;
+    case 159:
+       strcpy(errtext, "can't resize share mem block");
+       break;
+    case 201:
+       strcpy(errtext, "header already has keywords");
+       break;
+    case 202:
+       strcpy(errtext, "keyword not found in header");
+       break;
+    case 203:
+       strcpy(errtext, "keyword number out of bounds");
+       break;
+    case 204:
+       strcpy(errtext, "keyword value is undefined");
+       break;
+    case 205:
+       strcpy(errtext, "string missing closing quote");
+       break;
+    case 206:
+       strcpy(errtext, "error in indexed keyword name");
+       break;
+    case 207:
+       strcpy(errtext, "illegal character in keyword");
+       break;
+    case 208:
+       strcpy(errtext, "required keywords out of order");
+       break;
+    case 209:
+       strcpy(errtext, "keyword value not positive int");
+       break;
+    case 210:
+       strcpy(errtext, "END keyword not found");
+       break;
+    case 211:
+       strcpy(errtext, "illegal BITPIX keyword value");
+       break;
+    case 212:
+       strcpy(errtext, "illegal NAXIS keyword value");
+       break;
+    case 213:
+       strcpy(errtext, "illegal NAXISn keyword value");
+       break;
+    case 214:
+       strcpy(errtext, "illegal PCOUNT keyword value");
+       break;
+    case 215:
+       strcpy(errtext, "illegal GCOUNT keyword value");
+       break;
+    case 216:
+       strcpy(errtext, "illegal TFIELDS keyword value");
+       break;
+    case 217:
+       strcpy(errtext, "negative table row size");
+       break;
+    case 218:
+       strcpy(errtext, "negative number of rows");
+       break;
+    case 219:
+       strcpy(errtext, "named column not found");
+       break;
+    case 220:
+       strcpy(errtext, "illegal SIMPLE keyword value");
+       break;
+    case 221:
+       strcpy(errtext, "first keyword not SIMPLE");
+       break;
+    case 222:
+       strcpy(errtext, "second keyword not BITPIX");
+       break;
+    case 223:
+       strcpy(errtext, "third keyword not NAXIS");
+       break;
+    case 224:
+       strcpy(errtext, "missing NAXISn keywords");
+       break;
+    case 225:
+       strcpy(errtext, "first keyword not XTENSION");
+       break;
+    case 226:
+       strcpy(errtext, "CHDU not an ASCII table");
+       break;
+    case 227:
+       strcpy(errtext, "CHDU not a binary table");
+       break;
+    case 228:
+       strcpy(errtext, "PCOUNT keyword not found");
+       break;
+    case 229:
+       strcpy(errtext, "GCOUNT keyword not found");
+       break;
+    case 230:
+       strcpy(errtext, "TFIELDS keyword not found");
+       break;
+    case 231:
+       strcpy(errtext, "missing TBCOLn keyword");
+       break;
+    case 232:
+       strcpy(errtext, "missing TFORMn keyword");
+       break;
+    case 233:
+       strcpy(errtext, "CHDU not an IMAGE extension");
+       break;
+    case 234:
+       strcpy(errtext, "illegal TBCOLn keyword value");
+       break;
+    case 235:
+       strcpy(errtext, "CHDU not a table extension");
+       break;
+    case 236:
+       strcpy(errtext, "column exceeds width of table");
+       break;
+    case 237:
+       strcpy(errtext, "more than 1 matching col. name");
+       break;
+    case 241:
+       strcpy(errtext, "row width not = field widths");
+       break;
+    case 251:
+       strcpy(errtext, "unknown FITS extension type");
+       break;
+    case 252:
+       strcpy(errtext, "1st key not SIMPLE or XTENSION");
+       break;
+    case 253:
+       strcpy(errtext, "END keyword is not blank");
+       break;
+    case 254:
+       strcpy(errtext, "Header fill area not blank");
+       break;
+    case 255:
+       strcpy(errtext, "Data fill area invalid");
+       break;
+    case 261:
+       strcpy(errtext, "illegal TFORM format code");
+       break;
+    case 262:
+       strcpy(errtext, "unknown TFORM datatype code");
+       break;
+    case 263:
+       strcpy(errtext, "illegal TDIMn keyword value");
+       break;
+    case 264:
+       strcpy(errtext, "invalid BINTABLE heap pointer");
+       break;
+    default:
+       strcpy(errtext, "unknown error status");
+       break;
+    }
+  }
+  else if (status < 600)
+  {
+    switch(status) {
+
+    case 301:
+       strcpy(errtext, "illegal HDU number");
+       break;
+    case 302:
+       strcpy(errtext, "column number < 1 or > tfields");
+       break;
+    case 304:
+       strcpy(errtext, "negative byte address");
+       break;
+    case 306:
+       strcpy(errtext, "negative number of elements");
+       break;
+    case 307:
+       strcpy(errtext, "bad first row number");
+       break;
+    case 308:
+       strcpy(errtext, "bad first element number");
+       break;
+    case 309:
+       strcpy(errtext, "not an ASCII (A) column");
+       break;
+    case 310:
+       strcpy(errtext, "not a logical (L) column");
+       break;
+    case 311:
+       strcpy(errtext, "bad ASCII table datatype");
+       break;
+    case 312:
+       strcpy(errtext, "bad binary table datatype");
+       break;
+    case 314:
+       strcpy(errtext, "null value not defined");
+       break;
+    case 317:
+       strcpy(errtext, "not a variable length column");
+       break;
+    case 320:
+       strcpy(errtext, "illegal number of dimensions");
+       break;
+    case 321:
+       strcpy(errtext, "1st pixel no. > last pixel no.");
+       break;
+    case 322:
+       strcpy(errtext, "BSCALE or TSCALn = 0.");
+       break;
+    case 323:
+       strcpy(errtext, "illegal axis length < 1");
+       break;
+    case 340:
+       strcpy(errtext, "not group table");
+       break;
+    case 341:
+       strcpy(errtext, "HDU already member of group");
+       break;
+    case 342:
+       strcpy(errtext, "group member not found");
+       break;
+    case 343:
+       strcpy(errtext, "group not found");
+       break;
+    case 344:
+       strcpy(errtext, "bad group id");
+       break;
+    case 345:
+       strcpy(errtext, "too many HDUs tracked");
+       break;
+    case 346:
+       strcpy(errtext, "HDU alread tracked");
+       break;
+    case 347:
+       strcpy(errtext, "bad Grouping option");
+       break;
+    case 348:
+       strcpy(errtext, "identical pointers (groups)");
+       break;
+    case 360:
+       strcpy(errtext, "malloc failed in parser");
+       break;
+    case 361:
+       strcpy(errtext, "file read error in parser");
+       break;
+    case 362:
+       strcpy(errtext, "null pointer arg (parser)");
+       break;
+    case 363:
+       strcpy(errtext, "empty line (parser)");
+       break;
+    case 364:
+       strcpy(errtext, "cannot unread > 1 line");
+       break;
+    case 365:
+       strcpy(errtext, "parser too deeply nested");
+       break;
+    case 366:
+       strcpy(errtext, "file open failed (parser)");
+       break;
+    case 367:
+       strcpy(errtext, "hit EOF (parser)");
+       break;
+    case 368:
+       strcpy(errtext, "bad argument (parser)");
+       break;
+    case 369:
+       strcpy(errtext, "unexpected token (parser)");
+       break;
+    case 401:
+       strcpy(errtext, "bad int to string conversion");
+       break;
+    case 402:
+       strcpy(errtext, "bad float to string conversion");
+       break;
+    case 403:
+       strcpy(errtext, "keyword value not integer");
+       break;
+    case 404:
+       strcpy(errtext, "keyword value not logical");
+       break;
+    case 405:
+       strcpy(errtext, "keyword value not floating pt");
+       break;
+    case 406:
+       strcpy(errtext, "keyword value not double");
+       break;
+    case 407:
+       strcpy(errtext, "bad string to int conversion");
+       break;
+    case 408:
+       strcpy(errtext, "bad string to float conversion");
+       break;
+    case 409:
+       strcpy(errtext, "bad string to double convert");
+       break;
+    case 410:
+       strcpy(errtext, "illegal datatype code value");
+       break;
+    case 411:
+       strcpy(errtext, "illegal no. of decimals");
+       break;
+    case 412:
+       strcpy(errtext, "datatype conversion overflow");
+       break;
+    case 413:
+       strcpy(errtext, "error compressing image");
+       break;
+    case 414:
+       strcpy(errtext, "error uncompressing image");
+       break;
+    case 420:
+       strcpy(errtext, "bad date or time conversion");
+       break;
+    case 431:
+       strcpy(errtext, "syntax error in expression");
+       break;
+    case 432:
+       strcpy(errtext, "expression result wrong type");
+       break;
+    case 433:
+       strcpy(errtext, "vector result too large");
+       break;
+    case 434:
+       strcpy(errtext, "missing output column");
+       break;
+    case 435:
+       strcpy(errtext, "bad data in parsed column");
+       break;
+    case 436:
+       strcpy(errtext, "output extension of wrong type");
+       break;
+    case 501:
+       strcpy(errtext, "WCS angle too large");
+       break;
+    case 502:
+       strcpy(errtext, "bad WCS coordinate");
+       break;
+    case 503:
+       strcpy(errtext, "error in WCS calculation");
+       break;
+    case 504:
+       strcpy(errtext, "bad WCS projection type");
+       break;
+    case 505:
+       strcpy(errtext, "WCS keywords not found");
+       break;
+    default:
+       strcpy(errtext, "unknown error status");
+       break;
+    }
+  }
+  else
+  {
+     strcpy(errtext, "unknown error status");
+  }
+  return;
+}
+/*--------------------------------------------------------------------------*/
+void ffpmsg(const char *err_message)
+/*
+  put message on to error stack
+*/
+{
+    ffxmsg(PutMesg, (char *)err_message);
+    return;
+}
+/*--------------------------------------------------------------------------*/
+void ffpmrk(void)
+/*
+  write a marker to the stack.  It is then possible to pop only those
+  messages following the marker off of the stack, leaving the previous
+  messages unaffected.
+
+  The marker is ignored by the ffgmsg routine.
+*/
+{
+    char *dummy = 0;
+
+    ffxmsg(PutMark, dummy);
+    return;
+}
+/*--------------------------------------------------------------------------*/
+int ffgmsg(char *err_message)
+/*
+  get oldest message from error stack, ignoring markers
+*/
+{
+    ffxmsg(GetMesg, err_message);
+    return(*err_message);
+}
+/*--------------------------------------------------------------------------*/
+void ffcmsg(void)
+/*
+  erase all messages in the error stack
+*/
+{
+    char *dummy = 0;
+
+    ffxmsg(DelAll, dummy);
+    return;
+}
+/*--------------------------------------------------------------------------*/
+void ffcmrk(void)
+/*
+  erase newest messages in the error stack, stopping if a marker is found.
+  The marker is also erased in this case.
+*/
+{
+    char *dummy = 0;
+
+    ffxmsg(DelMark, dummy);
+    return;
+}
+/*--------------------------------------------------------------------------*/
+void ffxmsg( int action,
+            char *errmsg)
+/*
+  general routine to get, put, or clear the error message stack.
+  Use a static array rather than allocating memory as needed for
+  the error messages because it is likely to be more efficient
+  and simpler to implement.
+
+  Action Code:
+DelAll     1  delete all messages on the error stack 
+DelMark    2  delete messages back to and including the 1st marker 
+DelNewest  3  delete the newest message from the stack 
+GetMesg    4  pop and return oldest message, ignoring marks 
+PutMesg    5  add a new message to the stack 
+PutMark    6  add a marker to the stack 
+
+*/
+{
+    int ii;
+    char markflag;
+    static char *txtbuff[errmsgsiz], *tmpbuff, *msgptr;
+    static char errbuff[errmsgsiz][81];  /* initialize all = \0 */
+    static int nummsg = 0;
+
+    FFLOCK;
+    
+    if (action == DelAll)  /* clear the whole message stack */
+    {
+      for (ii = 0; ii < nummsg; ii ++)
+        *txtbuff[ii] = '\0';
+
+      nummsg = 0;
+    }
+    else if (action == DelMark)  /* clear up to and including first marker */
+    {
+      while (nummsg > 0) {
+        nummsg--;  
+        markflag = *txtbuff[nummsg]; /* store possible marker character */
+        *txtbuff[nummsg] = '\0';  /* clear the buffer for this msg */
+
+        if (markflag == ESMARKER)
+           break;   /* found a marker, so quit */
+      }
+    }
+    else if (action == DelNewest)  /* remove newest message from stack */ 
+    {
+      if (nummsg > 0)
+      {
+        nummsg--;  
+        *txtbuff[nummsg] = '\0';  /* clear the buffer for this msg */
+      }
+    }
+    else if (action == GetMesg)  /* pop and return oldest message from stack */ 
+    {                            /* ignoring markers */
+      while (nummsg > 0)
+      {
+         strcpy(errmsg, txtbuff[0]);   /* copy oldest message to output */
+
+         *txtbuff[0] = '\0';  /* clear the buffer for this msg */
+           
+         nummsg--;  
+         for (ii = 0; ii < nummsg; ii++)
+             txtbuff[ii] = txtbuff[ii + 1]; /* shift remaining pointers */
+
+         if (errmsg[0] != ESMARKER) {   /* quit if this is not a marker */
+            FFUNLOCK;
+            return;
+         }
+       }
+       errmsg[0] = '\0';  /*  no messages in the stack */
+    }
+    else if (action == PutMesg)  /* add new message to stack */
+    {
+     msgptr = errmsg;
+     while (strlen(msgptr))
+     {
+      if (nummsg == errmsgsiz)
+      {
+        tmpbuff = txtbuff[0];  /* buffers full; reuse oldest buffer */
+        *txtbuff[0] = '\0';  /* clear the buffer for this msg */
+
+        nummsg--;
+        for (ii = 0; ii < nummsg; ii++)
+             txtbuff[ii] = txtbuff[ii + 1];   /* shift remaining pointers */
+
+        txtbuff[nummsg] = tmpbuff;  /* set pointer for the new message */
+      }
+      else
+      {
+        for (ii = 0; ii < errmsgsiz; ii++)
+        {
+          if (*errbuff[ii] == '\0') /* find first empty buffer */
+          {
+            txtbuff[nummsg] = errbuff[ii];
+            break;
+          }
+        }
+      }
+
+      strncat(txtbuff[nummsg], msgptr, 80);
+      nummsg++;
+
+      msgptr += minvalue(80, strlen(msgptr));
+     }
+    }
+    else if (action == PutMark)  /* put a marker on the stack */
+    {
+      if (nummsg == errmsgsiz)
+      {
+        tmpbuff = txtbuff[0];  /* buffers full; reuse oldest buffer */
+        *txtbuff[0] = '\0';  /* clear the buffer for this msg */
+
+        nummsg--;
+        for (ii = 0; ii < nummsg; ii++)
+             txtbuff[ii] = txtbuff[ii + 1];   /* shift remaining pointers */
+
+        txtbuff[nummsg] = tmpbuff;  /* set pointer for the new message */
+      }
+      else
+      {
+        for (ii = 0; ii < errmsgsiz; ii++)
+        {
+          if (*errbuff[ii] == '\0') /* find first empty buffer */
+          {
+            txtbuff[nummsg] = errbuff[ii];
+            break;
+          }
+        }
+      }
+
+      *txtbuff[nummsg] = ESMARKER;      /* write the marker */
+      *(txtbuff[nummsg] + 1) = '\0';
+      nummsg++;
+
+    }
+
+    FFUNLOCK;
+    return;
+}
+/*--------------------------------------------------------------------------*/
+int ffpxsz(int datatype)
+/*
+   return the number of bytes per pixel associated with the datatype
+*/
+{
+    if (datatype == TBYTE)
+       return(sizeof(char));
+    else if (datatype == TUSHORT)
+       return(sizeof(short));
+    else if (datatype == TSHORT)
+       return(sizeof(short));
+    else if (datatype == TULONG)
+       return(sizeof(long));
+    else if (datatype == TLONG)
+       return(sizeof(long));
+    else if (datatype == TINT)
+       return(sizeof(int));
+    else if (datatype == TUINT)
+       return(sizeof(int));
+    else if (datatype == TFLOAT)
+       return(sizeof(float));
+    else if (datatype == TDOUBLE)
+       return(sizeof(double));
+    else if (datatype == TLOGICAL)
+       return(sizeof(char));
+    else
+       return(0);
+}
+/*--------------------------------------------------------------------------*/
+int fftkey(const char *keyword,    /* I -  keyword name */
+           int *status)      /* IO - error status */
+/*
+  Test that the keyword name conforms to the FITS standard.  Must contain
+  only capital letters, digits, minus or underscore chars.  Trailing spaces
+  are allowed.  If the input status value is less than zero, then the test
+  is modified so that upper or lower case letters are allowed, and no 
+  error messages are printed if the keyword is not legal.
+*/
+{
+    size_t maxchr, ii;
+    int spaces=0;
+    char msg[81], testchar;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    maxchr=strlen(keyword);
+    if (maxchr > 8)
+        maxchr = 8;
+
+    for (ii = 0; ii < maxchr; ii++)
+    {
+        if (*status == 0)
+            testchar = keyword[ii];
+        else
+            testchar = toupper(keyword[ii]);
+
+        if ( (testchar >= 'A' && testchar <= 'Z') ||
+             (testchar >= '0' && testchar <= '9') ||
+              testchar == '-' || testchar == '_'   )
+              {
+                if (spaces)
+                {
+                  if (*status == 0)
+                  {
+                     /* don't print error message if status < 0  */
+                    sprintf(msg,
+                       "Keyword name contains embedded space(s): %.8s",
+                        keyword);
+                     ffpmsg(msg);
+                  }
+                  return(*status = BAD_KEYCHAR);        
+                }
+              }
+        else if (keyword[ii] == ' ')
+            spaces = 1;
+
+        else     
+        {
+          if (*status == 0)
+          {
+            /* don't print error message if status < 0  */
+            sprintf(msg, "Character %d in this keyword is illegal: %.8s",
+                    (int) (ii+1), keyword);
+            ffpmsg(msg);
+
+            /* explicitly flag the 2 most common cases */
+            if (keyword[ii] == 0) 
+                ffpmsg(" (This a NULL (0) character).");                
+            else if (keyword[ii] == 9)
+                ffpmsg(" (This an ASCII TAB (9) character).");   
+          }             
+
+          return(*status = BAD_KEYCHAR);        
+        }                
+    }
+    return(*status);        
+}
+/*--------------------------------------------------------------------------*/
+int fftrec(char *card,       /* I -  keyword card to test */
+           int *status)      /* IO - error status */
+/*
+  Test that the keyword card conforms to the FITS standard.  Must contain
+  only printable ASCII characters;
+*/
+{
+    size_t ii, maxchr;
+    char msg[81];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    maxchr = strlen(card);
+
+    for (ii = 8; ii < maxchr; ii++)
+    {
+        if (card[ii] < 32 || card[ii] > 126)
+        {
+            sprintf(msg, 
+           "Character %d in this keyword is illegal. Hex Value = %X",
+              (int) (ii+1), (int) card[ii] );
+
+            if (card[ii] == 0)
+	        strcat(msg, " (NULL char.)");
+            else if (card[ii] == 9)
+	        strcat(msg, " (TAB char.)");
+            else if (card[ii] == 10)
+	        strcat(msg, " (Line Feed char.)");
+            else if (card[ii] == 11)
+	        strcat(msg, " (Vertical Tab)");
+            else if (card[ii] == 12)
+	        strcat(msg, " (Form Feed char.)");
+            else if (card[ii] == 13)
+	        strcat(msg, " (Carriage Return)");
+            else if (card[ii] == 27)
+	        strcat(msg, " (Escape char.)");
+            else if (card[ii] == 127)
+	        strcat(msg, " (Delete char.)");
+
+            ffpmsg(msg);
+
+            strncpy(msg, card, 80);
+            msg[80] = '\0';
+            ffpmsg(msg);
+            return(*status = BAD_KEYCHAR);        
+        }
+    }
+    return(*status);        
+}
+/*--------------------------------------------------------------------------*/
+void ffupch(char *string)
+/*
+  convert string to upper case, in place.
+*/
+{
+    size_t len, ii;
+
+    len = strlen(string);
+    for (ii = 0; ii < len; ii++)
+        string[ii] = toupper(string[ii]);
+    return;
+}
+/*--------------------------------------------------------------------------*/
+int ffmkky(const char *keyname,   /* I - keyword name    */
+            char *value,     /* I - keyword value   */
+            const char *comm,      /* I - keyword comment */
+            char *card,      /* O - constructed keyword card */
+            int  *status)    /* IO - status value   */
+/*
+  Make a complete FITS 80-byte keyword card from the input name, value and
+  comment strings. Output card is null terminated without any trailing blanks.
+*/
+{
+    size_t namelen, len, ii;
+    char tmpname[FLEN_KEYWORD], *cptr;
+    int tstatus = -1, nblank = 0;
+
+    if (*status > 0)
+        return(*status);
+
+    *tmpname = '\0';
+    *card = '\0';
+
+    while(*(keyname + nblank) == ' ')  /* skip leading blanks in the name */
+        nblank++;
+
+    strncat(tmpname, keyname + nblank, FLEN_KEYWORD - 1);
+
+    len = strlen(value);        
+    namelen = strlen(tmpname);
+
+    if (namelen)
+    {
+        cptr = tmpname + namelen - 1;
+
+        while(*cptr == ' ')  /* skip trailing blanks */
+        {
+            *cptr = '\0';
+            cptr--;
+        }
+
+        namelen = cptr - tmpname + 1;
+    }
+    
+    if (namelen <= 8  && (fftkey(keyname, &tstatus) <= 0) )
+    {
+        /* a normal FITS keyword */
+        strcat(card, tmpname);   /* copy keyword name to buffer */
+   
+        for (ii = namelen; ii < 8; ii++)
+            card[ii] = ' ';      /* pad keyword name with spaces */
+
+        card[8]  = '=';          /* append '= ' in columns 9-10 */
+        card[9]  = ' ';
+        card[10] = '\0';        /* terminate the partial string */
+        namelen = 10;
+    }
+    else
+    {
+        /* use the ESO HIERARCH convention for longer keyword names */
+
+        /* check that the name does not contain an '=' (equals sign) */
+        if (strchr(tmpname, '=') )
+        {
+            ffpmsg("Illegal keyword name; contains an equals sign (=)");
+            ffpmsg(tmpname);
+            return(*status = BAD_KEYCHAR);
+        }
+
+        /* Don't repeat HIERARCH if the keyword already contains it */
+        if (FSTRNCMP(tmpname, "HIERARCH ", 9) && 
+            FSTRNCMP(tmpname, "hierarch ", 9))
+            strcat(card, "HIERARCH ");
+        else
+            namelen -= 9;  /* deleted the string 'HIERARCH ' */
+
+        strcat(card, tmpname);
+
+        if (namelen + 12 + len > 80) {
+            /* save 1 char by not putting a space before the equals sign */
+            strcat(card, "= ");
+            namelen += 11;
+        } else {
+            strcat(card, " = ");
+            namelen += 12;
+        }
+    }
+
+    if (len > 0)
+    {
+        if (value[0] == '\'')  /* is this a quoted string value? */
+        {
+            if (namelen > 77)
+            {
+                ffpmsg(
+               "The following keyword + value is too long to fit on a card:");
+                ffpmsg(keyname);
+                ffpmsg(value);
+                return(*status = BAD_KEYCHAR);
+            }
+
+            strncat(card, value, 80 - namelen); /* append the value string */
+            len = minvalue(80, namelen + len);
+
+            /* restore the closing quote if it got truncated */
+            if (len == 80)
+            {
+                   card[79] = '\'';
+            }
+
+            if (comm)
+            {
+              if (comm[0] != 0)
+              {
+                if (len < 30)
+                {
+                  for (ii = len; ii < 30; ii++)
+                    card[ii] = ' '; /* fill with spaces to col 30 */
+
+                  card[30] = '\0';
+                  len = 30;
+                }
+              }
+            }
+        }
+        else
+        {
+            if (namelen + len > 80)
+            {
+                ffpmsg(
+               "The following keyword + value is too long to fit on a card:");
+                ffpmsg(keyname);
+                ffpmsg(value);
+                return(*status = BAD_KEYCHAR);
+            }
+            else if (namelen + len < 30)
+            {
+                /* add spaces so field ends at least in col 30 */
+                strncat(card, "                    ", 30 - (namelen + len));
+            }
+
+            strncat(card, value, 80 - namelen); /* append the value string */
+            len = minvalue(80, namelen + len);
+            len = maxvalue(30, len);
+        }
+
+        if (comm)
+        {
+          if ((len < 77) && ( strlen(comm) > 0) )  /* room for a comment? */
+          {
+            strcat(card, " / ");   /* append comment separator */
+            strncat(card, comm, 77 - len); /* append comment (what fits) */
+          } 
+        }
+    }
+    else
+    {
+      if (namelen == 10)  /* This case applies to normal keywords only */
+      {
+        card[8] = ' '; /* keywords with no value have no '=' */ 
+        if (comm)
+        {
+          strncat(card, comm, 80 - namelen); /* append comment (what fits) */
+        }
+      }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffmkey(fitsfile *fptr,    /* I - FITS file pointer  */
+           char *card,        /* I - card string value  */
+           int *status)       /* IO - error status      */
+/*
+  replace the previously read card (i.e. starting 80 bytes before the
+  (fptr->Fptr)->nextkey position) with the contents of the input card.
+*/
+{
+    char tcard[81];
+    size_t len, ii;
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    strncpy(tcard,card,80);
+    tcard[80] = '\0';
+
+    len = strlen(tcard);
+
+    /* silently replace any illegal characters with a space */
+    for (ii=0; ii < len; ii++)  
+        if (tcard[ii] < ' ' || tcard[ii] > 126) tcard[ii] = ' ';
+
+    for (ii=len; ii < 80; ii++)    /* fill card with spaces if necessary */
+        tcard[ii] = ' ';
+
+    for (ii=0; ii < 8; ii++)       /* make sure keyword name is uppercase */
+        tcard[ii] = toupper(tcard[ii]);
+
+    fftkey(tcard, status);        /* test keyword name contains legal chars */
+
+/*  no need to do this any more, since any illegal characters have been removed
+    fftrec(tcard, status);   */     /* test rest of keyword for legal chars   */
+
+    /* move position of keyword to be over written */
+    ffmbyt(fptr, ((fptr->Fptr)->nextkey) - 80, REPORT_EOF, status); 
+    ffpbyt(fptr, 80, tcard, status);   /* write the 80 byte card */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffkeyn(const char *keyroot,   /* I - root string for keyword name */
+           int value,       /* I - index number to be appended to root name */
+           char *keyname,   /* O - output root + index keyword name */
+           int *status)     /* IO - error status  */
+/*
+  Construct a keyword name string by appending the index number to the root.
+  e.g., if root = "TTYPE" and value = 12 then keyname = "TTYPE12".
+*/
+{
+    char suffix[16];
+    size_t rootlen;
+
+    keyname[0] = '\0';            /* initialize output name to null */
+    rootlen = strlen(keyroot);
+
+    if (rootlen == 0 || rootlen > 7 || value < 0 )
+       return(*status = 206);
+
+    sprintf(suffix, "%d", value); /* construct keyword suffix */
+
+    if ( strlen(suffix) + rootlen > 8)
+       return(*status = 206);
+
+    strcpy(keyname, keyroot);   /* copy root string to name string */
+    strcat(keyname, suffix);    /* append suffix to the root */
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffnkey(int value,       /* I - index number to be appended to root name */
+           char *keyroot,   /* I - root string for keyword name */
+           char *keyname,   /* O - output root + index keyword name */
+           int *status)     /* IO - error status  */
+/*
+  Construct a keyword name string by appending the root string to the index
+  number. e.g., if root = "TTYPE" and value = 12 then keyname = "12TTYPE".
+*/
+{
+    size_t rootlen;
+
+    keyname[0] = '\0';            /* initialize output name to null */
+    rootlen = strlen(keyroot);
+
+    if (rootlen == 0 || rootlen > 7 || value < 0 )
+       return(*status = 206);
+
+    sprintf(keyname, "%d", value); /* construct keyword prefix */
+
+    if (rootlen +  strlen(keyname) > 8)
+       return(*status = 206);
+
+    strcat(keyname, keyroot);  /* append root to the prefix */
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpsvc(char *card,    /* I - FITS header card (nominally 80 bytes long) */
+           char *value,   /* O - value string parsed from the card */
+           char *comm,    /* O - comment string parsed from the card */
+           int *status)   /* IO - error status   */
+/*
+  ParSe the Value and Comment strings from the input header card string.
+  If the card contains a quoted string value, the returned value string
+  includes the enclosing quote characters.  If comm = NULL, don't return
+  the comment string.
+*/
+{
+    int jj;
+    size_t ii, cardlen, nblank, valpos;
+
+    if (*status > 0)
+        return(*status);
+
+    value[0] = '\0';
+    if (comm)
+        comm[0] = '\0';
+
+    cardlen = strlen(card);
+    
+    /* support for ESO HIERARCH keywords; find the '=' */
+    if (FSTRNCMP(card, "HIERARCH ", 9) == 0)
+    {
+      valpos = strcspn(card, "=");
+
+      if (valpos == cardlen)   /* no value indicator ??? */
+      {
+        if (comm != NULL)
+        {
+          if (cardlen > 8)
+          {
+            strcpy(comm, &card[8]);
+
+            jj=cardlen - 8;
+            for (jj--; jj >= 0; jj--)  /* replace trailing blanks with nulls */
+            {
+               if (comm[jj] == ' ')
+                  comm[jj] = '\0';
+               else
+                  break;
+            }
+          }
+        }
+        return(*status);  /* no value indicator */
+      }
+      valpos++;  /* point to the position after the '=' */
+    }
+    else if (cardlen < 9  ||
+        FSTRNCMP(card, "COMMENT ", 8) == 0 ||  /* keywords with no value */
+        FSTRNCMP(card, "HISTORY ", 8) == 0 ||
+        FSTRNCMP(card, "END     ", 8) == 0 ||
+        FSTRNCMP(card, "        ", 8) == 0 ||
+        FSTRNCMP(&card[8],      "= ", 2) != 0  ) /* no '= ' in cols 9-10 */
+    {
+        /*  no value, so the comment extends from cols 9 - 80  */
+        if (comm != NULL)
+        {
+          if (cardlen > 8)
+          {
+             strcpy(comm, &card[8]);
+
+             jj=cardlen - 8;
+             for (jj--; jj >= 0; jj--)  /* replace trailing blanks with nulls */
+             {
+               if (comm[jj] == ' ')
+                  comm[jj] = '\0';
+               else
+                  break;
+             }
+          }
+        }
+        return(*status);
+    }
+    else
+    {
+        valpos = 10;  /* starting position of the value field */
+    }
+
+    nblank = strspn(&card[valpos], " "); /* find number of leading blanks */
+
+    if (nblank + valpos == cardlen)
+    {
+      /* the absence of a value string is legal, and simply indicates
+         that the keyword value is undefined.  Don't write an error
+         message in this case.
+      */
+        return(*status);
+    }
+
+    ii = valpos + nblank;
+
+    if (card[ii] == '/' )  /* slash indicates start of the comment */
+    {
+         ii++;
+    }
+    else if (card[ii] == '\'' )  /* is this a quoted string value? */
+    {
+        value[0] = card[ii];
+        for (jj=1, ii++; ii < cardlen; ii++, jj++)
+        {
+            if (card[ii] == '\'')  /*  is this the closing quote?  */
+            {
+                if (card[ii+1] == '\'')  /* 2 successive quotes? */ 
+                {
+                   value[jj] = card[ii];
+                   ii++;  
+                   jj++;
+                }
+                else
+                {
+                    value[jj] = card[ii];
+                    break;   /* found the closing quote, so exit this loop  */
+                }
+            }
+            value[jj] = card[ii];  /* copy the next character to the output */
+        }
+
+        if (ii == cardlen)
+        {
+            jj = minvalue(jj, 69);  /* don't exceed 70 char string length */
+            value[jj] = '\'';  /*  close the bad value string  */
+            value[jj+1] = '\0';  /*  terminate the bad value string  */
+            ffpmsg("This keyword string value has no closing quote:");
+            ffpmsg(card);
+	    /*  May 2008 - modified to not fail on this minor error  */
+/*            return(*status = NO_QUOTE);  */
+        }
+        else
+        {
+            value[jj+1] = '\0';  /*  terminate the good value string  */
+            ii++;   /*  point to the character following the value  */
+        }
+    }
+    else if (card[ii] == '(' )  /* is this a complex value? */
+    {
+        nblank = strcspn(&card[ii], ")" ); /* find closing ) */
+        if (nblank == strlen( &card[ii] ) )
+        {
+            ffpmsg("This complex keyword value has no closing ')':");
+            ffpmsg(card);
+            return(*status = NO_QUOTE);
+        }
+
+        nblank++;
+        strncpy(value, &card[ii], nblank);
+        value[nblank] = '\0';
+        ii = ii + nblank;        
+    }
+    else   /*  an integer, floating point, or logical FITS value string  */
+    {
+        nblank = strcspn(&card[ii], " /");  /* find the end of the token */
+        strncpy(value, &card[ii], nblank);
+        value[nblank] = '\0';
+        ii = ii + nblank;
+    }
+
+    /*  now find the comment string, if any  */
+    if (comm)
+    {
+      nblank = strspn(&card[ii], " ");  /*  find next non-space character  */
+      ii = ii + nblank;
+
+      if (ii < 80)
+      {
+        if (card[ii] == '/')   /*  ignore the slash separator  */
+        {
+            ii++;
+            if (card[ii] == ' ')  /*  also ignore the following space  */
+                ii++;
+        }
+        strcat(comm, &card[ii]);  /*  copy the remaining characters  */
+
+        jj=strlen(comm);
+        for (jj--; jj >= 0; jj--)  /* replace trailing blanks with nulls */
+        {
+            if (comm[jj] == ' ')
+                comm[jj] = '\0';
+            else
+                break;
+        }
+      }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgthd(char *tmplt, /* I - input header template string */
+           char *card,  /* O - returned FITS header record */
+           int *hdtype, /* O - how to interpreter the returned card string */ 
+            /*
+              -2 = modify the name of a keyword; the old keyword name
+                   is returned starting at address chars[0]; the new name
+                   is returned starting at address char[40] (to be consistent
+                   with the Fortran version).  Both names are null terminated. 
+              -1 = card contains the name of a keyword that is to be deleted
+               0 = append this keyword if it doesn't already exist, or 
+                   modify the value if the keyword already exists.
+               1 = append this comment keyword ('HISTORY', 
+                   'COMMENT', or blank keyword name) 
+               2  =  this is the END keyword; do not write it to the header
+            */
+           int *status)   /* IO - error status   */
+/*
+  'Get Template HeaDer'
+  parse a template header line and create a formated
+  character string which is suitable for appending to a FITS header 
+*/
+{
+    char keyname[FLEN_KEYWORD], value[140], comment[140];
+    char *tok, *suffix, *loc, tvalue[140];
+    int len, vlen, more, tstatus;
+    double dval;
+
+    if (*status > 0)
+        return(*status);
+
+    card[0]   = '\0';
+    *hdtype   = 0;
+
+    if (!FSTRNCMP(tmplt, "        ", 8) )
+    {
+        /* if first 8 chars of template are blank, then this is a comment */
+        strncat(card, tmplt, 80);
+        *hdtype = 1;
+        return(*status);
+    }
+
+    tok = tmplt;   /* point to start of template string */
+ 
+    keyname[0] = '\0';
+    value[0]   = '\0';
+    comment[0] = '\0';
+
+    len = strspn(tok, " ");  /* no. of spaces before keyword */
+    tok += len;
+
+    /* test for pecular case where token is a string of dashes */
+    if (strncmp(tok, "--------------------", 20) == 0)
+            return(*status = BAD_KEYCHAR);
+
+    if (tok[0] == '-')  /* is there a leading minus sign? */
+    {
+        /* first token is name of keyword to be deleted or renamed */
+        *hdtype = -1;
+        tok++;
+        len = strspn(tok, " ");  /* no. of spaces before keyword */
+        tok += len;
+        if (len < 8)  /* not a blank name? */
+        {
+          len = strcspn(tok, " =");  /* length of name */
+          if (len >= FLEN_KEYWORD)
+            return(*status = BAD_KEYCHAR);
+
+          strncat(card, tok, len);
+
+          /*
+            The HIERARCH convention supports non-standard characters
+            in the keyword name, so don't always convert to upper case or
+            abort if there are illegal characters in the name or if the
+            name is greater than 8 characters long.
+          */
+
+          if (len < 9)  /* this is possibly a normal FITS keyword name */
+          {
+            ffupch(card);
+            tstatus = 0;
+            if (fftkey(card, &tstatus) > 0)
+            {
+               /* name contained non-standard characters, so reset */
+               card[0] = '\0';
+               strncat(card, tok, len);
+            }
+          }
+
+          tok += len;
+        }
+
+        /* second token, if present, is the new name for the keyword */
+
+        len = strspn(tok, " ");  /* no. of spaces before next token */
+        tok += len;
+
+        if (tok[0] == '\0' || tok[0] == '=')
+            return(*status);  /* no second token */
+
+        *hdtype = -2;
+        len = strcspn(tok, " ");  /* length of new name */
+        if (len > 40)  /* name has to fit on columns 41-80 of card */
+          return(*status = BAD_KEYCHAR);
+
+        /* copy the new name to card + 40;  This is awkward, */
+        /* but is consistent with the way the Fortran FITSIO works */
+	strcat(card,"                                        ");
+        strncpy(&card[40], tok, len+1); /* copy len+1 to get terminator */
+
+        /*
+            The HIERARCH convention supports non-standard characters
+            in the keyword name, so don't always convert to upper case or
+            abort if there are illegal characters in the name or if the
+            name is greater than 8 characters long.
+        */
+
+        if (len < 9)  /* this is possibly a normal FITS keyword name */
+        {
+            ffupch(&card[40]);
+            tstatus = 0;
+            if (fftkey(&card[40], &tstatus) > 0)
+            {
+               /* name contained non-standard characters, so reset */
+               strncpy(&card[40], tok, len);
+            }
+        }
+    }
+    else  /* no negative sign at beginning of template */
+    {
+      /* get the keyword name token */
+
+      len = strcspn(tok, " =");  /* length of keyword name */
+      if (len >= FLEN_KEYWORD)
+        return(*status = BAD_KEYCHAR);
+
+      strncat(keyname, tok, len);
+
+      /*
+        The HIERARCH convention supports non-standard characters
+        in the keyword name, so don't always convert to upper case or
+        abort if there are illegal characters in the name or if the
+        name is greater than 8 characters long.
+      */
+
+      if (len < 9)  /* this is possibly a normal FITS keyword name */
+      {
+        ffupch(keyname);
+        tstatus = 0;
+        if (fftkey(keyname, &tstatus) > 0)
+        {
+           /* name contained non-standard characters, so reset */
+           keyname[0] = '\0';
+           strncat(keyname, tok, len);
+        }
+      }
+
+      if (!FSTRCMP(keyname, "END") )
+      {
+         strcpy(card, "END");
+         *hdtype = 2;
+         return(*status);
+      }
+
+      tok += len; /* move token pointer to end of the keyword */
+
+      if (!FSTRCMP(keyname, "COMMENT") || !FSTRCMP(keyname, "HISTORY")
+         || !FSTRCMP(keyname, "HIERARCH") )
+      {
+        *hdtype = 1;   /* simply append COMMENT and HISTORY keywords */
+        strcpy(card, keyname);
+        strncat(card, tok, 73);
+        return(*status);
+      }
+
+      /* look for the value token */
+      len = strspn(tok, " =");  /* spaces or = between name and value */
+      tok += len;
+
+      if (*tok == '\'') /* is value enclosed in quotes? */
+      {
+          more = TRUE;
+          while (more)
+          {
+            tok++;  /* temporarily move past the quote char */
+            len = strcspn(tok, "'");  /* length of quoted string */
+            tok--;
+            strncat(value, tok, len + 2); 
+ 
+            tok += len + 1;
+            if (tok[0] != '\'')   /* check there is a closing quote */
+              return(*status = NO_QUOTE);
+
+            tok++;
+            if (tok[0] != '\'')  /* 2 quote chars = literal quote */
+              more = FALSE;
+          }
+      }
+      else if (*tok == '/' || *tok == '\0')  /* There is no value */
+      {
+          strcat(value, " ");
+      }
+      else   /* not a quoted string value */
+      {
+          len = strcspn(tok, " /"); /* length of value string */
+
+          strncat(value, tok, len);
+          if (!( (tok[0] == 'T' || tok[0] == 'F') &&
+                 (tok[1] == ' ' || tok[1] == '/' || tok[1] == '\0') )) 
+          {
+             /* not a logical value */
+
+            dval = strtod(value, &suffix); /* try to read value as number */
+
+            if (*suffix != '\0' && *suffix != ' ' && *suffix != '/')
+            { 
+                /* value not recognized as a number; might be because it */
+                /* contains a 'd' or 'D' exponent character  */ 
+                strcpy(tvalue, value);
+                if ((loc = strchr(tvalue, 'D')))
+                {          
+                    *loc = 'E'; /*  replace D's with E's. */ 
+                    dval = strtod(tvalue, &suffix); /* read value again */
+                }
+                else if ((loc = strchr(tvalue, 'd')))
+                {
+                    *loc = 'E'; /*  replace d's with E's. */ 
+                    dval = strtod(tvalue, &suffix); /* read value again */
+                }
+                else if ((loc = strchr(tvalue, '.')))
+                {
+                    *loc = ','; /*  replace period with a comma */ 
+                    dval = strtod(tvalue, &suffix); /* read value again */
+                }
+            }
+   
+            if (*suffix != '\0' && *suffix != ' ' && *suffix != '/')
+            { 
+              /* value is not a number; must enclose it in quotes */
+              strcpy(value, "'");
+              strncat(value, tok, len);
+              strcat(value, "'");
+
+              /* the following useless statement stops the compiler warning */
+              /* that dval is not used anywhere */
+              if (dval == 0.)
+                 len += (int) dval; 
+            }
+            else  
+            {
+                /* value is a number; convert any 'e' to 'E', or 'd' to 'D' */
+                loc = strchr(value, 'e');
+                if (loc)
+                {          
+                    *loc = 'E';  
+                }
+                else
+                {
+                    loc = strchr(value, 'd');
+                    if (loc)
+                    {          
+                        *loc = 'D';  
+                    }
+                }
+            }
+          }
+          tok += len;
+      }
+
+      len = strspn(tok, " /"); /* no. of spaces between value and comment */
+      tok += len;
+
+      vlen = strlen(value);
+      if (vlen > 0 && vlen < 10 && value[0] == '\'')
+      {
+          /* pad quoted string with blanks so it is at least 8 chars long */
+          value[vlen-1] = '\0';
+          strncat(value, "        ", 10 - vlen);
+          strcat(&value[9], "'");
+      }
+
+      /* get the comment string */
+      strncat(comment, tok, 70);
+
+      /* construct the complete FITS header card */
+      ffmkky(keyname, value, comment, card, status);
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_translate_keyword(
+      char *inrec,        /* I - input string */
+      char *outrec,       /* O - output converted string, or */
+                          /*     a null string if input does not  */
+                          /*     match any of the patterns */
+      char *patterns[][2],/* I - pointer to input / output string */
+                          /*     templates */
+      int npat,           /* I - number of templates passed */
+      int n_value,        /* I - base 'n' template value of interest */
+      int n_offset,       /* I - offset to be applied to the 'n' */
+                          /*     value in the output string */
+      int n_range,        /* I - controls range of 'n' template */
+                          /*     values of interest (-1,0, or +1) */
+      int *pat_num,       /* O - matched pattern number (0 based) or -1 */
+      int *i,             /* O - value of i, if any, else 0 */
+      int *j,             /* O - value of j, if any, else 0 */
+      int *m,             /* O - value of m, if any, else 0 */
+      int *n,             /* O - value of n, if any, else 0 */
+
+      int *status)        /* IO - error status */
+
+/* 
+
+Translate a keyword name to a new name, based on a set of patterns.
+The user passes an array of patterns to be matched.  Input pattern
+number i is pattern[i][0], and output pattern number i is
+pattern[i][1].  Keywords are matched against the input patterns.  If a
+match is found then the keyword is re-written according to the output
+pattern.
+
+Order is important.  The first match is accepted.  The fastest match
+will be made when templates with the same first character are grouped
+together.
+
+Several characters have special meanings:
+
+     i,j - single digits, preserved in output template
+     n - column number of one or more digits, preserved in output template
+     m - generic number of one or more digits, preserved in output template
+     a - coordinate designator, preserved in output template
+     # - number of one or more digits
+     ? - any character
+     * - only allowed in first character position, to match all
+         keywords; only useful as last pattern in the list
+
+i, j, n, and m are returned by the routine.
+
+For example, the input pattern "iCTYPn" will match "1CTYP5" (if n_value
+is 5); the output pattern "CTYPEi" will be re-written as "CTYPE1".
+Notice that "i" is preserved.
+
+The following output patterns are special
+
+Special output pattern characters:
+
+    "-" - do not copy a keyword that matches the corresponding input pattern
+
+    "+" - copy the input unchanged
+
+The inrec string could be just the 8-char keyword name, or the entire 
+80-char header record.  Characters 9 = 80 in the input string simply get
+appended to the translated keyword name.
+
+If n_range = 0, then only keywords with 'n' equal to n_value will be 
+considered as a pattern match.  If n_range = +1, then all values of 
+'n' greater than or equal to n_value will be a match, and if -1, 
+then values of 'n' less than or equal to n_value will match.
+
+  This routine was written by Craig Markwardt, GSFC
+*/
+
+{
+    int i1 = 0, j1 = 0, n1 = 0, m1 = 0;
+    int fac;
+    char a = ' ';
+    char oldp;
+    char c, s;
+    int ip, ic, pat, pass = 0, firstfail;
+    char *spat;
+
+    if (*status > 0)
+        return(*status);
+    if ((inrec == 0) || (outrec == 0)) 
+      return (*status = NULL_INPUT_PTR);
+
+    *outrec = '\0';
+/*
+    if (*inrec == '\0') return 0;
+*/
+
+    if (*inrec == '\0')    /* expand to full 8 char blank keyword name */
+       strcpy(inrec, "        ");
+       
+    oldp = '\0';
+    firstfail = 0;
+
+    /* ===== Pattern match stage */
+    for (pat=0; pat < npat; pat++) {
+      spat = patterns[pat][0];
+      
+      i1 = 0; j1 = 0; m1 = -1; n1 = -1; a = ' ';  /* Initialize the place-holders */
+      pass = 0;
+      
+      /* Pass the wildcard pattern */
+      if (spat[0] == '*') { 
+	pass = 1;
+	break;
+      }
+      
+      /* Optimization: if we have seen this initial pattern character before,
+	 then it must have failed, and we can skip the pattern */
+      if (firstfail && spat[0] == oldp) continue;
+      oldp = spat[0];
+
+      /* 
+	 ip = index of pattern character being matched
+	 ic = index of keyname character being matched
+	 firstfail = 1 if we fail on the first characteor (0=not)
+      */
+      
+      for (ip=0, ic=0, firstfail=1;
+	   (spat[ip]) && (ic < 8);
+	   ip++, ic++, firstfail=0) {
+	c = inrec[ic];
+	s = spat[ip];
+
+	if (s == 'i') {
+	  /* Special pattern: 'i' placeholder */
+	  if (isdigit(c)) { i1 = c - '0'; pass = 1;}
+	} else if (s == 'j') {
+	  /* Special pattern: 'j' placeholder */
+	  if (isdigit(c)) { j1 = c - '0'; pass = 1;}
+	} else if ((s == 'n')||(s == 'm')||(s == '#')) {
+	  /* Special patterns: multi-digit number */
+	  int val = 0;
+	  pass = 0;
+	  if (isdigit(c)) {
+	    pass = 1;  /* NOTE, could fail below */
+	    
+	    /* Parse decimal number */
+	    while (ic<8 && isdigit(c)) { 
+	      val = val*10 + (c - '0');
+	      ic++; c = inrec[ic];
+	    }
+	    ic--; c = inrec[ic];
+	    
+	    if (s == 'n') { 
+	      
+	      /* Is it a column number? */
+	      if ( val >= 1 && val <= 999 &&                    /* Row range check */
+		   (((n_range == 0) && (val == n_value)) ||     /* Strict equality */
+		    ((n_range == -1) && (val <= n_value)) ||    /* n <= n_value */
+		    ((n_range == +1) && (val >= n_value))) ) {  /* n >= n_value */
+		n1 = val;
+	      } else {
+		pass = 0;
+	      }
+	    } else if (s == 'm') {
+	      
+	      /* Generic number */
+	      m1 = val; 
+	    }
+	  }
+	} else if (s == 'a') {
+	  /* Special pattern: coordinate designator */
+	  if (isupper(c) || c == ' ') { a = c; pass = 1;} 
+	} else if (s == '?') {
+	  /* Match any individual character */
+	  pass = 1;
+	} else if (c == s) {
+	  /* Match a specific character */
+	  pass = 1;
+	} else {
+	  /* FAIL */
+	  pass = 0;
+	}
+	if (!pass) break;
+      }
+      
+      /* Must pass to the end of the keyword.  No partial matches allowed */
+      if (pass && (ic >= 8 || inrec[ic] == ' ')) break;
+    }
+
+    /* Transfer the pattern-matched numbers to the output parameters */
+    if (i) { *i = i1; }
+    if (j) { *j = j1; }
+    if (n) { *n = n1; }
+    if (m) { *m = m1; }
+    if (pat_num) { *pat_num = pat; }
+
+    /* ===== Keyword rewriting and output stage */
+    spat = patterns[pat][1];
+
+    /* Return case: no match, or explicit deletion pattern */
+    if (pass == 0 || spat[0] == '\0' || spat[0] == '-') return 0;
+
+    /* A match: we start by copying the input record to the output */
+    strcpy(outrec, inrec);
+
+    /* Return case: return the input record unchanged */
+    if (spat[0] == '+') return 0;
+
+
+    /* Final case: a new output pattern */
+    for (ip=0, ic=0; spat[ip]; ip++, ic++) {
+      s = spat[ip];
+      if (s == 'i') {
+	outrec[ic] = (i1+'0');
+      } else if (s == 'j') {
+	outrec[ic] = (j1+'0');
+      } else if (s == 'n') {
+	if (n1 == -1) { n1 = n_value; }
+	if (n1 > 0) {
+	  n1 += n_offset;
+	  for (fac = 1; (n1/fac) > 0; fac *= 10);
+	  fac /= 10;
+	  while(fac > 0) {
+	    outrec[ic] = ((n1/fac) % 10) + '0';
+	    fac /= 10;
+	    ic ++;
+	  }
+	  ic--;
+	}
+      } else if (s == 'm' && m1 >= 0) {
+	for (fac = 1; (m1/fac) > 0; fac *= 10);
+	fac /= 10;
+	while(fac > 0) {
+	  outrec[ic] = ((m1/fac) % 10) + '0';
+	  fac /= 10;
+	  ic ++;
+	}
+	ic --;
+      } else if (s == 'a') {
+	outrec[ic] = a;
+      } else {
+	outrec[ic] = s;
+      }
+    }
+
+    /* Pad the keyword name with spaces */
+    for ( ; ic<8; ic++) { outrec[ic] = ' '; }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_translate_keywords(
+	   fitsfile *infptr,   /* I - pointer to input HDU */
+	   fitsfile *outfptr,  /* I - pointer to output HDU */
+	   int firstkey,       /* I - first HDU record number to start with */
+	   char *patterns[][2],/* I - pointer to input / output keyword templates */
+	   int npat,           /* I - number of templates passed */
+	   int n_value,        /* I - base 'n' template value of interest */
+	   int n_offset,       /* I - offset to be applied to the 'n' */
+ 	                       /*     value in the output string */
+	   int n_range,        /* I - controls range of 'n' template */
+	                       /*     values of interest (-1,0, or +1) */
+           int *status)        /* IO - error status */
+/*
+     Copy relevant keywords from the table header into the newly
+     created primary array header.  Convert names of keywords where
+     appropriate.  See fits_translate_keyword() for the definitions.
+
+     Translation begins at header record number 'firstkey', and
+     continues to the end of the header.
+
+  This routine was written by Craig Markwardt, GSFC
+*/
+{
+    int nrec, nkeys, nmore;
+    char rec[FLEN_CARD];
+    int i = 0, j = 0, n = 0, m = 0;
+    int pat_num = 0, maxchr, ii;
+    char outrec[FLEN_CARD];
+
+    if (*status > 0)
+        return(*status);
+
+    ffghsp(infptr, &nkeys, &nmore, status);  /* get number of keywords */
+
+    for (nrec = firstkey; nrec <= nkeys; nrec++) {
+      outrec[0] = '\0';
+
+      ffgrec(infptr, nrec, rec, status);
+
+      /* silently overlook any illegal ASCII characters in the value or */
+      /* comment fields of the record. It is usually not appropriate to */
+      /* abort the process because of this minor transgression of the FITS rules. */
+      /* Set the offending character to a blank */
+
+      maxchr = strlen(rec);
+      for (ii = 8; ii < maxchr; ii++)
+      {
+        if (rec[ii] < 32 || rec[ii] > 126)
+          rec[ii] = ' ';
+      }
+      
+      fits_translate_keyword(rec, outrec, patterns, npat, 
+			     n_value, n_offset, n_range, 
+			     &pat_num, &i, &j, &m, &n, status);
+      
+      if (outrec[0]) {
+	ffprec(outfptr, outrec, status); /* copy the keyword */
+	rec[8] = 0; outrec[8] = 0;
+      } else {
+	rec[8] = 0; outrec[8] = 0;
+      }
+    }	
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_copy_pixlist2image(
+	   fitsfile *infptr,   /* I - pointer to input HDU */
+	   fitsfile *outfptr,  /* I - pointer to output HDU */
+	   int firstkey,       /* I - first HDU record number to start with */
+           int naxis,          /* I - number of axes in the image */
+           int *colnum,       /* I - numbers of the columns to be binned  */
+           int *status)        /* IO - error status */
+/*
+     Copy relevant keywords from the pixel list table header into a newly
+     created primary array header.  Convert names of keywords where
+     appropriate.  See fits_translate_pixkeyword() for the definitions.
+
+     Translation begins at header record number 'firstkey', and
+     continues to the end of the header.
+*/
+{
+    int nrec, nkeys, nmore;
+    char rec[FLEN_CARD], outrec[FLEN_CARD];
+    int pat_num = 0, npat;
+    int iret, jret, nret, mret, lret;
+    char *patterns[][2] = {
+
+			   {"TCTYPn",  "CTYPEn"    },
+			   {"TCTYna",  "CTYPEna"   },
+			   {"TCUNIn",  "CUNITn"    },
+			   {"TCUNna",  "CUNITna"   },
+			   {"TCRVLn",  "CRVALn"    },
+			   {"TCRVna",  "CRVALna"   },
+			   {"TCDLTn",  "CDELTn"    },
+			   {"TCDEna",  "CDELTna"   },
+			   {"TCRPXn",  "CRPIXn"    },
+			   {"TCRPna",  "CRPIXna"   },
+			   {"TCROTn",  "CROTAn"    },
+			   {"TPn_ma",  "PCn_ma"    },
+			   {"TPCn_m",  "PCn_ma"    },
+			   {"TCn_ma",  "CDn_ma"    },
+			   {"TCDn_m",  "CDn_ma"    },
+			   {"TVn_la",  "PVn_la"    },
+			   {"TPVn_l",  "PVn_la"    },
+			   {"TSn_la",  "PSn_la"    },
+			   {"TPSn_l",  "PSn_la"    },
+			   {"TWCSna",  "WCSNAMEa"  },
+			   {"TCNAna",  "CNAMEna"   },
+			   {"TCRDna",  "CRDERna"   },
+			   {"TCSYna",  "CSYERna"   },
+			   {"LONPna",  "LONPOLEa"  },
+			   {"LATPna",  "LATPOLEa"  },
+			   {"EQUIna",  "EQUINOXa"  },
+			   {"MJDOBn",  "MJD-OBS"   },
+			   {"MJDAn",   "MJD-AVG"   },
+			   {"DAVGn",   "DATE-AVG"  },
+			   {"RADEna",  "RADESYSa"  },
+			   {"RFRQna",  "RESTFRQa"  },
+			   {"RWAVna",  "RESTWAVa"  },
+			   {"SPECna",  "SPECSYSa"  },
+			   {"SOBSna",  "SSYSOBSa"  },
+			   {"SSRCna",  "SSYSSRCa"  },
+
+                           /* preserve common keywords */
+			   {"LONPOLEa",   "+"       },
+			   {"LATPOLEa",   "+"       },
+			   {"EQUINOXa",   "+"       },
+			   {"EPOCH",      "+"       },
+			   {"MJD-????",   "+"       },
+			   {"DATE????",   "+"       },
+			   {"TIME????",   "+"       },
+			   {"RADESYSa",   "+"       },
+			   {"RADECSYS",   "+"       },
+			   {"TELESCOP",   "+"       },
+			   {"INSTRUME",   "+"       },
+			   {"OBSERVER",   "+"       },
+			   {"OBJECT",     "+"       },
+
+                           /* Delete general table column keywords */
+			   {"XTENSION", "-"       },
+			   {"BITPIX",   "-"       },
+			   {"NAXIS",    "-"       },
+			   {"NAXISi",   "-"       },
+			   {"PCOUNT",   "-"       },
+			   {"GCOUNT",   "-"       },
+			   {"TFIELDS",  "-"       },
+
+			   {"TDIM#",   "-"       },
+			   {"THEAP",   "-"       },
+			   {"EXTNAME", "-"       }, 
+			   {"EXTVER",  "-"       },
+			   {"EXTLEVEL","-"       },
+			   {"CHECKSUM","-"       },
+			   {"DATASUM", "-"       },
+			   {"NAXLEN",  "-"       },
+			   {"AXLEN#",  "-"       },
+			   {"CPREF",  "-"       },
+			   
+                           /* Delete table keywords related to other columns */
+			   {"T????#a", "-"       }, 
+ 			   {"TC??#a",  "-"       },
+ 			   {"T??#_#",  "-"       },
+ 			   {"TWCS#a",  "-"       },
+
+			   {"LONP#a",  "-"       },
+			   {"LATP#a",  "-"       },
+			   {"EQUI#a",  "-"       },
+			   {"MJDOB#",  "-"       },
+			   {"MJDA#",   "-"       },
+			   {"RADE#a",  "-"       },
+			   {"DAVG#",   "-"       },
+
+			   {"iCTYP#",  "-"       },
+			   {"iCTY#a",  "-"       },
+			   {"iCUNI#",  "-"       },
+			   {"iCUN#a",  "-"       },
+			   {"iCRVL#",  "-"       },
+			   {"iCDLT#",  "-"       },
+			   {"iCRPX#",  "-"       },
+			   {"iCTY#a",  "-"       },
+			   {"iCUN#a",  "-"       },
+			   {"iCRV#a",  "-"       },
+			   {"iCDE#a",  "-"       },
+			   {"iCRP#a",  "-"       },
+			   {"ijPC#a",  "-"       },
+			   {"ijCD#a",  "-"       },
+			   {"iV#_#a",  "-"       },
+			   {"iS#_#a",  "-"       },
+			   {"iCRD#a",  "-"       },
+			   {"iCSY#a",  "-"       },
+			   {"iCROT#",  "-"       },
+			   {"WCAX#a",  "-"       },
+			   {"WCSN#a",  "-"       },
+			   {"iCNA#a",  "-"       },
+
+			   {"*",       "+"       }}; /* copy all other keywords */
+
+    if (*status > 0)
+        return(*status);
+
+    npat = sizeof(patterns)/sizeof(patterns[0][0])/2;
+
+    ffghsp(infptr, &nkeys, &nmore, status);  /* get number of keywords */
+
+    for (nrec = firstkey; nrec <= nkeys; nrec++) {
+      outrec[0] = '\0';
+
+      ffgrec(infptr, nrec, rec, status);
+
+      fits_translate_pixkeyword(rec, outrec, patterns, npat, 
+			     naxis, colnum, 
+			     &pat_num, &iret, &jret, &nret, &mret, &lret, status);
+
+      if (outrec[0]) {
+	ffprec(outfptr, outrec, status); /* copy the keyword */
+      } 
+
+      rec[8] = 0; outrec[8] = 0;
+    }	
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_translate_pixkeyword(
+      char *inrec,        /* I - input string */
+      char *outrec,       /* O - output converted string, or */
+                          /*     a null string if input does not  */
+                          /*     match any of the patterns */
+      char *patterns[][2],/* I - pointer to input / output string */
+                          /*     templates */
+      int npat,           /* I - number of templates passed */
+      int naxis,          /* I - number of columns to be binned */
+      int *colnum,       /* I - numbers of the columns to be binned */
+      int *pat_num,       /* O - matched pattern number (0 based) or -1 */
+      int *i,
+      int *j,
+      int *n,
+      int *m,
+      int *l,
+      int *status)        /* IO - error status */
+      
+/* 
+
+Translate a keyword name to a new name, based on a set of patterns.
+The user passes an array of patterns to be matched.  Input pattern
+number i is pattern[i][0], and output pattern number i is
+pattern[i][1].  Keywords are matched against the input patterns.  If a
+match is found then the keyword is re-written according to the output
+pattern.
+
+Order is important.  The first match is accepted.  The fastest match
+will be made when templates with the same first character are grouped
+together.
+
+Several characters have special meanings:
+
+     i,j - single digits, preserved in output template
+     n, m - column number of one or more digits, preserved in output template
+     k - generic number of one or more digits, preserved in output template
+     a - coordinate designator, preserved in output template
+     # - number of one or more digits
+     ? - any character
+     * - only allowed in first character position, to match all
+         keywords; only useful as last pattern in the list
+
+i, j, n, and m are returned by the routine.
+
+For example, the input pattern "iCTYPn" will match "1CTYP5" (if n_value
+is 5); the output pattern "CTYPEi" will be re-written as "CTYPE1".
+Notice that "i" is preserved.
+
+The following output patterns are special
+
+Special output pattern characters:
+
+    "-" - do not copy a keyword that matches the corresponding input pattern
+
+    "+" - copy the input unchanged
+
+The inrec string could be just the 8-char keyword name, or the entire 
+80-char header record.  Characters 9 = 80 in the input string simply get
+appended to the translated keyword name.
+
+If n_range = 0, then only keywords with 'n' equal to n_value will be 
+considered as a pattern match.  If n_range = +1, then all values of 
+'n' greater than or equal to n_value will be a match, and if -1, 
+then values of 'n' less than or equal to n_value will match.
+
+*/
+
+{
+    int i1 = 0, j1 = 0, val;
+    int fac, nval, mval, lval;
+    char a = ' ';
+    char oldp;
+    char c, s;
+    int ip, ic, pat, pass = 0, firstfail;
+    char *spat;
+
+    if (*status > 0)
+        return(*status);
+
+    if ((inrec == 0) || (outrec == 0)) 
+      return (*status = NULL_INPUT_PTR);
+
+    *outrec = '\0';
+    if (*inrec == '\0') return 0;
+
+    oldp = '\0';
+    firstfail = 0;
+
+    /* ===== Pattern match stage */
+    for (pat=0; pat < npat; pat++) {
+
+      spat = patterns[pat][0];
+      
+      i1 = 0; j1 = 0;   a = ' ';  /* Initialize the place-holders */
+      pass = 0;
+      
+      /* Pass the wildcard pattern */
+      if (spat[0] == '*') { 
+	pass = 1;
+	break;
+      }
+      
+      /* Optimization: if we have seen this initial pattern character before,
+	 then it must have failed, and we can skip the pattern */
+      if (firstfail && spat[0] == oldp) continue;
+      oldp = spat[0];
+
+      /* 
+	 ip = index of pattern character being matched
+	 ic = index of keyname character being matched
+	 firstfail = 1 if we fail on the first characteor (0=not)
+      */
+      
+      for (ip=0, ic=0, firstfail=1;
+	   (spat[ip]) && (ic < 8);
+	   ip++, ic++, firstfail=0) {
+	c = inrec[ic];
+	s = spat[ip];
+
+	if (s == 'i') {
+	  /* Special pattern: 'i' placeholder */
+	  if (isdigit(c)) { i1 = c - '0'; pass = 1;}
+	} else if (s == 'j') {
+	  /* Special pattern: 'j' placeholder */
+	  if (isdigit(c)) { j1 = c - '0'; pass = 1;}
+	} else if ((s == 'n')||(s == 'm')||(s == 'l')||(s == '#')) {
+	  /* Special patterns: multi-digit number */
+          val = 0;
+	  pass = 0;
+	  if (isdigit(c)) {
+	    pass = 1;  /* NOTE, could fail below */
+	    
+	    /* Parse decimal number */
+	    while (ic<8 && isdigit(c)) { 
+	      val = val*10 + (c - '0');
+	      ic++; c = inrec[ic];
+	    }
+	    ic--; c = inrec[ic];
+
+	    if (s == 'n' || s == 'm') { 
+	      
+	      /* Is it a column number? */
+	      if ( val >= 1 && val <= 999) {
+	         
+		 if (val == colnum[0])
+		     val = 1; 
+		 else if (val == colnum[1]) 
+		     val = 2; 
+		 else if (val == colnum[2]) 
+		     val = 3; 
+		 else if (val == colnum[3]) 
+		     val = 4; 
+		 else {
+		     pass = 0;
+		     val = 0; 
+		 }
+
+	         if (s == 'n')
+		    nval = val;
+		 else
+		    mval = val;
+ 
+              } else {
+		  pass = 0;
+              }
+	    } else if (s == 'l') {
+	      /* Generic number */
+	      lval = val; 
+	    }
+	  }
+	} else if (s == 'a') {
+	  /* Special pattern: coordinate designator */
+	  if (isupper(c) || c == ' ') { a = c; pass = 1;} 
+	} else if (s == '?') {
+	  /* Match any individual character */
+	  pass = 1;
+	} else if (c == s) {
+	  /* Match a specific character */
+	  pass = 1;
+	} else {
+	  /* FAIL */
+	  pass = 0;
+	}
+	
+	if (!pass) break;
+      }
+      
+
+      /* Must pass to the end of the keyword.  No partial matches allowed */
+      if (pass && (ic >= 8 || inrec[ic] == ' ')) break;
+    }
+
+
+    /* Transfer the pattern-matched numbers to the output parameters */
+    if (i) { *i = i1; }
+    if (j) { *j = j1; }
+    if (n) { *n = nval; }
+    if (m) { *m = mval; }
+    if (l) { *l = lval; }
+    if (pat_num) { *pat_num = pat; }
+
+    /* ===== Keyword rewriting and output stage */
+    spat = patterns[pat][1];
+
+    /* Return case: no match, or explicit deletion pattern */
+    if (pass == 0 || spat[0] == '\0' || spat[0] == '-') return 0;
+
+    /* A match: we start by copying the input record to the output */
+    strcpy(outrec, inrec);
+
+    /* Return case: return the input record unchanged */
+    if (spat[0] == '+') return 0;
+
+    /* Final case: a new output pattern */
+    for (ip=0, ic=0; spat[ip]; ip++, ic++) {
+      s = spat[ip];
+      if (s == 'i') {
+	outrec[ic] = (i1+'0');
+      } else if (s == 'j') {
+	outrec[ic] = (j1+'0');
+      } else if (s == 'n' && nval > 0) {
+	  for (fac = 1; (nval/fac) > 0; fac *= 10);
+	  fac /= 10;
+	  while(fac > 0) {
+	    outrec[ic] = ((nval/fac) % 10) + '0';
+	    fac /= 10;
+	    ic ++;
+	  }
+	  ic--;
+      } else if (s == 'm' && mval > 0) {
+	  for (fac = 1; (mval/fac) > 0; fac *= 10);
+	  fac /= 10;
+	  while(fac > 0) {
+	    outrec[ic] = ((mval/fac) % 10) + '0';
+	    fac /= 10;
+	    ic ++;
+	  }
+	  ic--;
+      } else if (s == 'l' && lval >= 0) {
+	for (fac = 1; (lval/fac) > 0; fac *= 10);
+	fac /= 10;
+	while(fac > 0) {
+	  outrec[ic] = ((lval/fac) % 10) + '0';
+	  fac /= 10;
+	  ic ++;
+	}
+	ic --;
+      } else if (s == 'a') {
+	outrec[ic] = a;
+      } else {
+	outrec[ic] = s;
+      }
+    }
+
+    /* Pad the keyword name with spaces */
+    for ( ; ic<8; ic++) { outrec[ic] = ' '; }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffasfm(char *tform,    /* I - format code from the TFORMn keyword */
+           int *dtcode,    /* O - numerical datatype code */
+           long *twidth,   /* O - width of the field, in chars */
+           int *decimals,  /* O - number of decimal places (F, E, D format) */
+           int *status)    /* IO - error status      */
+{
+/*
+  parse the ASCII table TFORM column format to determine the data
+  type, the field width, and number of decimal places (if relevant)
+*/
+    int ii, datacode;
+    long longval, width;
+    float fwidth;
+    char *form, temp[FLEN_VALUE], message[FLEN_ERRMSG];
+
+    if (*status > 0)
+        return(*status);
+
+    if (dtcode)
+        *dtcode = 0;
+
+    if (twidth)
+        *twidth = 0;
+
+    if (decimals)
+        *decimals = 0;
+
+    ii = 0;
+    while (tform[ii] != 0 && tform[ii] == ' ') /* find first non-blank char */
+         ii++;
+
+    strcpy(temp, &tform[ii]); /* copy format string */
+    ffupch(temp);     /* make sure it is in upper case */
+    form = temp;      /* point to start of format string */
+
+
+    if (form[0] == 0)
+    {
+        ffpmsg("Error: ASCII table TFORM code is blank");
+        return(*status = BAD_TFORM);
+    }
+
+    /*-----------------------------------------------*/
+    /*       determine default datatype code         */
+    /*-----------------------------------------------*/
+    if (form[0] == 'A')
+        datacode = TSTRING;
+    else if (form[0] == 'I')
+        datacode = TLONG;
+    else if (form[0] == 'F')
+        datacode = TFLOAT;
+    else if (form[0] == 'E')
+        datacode = TFLOAT;
+    else if (form[0] == 'D')
+        datacode = TDOUBLE;
+    else
+    {
+        sprintf(message,
+                "Illegal ASCII table TFORMn datatype: \'%s\'", tform);
+        ffpmsg(message);
+        return(*status = BAD_TFORM_DTYPE);
+    }
+
+    if (dtcode)
+       *dtcode = datacode;
+
+    form++;  /* point to the start of field width */
+
+    if (datacode == TSTRING || datacode == TLONG)
+    { 
+        /*-----------------------------------------------*/
+        /*              A or I data formats:             */
+        /*-----------------------------------------------*/
+
+        if (ffc2ii(form, &width, status) <= 0)  /* read the width field */
+        {
+            if (width <= 0)
+            {
+                width = 0;
+                *status = BAD_TFORM;
+            }
+            else
+            {                
+                /* set to shorter precision if I4 or less */
+                if (width <= 4 && datacode == TLONG)
+                    datacode = TSHORT;
+            }
+        }
+    }
+    else
+    {  
+        /*-----------------------------------------------*/
+        /*              F, E or D data formats:          */
+        /*-----------------------------------------------*/
+
+        if (ffc2rr(form, &fwidth, status) <= 0) /* read ww.dd width field */
+        {
+           if (fwidth <= 0.)
+            *status = BAD_TFORM;
+          else
+          {
+            width = (long) fwidth;  /* convert from float to long */
+
+            if (width > 7 && *temp == 'F')
+                datacode = TDOUBLE;  /* type double if >7 digits */
+
+            if (width < 10)
+                form = form + 1; /* skip 1 digit  */
+            else
+                form = form + 2; /* skip 2 digits */
+
+            if (form[0] == '.') /* should be a decimal point here */
+            {
+                form++;  /*  point to start of decimals field */
+
+                if (ffc2ii(form, &longval, status) <= 0) /* read decimals */
+                {
+                    if (decimals)
+                        *decimals = longval;  /* long to short convertion */
+
+                    if (longval >= width)  /* width < no. of decimals */
+                        *status = BAD_TFORM; 
+
+                    if (longval > 6 && *temp == 'E')
+                        datacode = TDOUBLE;  /* type double if >6 digits */
+                }
+            }
+
+          }
+        }
+    }
+    if (*status > 0)
+    {
+        *status = BAD_TFORM;
+        sprintf(message,"Illegal ASCII table TFORMn code: \'%s\'", tform);
+        ffpmsg(message);
+    }
+
+    if (dtcode)
+       *dtcode = datacode;
+
+    if (twidth)
+       *twidth = width;
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffbnfm(char *tform,     /* I - format code from the TFORMn keyword */
+           int *dtcode,   /* O - numerical datatype code */
+           long *trepeat,    /* O - repeat count of the field  */
+           long *twidth,     /* O - width of the field, in chars */
+           int *status)     /* IO - error status      */
+{
+/*
+  parse the binary table TFORM column format to determine the data
+  type, repeat count, and the field width (if it is an ASCII (A) field)
+*/
+    size_t ii, nchar;
+    int datacode, variable, iread;
+    long width, repeat;
+    char *form, temp[FLEN_VALUE], message[FLEN_ERRMSG];
+
+    if (*status > 0)
+        return(*status);
+
+    if (dtcode)
+        *dtcode = 0;
+
+    if (trepeat)
+        *trepeat = 0;
+
+    if (twidth)
+        *twidth = 0;
+
+    nchar = strlen(tform);
+
+    for (ii = 0; ii < nchar; ii++)
+    {
+        if (tform[ii] != ' ')     /* find first non-space char */
+            break;
+    }
+
+    if (ii == nchar)
+    {
+        ffpmsg("Error: binary table TFORM code is blank (ffbnfm).");
+        return(*status = BAD_TFORM);
+    }
+
+    strcpy(temp, &tform[ii]); /* copy format string */
+    ffupch(temp);     /* make sure it is in upper case */
+    form = temp;      /* point to start of format string */
+
+    /*-----------------------------------------------*/
+    /*       get the repeat count                    */
+    /*-----------------------------------------------*/
+
+    ii = 0;
+    while(isdigit((int) form[ii]))
+        ii++;   /* look for leading digits in the field */
+
+    if (ii == 0)
+        repeat = 1;  /* no explicit repeat count */
+    else
+        sscanf(form,"%ld", &repeat);  /* read repeat count */
+
+    /*-----------------------------------------------*/
+    /*             determine datatype code           */
+    /*-----------------------------------------------*/
+
+    form = form + ii;  /* skip over the repeat field */
+
+    if (form[0] == 'P' || form[0] == 'Q')
+    {
+        variable = 1;  /* this is a variable length column */
+/*        repeat = 1;  */ /* disregard any other repeat value */
+        form++;        /* move to the next data type code char */
+    }
+    else
+        variable = 0;
+
+    if (form[0] == 'U')  /* internal code to signify unsigned integer */
+    { 
+        datacode = TUSHORT;
+        width = 2;
+    }
+    else if (form[0] == 'I')
+    {
+        datacode = TSHORT;
+        width = 2;
+    }
+    else if (form[0] == 'V') /* internal code to signify unsigned integer */
+    {
+        datacode = TULONG;
+        width = 4;
+    }
+    else if (form[0] == 'J')
+    {
+        datacode = TLONG;
+        width = 4;
+    }
+    else if (form[0] == 'K')
+    {
+        datacode = TLONGLONG;
+        width = 8;
+    }
+    else if (form[0] == 'E')
+    {
+        datacode = TFLOAT;
+        width = 4;
+    }
+    else if (form[0] == 'D')
+    {
+        datacode = TDOUBLE;
+        width = 8;
+    }
+    else if (form[0] == 'A')
+    {
+        datacode = TSTRING;
+
+        /*
+          the following code is used to support the non-standard
+          datatype of the form rAw where r = total width of the field
+          and w = width of fixed-length substrings within the field.
+        */
+        iread = 0;
+        if (form[1] != 0)
+        {
+            if (form[1] == '(' )  /* skip parenthesis around */
+                form++;          /* variable length column width */
+
+            iread = sscanf(&form[1],"%ld", &width);
+        }
+
+        if (iread != 1 || (!variable && (width > repeat)) )
+            width = repeat;
+  
+    }
+    else if (form[0] == 'L')
+    {
+        datacode = TLOGICAL;
+        width = 1;
+    }
+    else if (form[0] == 'X')
+    {
+        datacode = TBIT;
+        width = 1;
+    }
+    else if (form[0] == 'B')
+    {
+        datacode = TBYTE;
+        width = 1;
+    }
+    else if (form[0] == 'S') /* internal code to signify signed byte */
+    {
+        datacode = TSBYTE;
+        width = 1;
+    }
+    else if (form[0] == 'C')
+    {
+        datacode = TCOMPLEX;
+        width = 8;
+    }
+    else if (form[0] == 'M')
+    {
+        datacode = TDBLCOMPLEX;
+        width = 16;
+    }
+    else
+    {
+        sprintf(message,
+        "Illegal binary table TFORMn datatype: \'%s\' ", tform);
+        ffpmsg(message);
+        return(*status = BAD_TFORM_DTYPE);
+    }
+
+    if (variable)
+        datacode = datacode * (-1); /* flag variable cols w/ neg type code */
+
+    if (dtcode)
+       *dtcode = datacode;
+
+    if (trepeat)
+       *trepeat = repeat;
+
+    if (twidth)
+       *twidth = width;
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffbnfmll(char *tform,     /* I - format code from the TFORMn keyword */
+           int *dtcode,   /* O - numerical datatype code */
+           LONGLONG *trepeat,    /* O - repeat count of the field  */
+           long *twidth,     /* O - width of the field, in chars */
+           int *status)     /* IO - error status      */
+{
+/*
+  parse the binary table TFORM column format to determine the data
+  type, repeat count, and the field width (if it is an ASCII (A) field)
+*/
+    size_t ii, nchar;
+    int datacode, variable, iread;
+    long width;
+    LONGLONG repeat;
+    char *form, temp[FLEN_VALUE], message[FLEN_ERRMSG];
+    double drepeat;
+
+    if (*status > 0)
+        return(*status);
+
+    if (dtcode)
+        *dtcode = 0;
+
+    if (trepeat)
+        *trepeat = 0;
+
+    if (twidth)
+        *twidth = 0;
+
+    nchar = strlen(tform);
+
+    for (ii = 0; ii < nchar; ii++)
+    {
+        if (tform[ii] != ' ')     /* find first non-space char */
+            break;
+    }
+
+    if (ii == nchar)
+    {
+        ffpmsg("Error: binary table TFORM code is blank (ffbnfmll).");
+        return(*status = BAD_TFORM);
+    }
+
+    strcpy(temp, &tform[ii]); /* copy format string */
+    ffupch(temp);     /* make sure it is in upper case */
+    form = temp;      /* point to start of format string */
+
+    /*-----------------------------------------------*/
+    /*       get the repeat count                    */
+    /*-----------------------------------------------*/
+
+    ii = 0;
+    while(isdigit((int) form[ii]))
+        ii++;   /* look for leading digits in the field */
+
+    if (ii == 0)
+        repeat = 1;  /* no explicit repeat count */
+    else {
+       /* read repeat count */
+
+        /* print as double, because the string-to-64-bit int conversion */
+        /* character is platform dependent (%lld, %ld, %I64d)           */
+
+        sscanf(form,"%lf", &drepeat);
+        repeat = (LONGLONG) (drepeat + 0.1);
+    }
+    /*-----------------------------------------------*/
+    /*             determine datatype code           */
+    /*-----------------------------------------------*/
+
+    form = form + ii;  /* skip over the repeat field */
+
+    if (form[0] == 'P' || form[0] == 'Q')
+    {
+        variable = 1;  /* this is a variable length column */
+/*        repeat = 1;  */  /* disregard any other repeat value */
+        form++;        /* move to the next data type code char */
+    }
+    else
+        variable = 0;
+
+    if (form[0] == 'U')  /* internal code to signify unsigned integer */
+    { 
+        datacode = TUSHORT;
+        width = 2;
+    }
+    else if (form[0] == 'I')
+    {
+        datacode = TSHORT;
+        width = 2;
+    }
+    else if (form[0] == 'V') /* internal code to signify unsigned integer */
+    {
+        datacode = TULONG;
+        width = 4;
+    }
+    else if (form[0] == 'J')
+    {
+        datacode = TLONG;
+        width = 4;
+    }
+    else if (form[0] == 'K')
+    {
+        datacode = TLONGLONG;
+        width = 8;
+    }
+    else if (form[0] == 'E')
+    {
+        datacode = TFLOAT;
+        width = 4;
+    }
+    else if (form[0] == 'D')
+    {
+        datacode = TDOUBLE;
+        width = 8;
+    }
+    else if (form[0] == 'A')
+    {
+        datacode = TSTRING;
+
+        /*
+          the following code is used to support the non-standard
+          datatype of the form rAw where r = total width of the field
+          and w = width of fixed-length substrings within the field.
+        */
+        iread = 0;
+        if (form[1] != 0)
+        {
+            if (form[1] == '(' )  /* skip parenthesis around */
+                form++;          /* variable length column width */
+
+            iread = sscanf(&form[1],"%ld", &width);
+        }
+
+        if (iread != 1 || (!variable && (width > repeat)) )
+            width = (long) repeat;
+  
+    }
+    else if (form[0] == 'L')
+    {
+        datacode = TLOGICAL;
+        width = 1;
+    }
+    else if (form[0] == 'X')
+    {
+        datacode = TBIT;
+        width = 1;
+    }
+    else if (form[0] == 'B')
+    {
+        datacode = TBYTE;
+        width = 1;
+    }
+    else if (form[0] == 'S') /* internal code to signify signed byte */
+    {
+        datacode = TSBYTE;
+        width = 1;
+    }
+    else if (form[0] == 'C')
+    {
+        datacode = TCOMPLEX;
+        width = 8;
+    }
+    else if (form[0] == 'M')
+    {
+        datacode = TDBLCOMPLEX;
+        width = 16;
+    }
+    else
+    {
+        sprintf(message,
+        "Illegal binary table TFORMn datatype: \'%s\' ", tform);
+        ffpmsg(message);
+        return(*status = BAD_TFORM_DTYPE);
+    }
+
+    if (variable)
+        datacode = datacode * (-1); /* flag variable cols w/ neg type code */
+
+    if (dtcode)
+       *dtcode = datacode;
+
+    if (trepeat)
+       *trepeat = repeat;
+
+    if (twidth)
+       *twidth = width;
+
+    return(*status);
+}
+
+/*--------------------------------------------------------------------------*/
+void ffcfmt(char *tform,    /* value of an ASCII table TFORMn keyword */
+            char *cform)    /* equivalent format code in C language syntax */
+/*
+  convert the FITS format string for an ASCII Table extension column into the
+  equivalent C format string that can be used in a printf statement, after
+  the values have been read as a double.
+*/
+{
+    int ii;
+
+    cform[0] = '\0';
+    ii = 0;
+    while (tform[ii] != 0 && tform[ii] == ' ') /* find first non-blank char */
+         ii++;
+
+    if (tform[ii] == 0)
+        return;    /* input format string was blank */
+
+    cform[0] = '%';  /* start the format string */
+
+    strcpy(&cform[1], &tform[ii + 1]); /* append the width and decimal code */
+
+
+    if (tform[ii] == 'A')
+        strcat(cform, "s");
+    else if (tform[ii] == 'I')
+        strcat(cform, ".0f");  /*  0 precision to suppress decimal point */
+    if (tform[ii] == 'F')
+        strcat(cform, "f");
+    if (tform[ii] == 'E')
+        strcat(cform, "E");
+    if (tform[ii] == 'D')
+        strcat(cform, "E");
+
+    return;
+}
+/*--------------------------------------------------------------------------*/
+void ffcdsp(char *tform,    /* value of an ASCII table TFORMn keyword */
+            char *cform)    /* equivalent format code in C language syntax */
+/*
+  convert the FITS TDISPn display format into the equivalent C format
+  suitable for use in a printf statement.
+*/
+{
+    int ii;
+
+    cform[0] = '\0';
+    ii = 0;
+    while (tform[ii] != 0 && tform[ii] == ' ') /* find first non-blank char */
+         ii++;
+
+    if (tform[ii] == 0)
+    {
+        cform[0] = '\0';
+        return;    /* input format string was blank */
+    }
+
+    if (strchr(tform+ii, '%'))  /* is there a % character in the string?? */
+    {
+        cform[0] = '\0';
+        return;    /* illegal TFORM string (possibly even harmful) */
+    }
+
+    cform[0] = '%';  /* start the format string */
+
+    strcpy(&cform[1], &tform[ii + 1]); /* append the width and decimal code */
+
+    if      (tform[ii] == 'A' || tform[ii] == 'a')
+        strcat(cform, "s");
+    else if (tform[ii] == 'I' || tform[ii] == 'i')
+        strcat(cform, "d");
+    else if (tform[ii] == 'O' || tform[ii] == 'o')
+        strcat(cform, "o");
+    else if (tform[ii] == 'Z' || tform[ii] == 'z')
+        strcat(cform, "X");
+    else if (tform[ii] == 'F' || tform[ii] == 'f')
+        strcat(cform, "f");
+    else if (tform[ii] == 'E' || tform[ii] == 'e')
+        strcat(cform, "E");
+    else if (tform[ii] == 'D' || tform[ii] == 'd')
+        strcat(cform, "E");
+    else if (tform[ii] == 'G' || tform[ii] == 'g')
+        strcat(cform, "G");
+    else
+        cform[0] = '\0';  /* unrecognized tform code */
+
+    return;
+}
+/*--------------------------------------------------------------------------*/
+int ffgcno( fitsfile *fptr,  /* I - FITS file pionter                       */
+            int  casesen,    /* I - case sensitive string comparison? 0=no  */
+            char *templt,    /* I - input name of column (w/wildcards)      */
+            int  *colnum,    /* O - number of the named column; 1=first col */
+            int  *status)    /* IO - error status                           */
+/*
+  Determine the column number corresponding to an input column name.
+  The first column of the table = column 1;  
+  This supports the * and ? wild cards in the input template.
+*/
+{
+    char colname[FLEN_VALUE];  /*  temporary string to hold column name  */
+
+    ffgcnn(fptr, casesen, templt, colname, colnum, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcnn( fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  casesen,    /* I - case sensitive string comparison? 0=no  */
+            char *templt,    /* I - input name of column (w/wildcards)      */
+            char *colname,   /* O - full column name up to 68 + 1 chars long*/
+            int  *colnum,    /* O - number of the named column; 1=first col */
+            int  *status)    /* IO - error status                           */
+/*
+  Return the full column name and column number of the next column whose
+  TTYPEn keyword value matches the input template string.
+  The template may contain the * and ? wildcards.  Status = 237 is
+  returned if the match is not unique.  If so, one may call this routine
+  again with input status=237  to get the next match.  A status value of
+  219 is returned when there are no more matching columns.
+*/
+{
+    char errmsg[FLEN_ERRMSG];
+    static int startcol;
+    int tstatus, ii, founde, foundw, match, exact, unique;
+    long ivalue;
+    tcolumn *colptr;
+
+    if (*status <= 0)
+    {
+        startcol = 0;   /* start search with first column */
+        tstatus = 0;
+    }
+    else if (*status == COL_NOT_UNIQUE) /* start search from previous spot */
+    {
+        tstatus = COL_NOT_UNIQUE;
+        *status = 0;
+    }
+    else
+        return(*status);  /* bad input status value */
+
+    colname[0] = 0;    /* initialize null return */
+    *colnum = 0;
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+        if ( ffrdef(fptr, status) > 0)   /* rescan header to get col struct */
+            return(*status);
+
+    colptr = (fptr->Fptr)->tableptr;   /* pointer to first column */
+    colptr += (startcol);      /* offset to starting column */
+
+    founde = FALSE;   /* initialize 'found exact match' flag */
+    foundw = FALSE;   /* initialize 'found wildcard match' flag */
+    unique = FALSE;
+
+    for (ii = startcol; ii < (fptr->Fptr)->tfield; ii++, colptr++)
+    {
+        ffcmps(templt, colptr->ttype, casesen, &match, &exact);
+        if (match)
+        {
+            if (founde && exact)
+            {
+                /* warning: this is the second exact match we've found     */
+                /*reset pointer to first match so next search starts there */
+               startcol = *colnum;
+               return(*status = COL_NOT_UNIQUE);
+            }
+            else if (founde)   /* a wildcard match */
+            {
+                /* already found exact match so ignore this non-exact match */
+            }
+            else if (exact)
+            {
+                /* this is the first exact match we have found, so save it. */
+                strcpy(colname, colptr->ttype);
+                *colnum = ii + 1;
+                founde = TRUE;
+            }
+            else if (foundw)
+            {
+                /* we have already found a wild card match, so not unique */
+                /* continue searching for other matches                   */
+                unique = FALSE;
+            }
+            else
+            {
+               /* this is the first wild card match we've found. save it */
+               strcpy(colname, colptr->ttype);
+               *colnum = ii + 1;
+               startcol = *colnum;
+               foundw = TRUE;
+               unique = TRUE;
+            }
+        }
+    }
+
+    /* OK, we've checked all the names now see if we got any matches */
+    if (founde)
+    {
+        if (tstatus == COL_NOT_UNIQUE)  /* we did find 1 exact match but */
+            *status = COL_NOT_UNIQUE;   /* there was a previous match too */
+    }
+    else if (foundw)
+    {
+        /* found one or more wildcard matches; report error if not unique */
+       if (!unique || tstatus == COL_NOT_UNIQUE)
+           *status = COL_NOT_UNIQUE;
+    }
+    else
+    {
+        /* didn't find a match; check if template is a positive integer */
+        ffc2ii(templt, &ivalue, &tstatus);
+        if (tstatus ==  0 && ivalue <= (fptr->Fptr)->tfield && ivalue > 0)
+        {
+            *colnum = ivalue;
+
+            colptr = (fptr->Fptr)->tableptr;   /* pointer to first column */
+            colptr += (ivalue - 1);    /* offset to correct column */
+            strcpy(colname, colptr->ttype);
+        }
+        else
+        {
+            *status = COL_NOT_FOUND;
+            if (tstatus != COL_NOT_UNIQUE)
+            {
+              sprintf(errmsg, "ffgcnn could not find column: %.45s", templt);
+              ffpmsg(errmsg);
+            }
+        }
+    }
+    
+    startcol = *colnum;  /* save pointer for next time */
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+void ffcmps(char *templt,   /* I - input template (may have wildcards)      */
+            char *colname,  /* I - full column name up to 68 + 1 chars long */
+            int  casesen,   /* I - case sensitive string comparison? 1=yes  */
+            int  *match,    /* O - do template and colname match? 1=yes     */
+            int  *exact)    /* O - do strings exactly match, or wildcards   */
+/*
+  compare the template to the string and test if they match.
+  The strings are limited to 68 characters or less (the max. length
+  of a FITS string keyword value.  This routine reports whether
+  the two strings match and whether the match is exact or
+  involves wildcards.
+
+  This algorithm is very similar to the way unix filename wildcards
+  work except that this first treats a wild card as a literal character
+  when looking for a match.  If there is no literal match, then
+  it interpretes it as a wild card.  So the template 'AB*DE'
+  is considered to be an exact rather than a wild card match to
+  the string 'AB*DE'.  The '#' wild card in the template string will 
+  match any consecutive string of decimal digits in the colname.
+  
+*/
+{
+    int ii, found, t1, s1, wildsearch = 0, tsave = 0, ssave = 0;
+    char temp[FLEN_VALUE], col[FLEN_VALUE];
+
+    *match = FALSE;
+    *exact = TRUE;
+
+    strncpy(temp, templt, FLEN_VALUE); /* copy strings to work area */
+    strncpy(col, colname, FLEN_VALUE);
+    temp[FLEN_VALUE - 1] = '\0';  /* make sure strings are terminated */
+    col[FLEN_VALUE - 1]  = '\0';
+
+    /* truncate trailing non-significant blanks */
+    for (ii = strlen(temp) - 1; ii >= 0 && temp[ii] == ' '; ii--)
+        temp[ii] = '\0';
+
+    for (ii = strlen(col) - 1; ii >= 0 && col[ii] == ' '; ii--)
+        col[ii] = '\0';
+       
+    if (!casesen)
+    {             /* convert both strings to uppercase before comparison */
+        ffupch(temp);
+        ffupch(col);
+    }
+
+    if (!FSTRCMP(temp, col) )
+    {
+        *match = TRUE;     /* strings exactly match */
+        return;
+    }
+
+    *exact = FALSE;    /* strings don't exactly match */
+
+    t1 = 0;   /* start comparison with 1st char of each string */
+    s1 = 0;
+
+    while(1)  /* compare corresponding chars in each string */
+    {
+      if (temp[t1] == '\0' && col[s1] == '\0')
+      { 
+         /* completely scanned both strings so they match */
+         *match = TRUE;
+         return;
+      }
+      else if (temp[t1] == '\0')
+      { 
+        if (wildsearch)
+        {
+            /* 
+               the previous wildcard search may have been going down
+               a blind alley.  Backtrack, and resume the wildcard
+               search with the next character in the string.
+            */
+            t1 = tsave;
+            s1 = ssave + 1;
+        }
+        else
+        {
+            /* reached end of template string so they don't match */
+            return;
+        }
+      }
+      else if (col[s1] == '\0')
+      { 
+         /* reached end of other string; they match if the next */
+         /* character in the template string is a '*' wild card */
+
+        if (temp[t1] == '*' && temp[t1 + 1] == '\0')
+        {
+           *match = TRUE;
+        }
+
+        return;
+      }
+
+      if (temp[t1] == col[s1] || (temp[t1] == '?') )
+      {
+        s1++;  /* corresponding chars in the 2 strings match */
+        t1++;  /* increment both pointers and loop back again */
+      }
+      else if (temp[t1] == '#' && isdigit((int) col[s1]) )
+      {
+        s1++;  /* corresponding chars in the 2 strings match */
+        t1++;  /* increment both pointers */
+
+        /* find the end of the string of digits */
+        while (isdigit((int) col[s1]) ) 
+            s1++;        
+      }
+      else if (temp[t1] == '*')
+      {
+
+        /* save current string locations, in case we need to restart */
+        wildsearch = 1;
+        tsave = t1;
+        ssave = s1;
+
+        /* get next char from template and look for it in the col name */
+        t1++;
+        if (temp[t1] == '\0' || temp[t1] == ' ')
+        {
+          /* reached end of template so strings match */
+          *match = TRUE;
+          return;
+        }
+
+        found = FALSE;
+        while (col[s1] && !found)
+        {
+          if (temp[t1] == col[s1])
+          {
+            t1++;  /* found matching characters; incre both pointers */
+            s1++;  /* and loop back to compare next chars */
+            found = TRUE;
+          }
+          else
+            s1++;  /* increment the column name pointer and try again */
+        }
+
+        if (!found)
+        {
+          return;  /* hit end of column name and failed to find a match */
+        }
+      }
+      else
+      {
+        if (wildsearch)
+        {
+            /* 
+               the previous wildcard search may have been going down
+               a blind alley.  Backtrack, and resume the wildcard
+               search with the next character in the string.
+            */
+            t1 = tsave;
+            s1 = ssave + 1;
+        }
+        else
+        {
+          return;   /* strings don't match */
+        }
+      }
+    }
+}
+/*--------------------------------------------------------------------------*/
+int ffgtcl( fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  colnum,     /* I - column number                           */
+            int *typecode,   /* O - datatype code (21 = short, etc)         */
+            long *repeat,    /* O - repeat count of field                   */
+            long *width,     /* O - if ASCII, width of field or unit string */
+            int  *status)    /* IO - error status                           */
+/*
+  Get Type of table column. 
+  Returns the datatype code of the column, as well as the vector
+  repeat count and (if it is an ASCII character column) the
+  width of the field or a unit string within the field.  This supports the
+  TFORMn = 'rAw' syntax for specifying arrays of substrings, so
+  if TFORMn = '60A12' then repeat = 60 and width = 12.
+*/
+{
+    LONGLONG trepeat, twidth;
+    
+    ffgtclll(fptr, colnum, typecode, &trepeat, &twidth, status);
+
+    if (*status > 0)
+        return(*status);
+	
+    if (repeat)
+        *repeat= (long) trepeat;
+      
+    if (width)
+        *width = (long) twidth;
+    
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgtclll( fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  colnum,       /* I - column number                           */
+            int *typecode,   /* O - datatype code (21 = short, etc)         */
+            LONGLONG *repeat, /* O - repeat count of field                   */
+            LONGLONG *width, /* O - if ASCII, width of field or unit string */
+            int  *status)    /* IO - error status                           */
+/*
+  Get Type of table column. 
+  Returns the datatype code of the column, as well as the vector
+  repeat count and (if it is an ASCII character column) the
+  width of the field or a unit string within the field.  This supports the
+  TFORMn = 'rAw' syntax for specifying arrays of substrings, so
+  if TFORMn = '60A12' then repeat = 60 and width = 12.
+*/
+{
+    tcolumn *colptr;
+    int hdutype, decims;
+    long tmpwidth;
+
+    if (*status > 0)
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+        if ( ffrdef(fptr, status) > 0)               /* rescan header */
+            return(*status);
+
+    if (colnum < 1 || colnum > (fptr->Fptr)->tfield)
+        return(*status = BAD_COL_NUM);
+
+    colptr = (fptr->Fptr)->tableptr;   /* pointer to first column */
+    colptr += (colnum - 1);    /* offset to correct column */
+
+    if (ffghdt(fptr, &hdutype, status) > 0)
+        return(*status);
+
+    if (hdutype == ASCII_TBL)
+    {
+       ffasfm(colptr->tform, typecode, &tmpwidth, &decims, status);
+       *width = tmpwidth;
+       
+      if (repeat)
+           *repeat = 1;
+    }
+    else
+    {
+      if (typecode)
+          *typecode = colptr->tdatatype;
+
+      if (width)
+          *width = colptr->twidth;
+
+      if (repeat)
+          *repeat = colptr->trepeat;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffeqty( fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  colnum,     /* I - column number                           */
+            int *typecode,   /* O - datatype code (21 = short, etc)         */
+            long *repeat,    /* O - repeat count of field                   */
+            long *width,     /* O - if ASCII, width of field or unit string */
+            int  *status)    /* IO - error status                           */
+/*
+  Get the 'equivalent' table column type. 
+
+  This routine is similar to the ffgtcl routine (which returns the physical
+  datatype of the column, as stored in the FITS file) except that if the
+  TSCALn and TZEROn keywords are defined for the column, then it returns
+  the 'equivalent' datatype.  Thus, if the column is defined as '1I'  (short
+  integer) this routine may return the type as 'TUSHORT' or as 'TFLOAT'
+  depending on the TSCALn and TZEROn values.
+  
+  Returns the datatype code of the column, as well as the vector
+  repeat count and (if it is an ASCII character column) the
+  width of the field or a unit string within the field.  This supports the
+  TFORMn = 'rAw' syntax for specifying arrays of substrings, so
+  if TFORMn = '60A12' then repeat = 60 and width = 12.
+*/
+{
+    LONGLONG trepeat, twidth;
+    
+    ffeqtyll(fptr, colnum, typecode, &trepeat, &twidth, status);
+
+    if (repeat)
+        *repeat= (long) trepeat;
+
+    if (width)
+        *width = (long) twidth;
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffeqtyll( fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  colnum,     /* I - column number                           */
+            int *typecode,   /* O - datatype code (21 = short, etc)         */
+            LONGLONG *repeat,    /* O - repeat count of field                   */
+            LONGLONG *width,     /* O - if ASCII, width of field or unit string */
+            int  *status)    /* IO - error status                           */
+/*
+  Get the 'equivalent' table column type. 
+
+  This routine is similar to the ffgtcl routine (which returns the physical
+  datatype of the column, as stored in the FITS file) except that if the
+  TSCALn and TZEROn keywords are defined for the column, then it returns
+  the 'equivalent' datatype.  Thus, if the column is defined as '1I'  (short
+  integer) this routine may return the type as 'TUSHORT' or as 'TFLOAT'
+  depending on the TSCALn and TZEROn values.
+  
+  Returns the datatype code of the column, as well as the vector
+  repeat count and (if it is an ASCII character column) the
+  width of the field or a unit string within the field.  This supports the
+  TFORMn = 'rAw' syntax for specifying arrays of substrings, so
+  if TFORMn = '60A12' then repeat = 60 and width = 12.
+*/
+{
+    tcolumn *colptr;
+    int hdutype, decims, tcode, effcode;
+    double tscale, tzero, min_val, max_val;
+    long lngscale, lngzero = 0, tmpwidth;
+
+    if (*status > 0)
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+        if ( ffrdef(fptr, status) > 0)               /* rescan header */
+            return(*status);
+
+    if (colnum < 1 || colnum > (fptr->Fptr)->tfield)
+        return(*status = BAD_COL_NUM);
+
+    colptr = (fptr->Fptr)->tableptr;   /* pointer to first column */
+    colptr += (colnum - 1);    /* offset to correct column */
+
+    if (ffghdt(fptr, &hdutype, status) > 0)
+        return(*status);
+
+    if (hdutype == ASCII_TBL)
+    {
+      ffasfm(colptr->tform, typecode, &tmpwidth, &decims, status);
+      *width = tmpwidth;
+
+      if (repeat)
+           *repeat = 1;
+    }
+    else
+    {
+      if (typecode)
+          *typecode = colptr->tdatatype;
+
+      if (width)
+          *width = colptr->twidth;
+
+      if (repeat)
+          *repeat = colptr->trepeat;
+    }
+
+    /* return if caller is not interested in the typecode value */
+    if (!typecode)
+        return(*status);
+
+    /* check if the tscale and tzero keywords are defined, which might
+       change the effective datatype of the column  */
+
+    tscale = colptr->tscale;
+    tzero = colptr->tzero;
+
+    if (tscale == 1.0 && tzero == 0.0)  /* no scaling */
+        return(*status);
+ 
+    tcode = abs(*typecode);
+
+    switch (tcode)
+    {
+      case TBYTE:   /* binary table 'rB' column */
+        min_val = 0.;
+        max_val = 255.0;
+        break;
+
+      case TSHORT:
+        min_val = -32768.0;
+        max_val =  32767.0;
+        break;
+        
+      case TLONG:
+
+        min_val = -2147483648.0;
+        max_val =  2147483647.0;
+        break;
+        
+      default:  /* don't have to deal with other data types */
+        return(*status);
+    }
+
+    if (tscale >= 0.) {
+        min_val = tzero + tscale * min_val;
+        max_val = tzero + tscale * max_val;
+    } else {
+        max_val = tzero + tscale * min_val;
+        min_val = tzero + tscale * max_val;
+    }
+    if (tzero < 2147483648.)  /* don't exceed range of 32-bit integer */
+       lngzero = (long) tzero;
+    lngscale   = (long) tscale;
+
+    if ((tzero != 2147483648.) && /* special value that exceeds integer range */
+       (lngzero != tzero || lngscale != tscale)) { /* not integers? */
+       /* floating point scaled values; just decide on required precision */
+       if (tcode == TBYTE || tcode == TSHORT)
+          effcode = TFLOAT;
+       else
+          effcode = TDOUBLE;
+
+    /*
+       In all the remaining cases, TSCALn and TZEROn are integers,
+       and not equal to 1 and 0, respectively.  
+    */
+
+    } else if ((min_val == -128.) && (max_val == 127.)) {
+        effcode = TSBYTE;
+ 
+    } else if ((min_val >= -32768.0) && (max_val <= 32767.0)) {
+        effcode = TSHORT;
+
+    } else if ((min_val >= 0.0) && (max_val <= 65535.0)) {
+        effcode = TUSHORT;
+
+    } else if ((min_val >= -2147483648.0) && (max_val <= 2147483647.0)) {
+        effcode = TLONG;
+
+    } else if ((min_val >= 0.0) && (max_val < 4294967296.0)) {
+        effcode = TULONG;
+
+    } else {  /* exceeds the range of a 32-bit integer */
+        effcode = TDOUBLE;
+    }   
+
+    /* return the effective datatype code (negative if variable length col.) */
+    if (*typecode < 0)  /* variable length array column */
+        *typecode = -effcode;
+    else
+        *typecode = effcode;
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgncl( fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  *ncols,     /* O - number of columns in the table          */
+            int  *status)    /* IO - error status                           */
+/*
+  Get the number of columns in the table (= TFIELDS keyword)
+*/
+{
+    if (*status > 0)
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+        if ( ffrdef(fptr, status) > 0)               /* rescan header */
+            return(*status);
+
+    if ((fptr->Fptr)->hdutype == IMAGE_HDU)
+        return(*status = NOT_TABLE);
+
+    *ncols = (fptr->Fptr)->tfield;
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgnrw( fitsfile *fptr,  /* I - FITS file pointer                       */
+            long  *nrows,    /* O - number of rows in the table             */
+            int  *status)    /* IO - error status                           */
+/*
+  Get the number of rows in the table (= NAXIS2 keyword)
+*/
+{
+    if (*status > 0)
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+        if ( ffrdef(fptr, status) > 0)               /* rescan header */
+            return(*status);
+
+    if ((fptr->Fptr)->hdutype == IMAGE_HDU)
+        return(*status = NOT_TABLE);
+
+    /* the NAXIS2 keyword may not be up to date, so use the structure value */
+    *nrows = (long) (fptr->Fptr)->numrows;
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgnrwll( fitsfile *fptr,  /* I - FITS file pointer                     */
+            LONGLONG  *nrows,  /* O - number of rows in the table           */
+            int  *status)      /* IO - error status                         */
+/*
+  Get the number of rows in the table (= NAXIS2 keyword)
+*/
+{
+    if (*status > 0)
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+        if ( ffrdef(fptr, status) > 0)               /* rescan header */
+            return(*status);
+
+    if ((fptr->Fptr)->hdutype == IMAGE_HDU)
+        return(*status = NOT_TABLE);
+
+    /* the NAXIS2 keyword may not be up to date, so use the structure value */
+    *nrows = (fptr->Fptr)->numrows;
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgacl( fitsfile *fptr,   /* I - FITS file pointer                      */
+            int  colnum,      /* I - column number                          */
+            char *ttype,      /* O - TTYPEn keyword value                   */
+            long *tbcol,      /* O - TBCOLn keyword value                   */
+            char *tunit,      /* O - TUNITn keyword value                   */
+            char *tform,      /* O - TFORMn keyword value                   */
+            double *tscal,    /* O - TSCALn keyword value                   */
+            double *tzero,    /* O - TZEROn keyword value                   */
+            char *tnull,      /* O - TNULLn keyword value                   */
+            char *tdisp,      /* O - TDISPn keyword value                   */
+            int  *status)     /* IO - error status                          */
+/*
+  get ASCII column keyword values
+*/
+{
+    char name[FLEN_KEYWORD], comm[FLEN_COMMENT];
+    tcolumn *colptr;
+    int tstatus;
+
+    if (*status > 0)
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+        if ( ffrdef(fptr, status) > 0)               /* rescan header */
+            return(*status);
+
+    if (colnum < 1 || colnum > (fptr->Fptr)->tfield)
+        return(*status = BAD_COL_NUM);
+
+    /* get what we can from the column structure */
+
+    colptr = (fptr->Fptr)->tableptr;   /* pointer to first column */
+    colptr += (colnum -1);     /* offset to correct column */
+
+    if (ttype)
+        strcpy(ttype, colptr->ttype);
+
+    if (tbcol)
+        *tbcol = (long) ((colptr->tbcol) + 1);  /* first col is 1, not 0 */
+
+    if (tform)
+        strcpy(tform, colptr->tform);
+
+    if (tscal)
+        *tscal = colptr->tscale;
+
+    if (tzero)
+        *tzero = colptr->tzero;
+
+    if (tnull)
+        strcpy(tnull, colptr->strnull);
+
+    /* read keywords to get additional parameters */
+
+    if (tunit)
+    {
+        ffkeyn("TUNIT", colnum, name, status);
+        tstatus = 0;
+        *tunit = '\0';
+        ffgkys(fptr, name, tunit, comm, &tstatus);
+    }
+
+    if (tdisp)
+    {
+        ffkeyn("TDISP", colnum, name, status);
+        tstatus = 0;
+        *tdisp = '\0';
+        ffgkys(fptr, name, tdisp, comm, &tstatus);
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgbcl( fitsfile *fptr,   /* I - FITS file pointer                      */
+            int  colnum,      /* I - column number                          */
+            char *ttype,      /* O - TTYPEn keyword value                   */
+            char *tunit,      /* O - TUNITn keyword value                   */
+            char *dtype,      /* O - datatype char: I, J, E, D, etc.        */
+            long *repeat,     /* O - vector column repeat count             */
+            double *tscal,    /* O - TSCALn keyword value                   */
+            double *tzero,    /* O - TZEROn keyword value                   */
+            long *tnull,      /* O - TNULLn keyword value integer cols only */
+            char *tdisp,      /* O - TDISPn keyword value                   */
+            int  *status)     /* IO - error status                          */
+/*
+  get BINTABLE column keyword values
+*/
+{
+    LONGLONG trepeat, ttnull;
+    
+    if (*status > 0)
+        return(*status);
+
+    ffgbclll(fptr, colnum, ttype, tunit, dtype, &trepeat, tscal, tzero,
+             &ttnull, tdisp, status);
+
+    if (repeat)
+        *repeat = (long) trepeat;
+
+    if (tnull)
+        *tnull = (long) ttnull;
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgbclll( fitsfile *fptr,   /* I - FITS file pointer                      */
+            int  colnum,      /* I - column number                          */
+            char *ttype,      /* O - TTYPEn keyword value                   */
+            char *tunit,      /* O - TUNITn keyword value                   */
+            char *dtype,      /* O - datatype char: I, J, E, D, etc.        */
+            LONGLONG *repeat, /* O - vector column repeat count             */
+            double *tscal,    /* O - TSCALn keyword value                   */
+            double *tzero,    /* O - TZEROn keyword value                   */
+            LONGLONG *tnull,  /* O - TNULLn keyword value integer cols only */
+            char *tdisp,      /* O - TDISPn keyword value                   */
+            int  *status)     /* IO - error status                          */
+/*
+  get BINTABLE column keyword values
+*/
+{
+    char name[FLEN_KEYWORD], comm[FLEN_COMMENT];
+    tcolumn *colptr;
+    int tstatus;
+
+    if (*status > 0)
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+        if ( ffrdef(fptr, status) > 0)               /* rescan header */
+            return(*status);
+
+    if (colnum < 1 || colnum > (fptr->Fptr)->tfield)
+        return(*status = BAD_COL_NUM);
+
+    /* get what we can from the column structure */
+
+    colptr = (fptr->Fptr)->tableptr;   /* pointer to first column */
+    colptr += (colnum -1);     /* offset to correct column */
+
+    if (ttype)
+        strcpy(ttype, colptr->ttype);
+
+    if (dtype)
+    {
+        if (colptr->tdatatype < 0)  /* add the "P" prefix for */
+            strcpy(dtype, "P");     /* variable length columns */
+        else
+            dtype[0] = 0;
+
+        if      (abs(colptr->tdatatype) == TBIT)
+            strcat(dtype, "X");
+        else if (abs(colptr->tdatatype) == TBYTE)
+            strcat(dtype, "B");
+        else if (abs(colptr->tdatatype) == TLOGICAL)
+            strcat(dtype, "L");
+        else if (abs(colptr->tdatatype) == TSTRING)
+            strcat(dtype, "A");
+        else if (abs(colptr->tdatatype) == TSHORT)
+            strcat(dtype, "I");
+        else if (abs(colptr->tdatatype) == TLONG)
+            strcat(dtype, "J");
+        else if (abs(colptr->tdatatype) == TLONGLONG)
+            strcat(dtype, "K");
+        else if (abs(colptr->tdatatype) == TFLOAT)
+            strcat(dtype, "E");
+        else if (abs(colptr->tdatatype) == TDOUBLE)
+            strcat(dtype, "D");
+        else if (abs(colptr->tdatatype) == TCOMPLEX)
+            strcat(dtype, "C");
+        else if (abs(colptr->tdatatype) == TDBLCOMPLEX)
+            strcat(dtype, "M");
+    }
+
+    if (repeat)
+        *repeat = colptr->trepeat;
+
+    if (tscal)
+        *tscal  = colptr->tscale;
+
+    if (tzero)
+        *tzero  = colptr->tzero;
+
+    if (tnull)
+        *tnull  = colptr->tnull;
+
+    /* read keywords to get additional parameters */
+
+    if (tunit)
+    {
+        ffkeyn("TUNIT", colnum, name, status);
+        tstatus = 0;
+        *tunit = '\0';
+        ffgkys(fptr, name, tunit, comm, &tstatus);
+    }
+
+    if (tdisp)
+    {
+        ffkeyn("TDISP", colnum, name, status);
+        tstatus = 0;
+        *tdisp = '\0';
+        ffgkys(fptr, name, tdisp, comm, &tstatus);
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffghdn(fitsfile *fptr,   /* I - FITS file pointer                      */
+            int *chdunum)    /* O - number of the CHDU; 1 = primary array  */
+/*
+  Return the number of the Current HDU in the FITS file.  The primary array
+  is HDU number 1.  Note that this is one of the few cfitsio routines that
+  does not return the error status value as the value of the function.
+*/
+{
+    *chdunum = (fptr->HDUposition) + 1;
+    return(*chdunum);
+}
+/*--------------------------------------------------------------------------*/
+int ffghadll(fitsfile *fptr,     /* I - FITS file pointer                     */
+            LONGLONG *headstart, /* O - byte offset to beginning of CHDU      */
+            LONGLONG *datastart, /* O - byte offset to beginning of next HDU  */
+            LONGLONG *dataend,   /* O - byte offset to beginning of next HDU  */
+            int *status)         /* IO - error status     */
+/*
+  Return the address (= byte offset) in the FITS file to the beginning of
+  the current HDU, the beginning of the data unit, and the end of the data unit.
+*/
+{
+    if (*status > 0)
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+    {
+        if (ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status) > 0)
+            return(*status);
+    }
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+    {
+        if (ffrdef(fptr, status) > 0)           /* rescan header */
+            return(*status);
+    }
+
+    if (headstart)
+        *headstart = (fptr->Fptr)->headstart[(fptr->Fptr)->curhdu];       
+
+    if (datastart)
+        *datastart = (fptr->Fptr)->datastart;
+
+    if (dataend)
+        *dataend = (fptr->Fptr)->headstart[((fptr->Fptr)->curhdu) + 1];       
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffghof(fitsfile *fptr,     /* I - FITS file pointer                     */
+            OFF_T *headstart,  /* O - byte offset to beginning of CHDU      */
+            OFF_T *datastart,  /* O - byte offset to beginning of next HDU  */
+            OFF_T *dataend,    /* O - byte offset to beginning of next HDU  */
+            int *status)       /* IO - error status     */
+/*
+  Return the address (= byte offset) in the FITS file to the beginning of
+  the current HDU, the beginning of the data unit, and the end of the data unit.
+*/
+{
+    if (*status > 0)
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+    {
+        if (ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status) > 0)
+            return(*status);
+    }
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+    {
+        if (ffrdef(fptr, status) > 0)           /* rescan header */
+            return(*status);
+    }
+
+    if (headstart)
+        *headstart = (OFF_T) (fptr->Fptr)->headstart[(fptr->Fptr)->curhdu];       
+
+    if (datastart)
+        *datastart = (OFF_T) (fptr->Fptr)->datastart;
+
+    if (dataend)
+        *dataend   = (OFF_T) (fptr->Fptr)->headstart[((fptr->Fptr)->curhdu) + 1];       
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffghad(fitsfile *fptr,     /* I - FITS file pointer                     */
+            long *headstart,  /* O - byte offset to beginning of CHDU      */
+            long *datastart,  /* O - byte offset to beginning of next HDU  */
+            long *dataend,    /* O - byte offset to beginning of next HDU  */
+            int *status)       /* IO - error status     */
+/*
+  Return the address (= byte offset) in the FITS file to the beginning of
+  the current HDU, the beginning of the data unit, and the end of the data unit.
+*/
+{
+    LONGLONG shead, sdata, edata;
+
+    if (*status > 0)
+        return(*status);
+
+    ffghadll(fptr, &shead, &sdata, &edata, status);
+
+    if (headstart)
+    {
+        if (shead > LONG_MAX)
+            *status = NUM_OVERFLOW;
+        else
+            *headstart = (long) shead;
+    }
+
+    if (datastart)
+    {
+        if (sdata > LONG_MAX)
+            *status = NUM_OVERFLOW;
+        else
+            *datastart = (long) sdata;
+    }
+
+    if (dataend)
+    {
+        if (edata > LONG_MAX)
+            *status = NUM_OVERFLOW;
+        else
+            *dataend = (long) edata;       
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffrhdu(fitsfile *fptr,    /* I - FITS file pointer */
+           int *hdutype,      /* O - type of HDU       */
+           int *status)       /* IO - error status     */
+/*
+  read the required keywords of the CHDU and initialize the corresponding
+  structure elements that describe the format of the HDU
+*/
+{
+    int ii, tstatus;
+    char card[FLEN_CARD];
+    char name[FLEN_KEYWORD], value[FLEN_VALUE], comm[FLEN_COMMENT];
+    char xname[FLEN_VALUE], *xtension, urltype[20];
+
+    if (*status > 0)
+        return(*status);
+
+    if (ffgrec(fptr, 1, card, status) > 0 )  /* get the 80-byte card */
+    {
+        ffpmsg("Cannot read first keyword in header (ffrhdu).");
+        return(*status);
+    }
+    strncpy(name,card,8);  /* first 8 characters = the keyword name */
+    name[8] = '\0';
+
+    for (ii=7; ii >= 0; ii--)  /* replace trailing blanks with nulls */
+    {
+        if (name[ii] == ' ')
+            name[ii] = '\0';
+        else
+            break;
+    }
+
+    if (ffpsvc(card, value, comm, status) > 0)   /* parse value and comment */
+    {
+        ffpmsg("Cannot read value of first  keyword in header (ffrhdu):");
+        ffpmsg(card);
+        return(*status);
+    }
+
+    if (!strcmp(name, "SIMPLE"))        /* this is the primary array */
+    {
+
+       ffpinit(fptr, status);           /* initialize the primary array */
+
+       if (hdutype != NULL)
+           *hdutype = 0;
+    }
+
+    else if (!strcmp(name, "XTENSION"))   /* this is an XTENSION keyword */
+    {
+        if (ffc2s(value, xname, status) > 0)  /* get the value string */
+        {
+            ffpmsg("Bad value string for XTENSION keyword:");
+            ffpmsg(value);
+            return(*status);
+        }
+
+        xtension = xname;
+        while (*xtension == ' ')  /* ignore any leading spaces in name */
+           xtension++;
+
+        if (!strcmp(xtension, "TABLE"))
+        {
+            ffainit(fptr, status);       /* initialize the ASCII table */
+            if (hdutype != NULL)
+                *hdutype = 1;
+        }
+
+        else if (!strcmp(xtension, "BINTABLE") ||
+                 !strcmp(xtension, "A3DTABLE") ||
+                 !strcmp(xtension, "3DTABLE") )
+        {
+            ffbinit(fptr, status);       /* initialize the binary table */
+            if (hdutype != NULL)
+                *hdutype = 2;
+        }
+
+        else
+        {
+            tstatus = 0;
+            ffpinit(fptr, &tstatus);       /* probably an IMAGE extension */
+
+            if (tstatus == UNKNOWN_EXT && hdutype != NULL)
+                *hdutype = -1;       /* don't recognize this extension type */
+            else
+            {
+                *status = tstatus;
+                if (hdutype != NULL)
+                    *hdutype = 0;
+            }
+        }
+    }
+
+    else     /*  not the start of a new extension */
+    {
+        if (card[0] == 0  ||
+            card[0] == 10)     /* some editors append this character to EOF */
+        {           
+            *status = END_OF_FILE;
+        }
+        else
+        {
+          *status = UNKNOWN_REC;  /* found unknown type of record */
+          ffpmsg
+        ("Extension doesn't start with SIMPLE or XTENSION keyword. (ffrhdu)");
+        ffpmsg(card);
+        }
+    }
+
+    /*  compare the starting position of the next HDU (if any) with the size */
+    /*  of the whole file to see if this is the last HDU in the file */
+
+    if ((fptr->Fptr)->headstart[ (fptr->Fptr)->curhdu + 1] < 
+        (fptr->Fptr)->logfilesize )
+    {
+        (fptr->Fptr)->lasthdu = 0;  /* no, not the last HDU */
+    }
+    else
+    {
+        (fptr->Fptr)->lasthdu = 1;  /* yes, this is the last HDU */
+
+        /* special code for mem:// type files (FITS file in memory) */
+        /* Allocate enough memory to hold the entire HDU. */
+        /* Without this code, CFITSIO would repeatedly realloc  memory */
+        /* to incrementally increase the size of the file by 2880 bytes */
+        /* at a time, until it reached the final size */
+ 
+        ffurlt(fptr, urltype, status);
+        if (!strcmp(urltype,"mem://") || !strcmp(urltype,"memkeep://"))
+        {
+            fftrun(fptr, (fptr->Fptr)->headstart[ (fptr->Fptr)->curhdu + 1],
+               status);
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpinit(fitsfile *fptr,      /* I - FITS file pointer */
+           int *status)          /* IO - error status     */
+/*
+  initialize the parameters defining the structure of the primary array
+  or an Image extension 
+*/
+{
+    int groups, tstatus, simple, bitpix, naxis, extend, nspace;
+    int ttype = 0, bytlen = 0, ii;
+    long  pcount, gcount;
+    LONGLONG naxes[999], npix, blank;
+    double bscale, bzero;
+    char comm[FLEN_COMMENT];
+    tcolumn *colptr;
+
+    if (*status > 0)
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    (fptr->Fptr)->hdutype = IMAGE_HDU; /* primary array or IMAGE extension  */
+    (fptr->Fptr)->headend = (fptr->Fptr)->logfilesize;  /* set max size */
+
+    groups = 0;
+    tstatus = *status;
+
+    /* get all the descriptive info about this HDU */
+    ffgphd(fptr, 999, &simple, &bitpix, &naxis, naxes, &pcount, &gcount, 
+           &extend, &bscale, &bzero, &blank, &nspace, status);
+
+    if (*status == NOT_IMAGE)
+        *status = tstatus;    /* ignore 'unknown extension type' error */
+    else if (*status > 0)
+        return(*status);
+
+    /*
+       the logical end of the header is 80 bytes before the current position, 
+       minus any trailing blank keywords just before the END keyword.
+    */
+    (fptr->Fptr)->headend = (fptr->Fptr)->nextkey - (80 * (nspace + 1));
+
+    /* the data unit begins at the beginning of the next logical block */
+    (fptr->Fptr)->datastart = (((fptr->Fptr)->nextkey - 80) / 2880 + 1)
+                              * 2880;
+
+    if (naxis > 0 && naxes[0] == 0)  /* test for 'random groups' */
+    {
+        tstatus = 0;
+        ffmaky(fptr, 2, status);         /* reset to beginning of header */
+
+        if (ffgkyl(fptr, "GROUPS", &groups, comm, &tstatus))
+            groups = 0;          /* GROUPS keyword not found */
+    }
+
+    if (bitpix == BYTE_IMG)   /* test  bitpix and set the datatype code */
+    {
+        ttype=TBYTE;
+        bytlen=1;
+    }
+    else if (bitpix == SHORT_IMG)
+    {
+        ttype=TSHORT;
+        bytlen=2;
+    }
+    else if (bitpix == LONG_IMG)
+    {
+        ttype=TLONG;
+        bytlen=4;
+    }
+    else if (bitpix == LONGLONG_IMG)
+    {
+        ttype=TLONGLONG;
+        bytlen=8;
+    }
+    else if (bitpix == FLOAT_IMG)
+    {
+        ttype=TFLOAT;
+        bytlen=4;
+    }
+    else if (bitpix == DOUBLE_IMG)
+    {
+        ttype=TDOUBLE;
+        bytlen=8;
+    }
+        
+    /*   calculate the size of the primary array  */
+    (fptr->Fptr)->imgdim = naxis;
+    if (naxis == 0)
+    {
+        npix = 0;
+    }
+    else
+    {
+        if (groups)
+        {
+            npix = 1;  /* NAXIS1 = 0 is a special flag for 'random groups' */
+        }
+        else
+        {
+            npix = naxes[0];
+        }
+
+        (fptr->Fptr)->imgnaxis[0] = naxes[0];
+        for (ii=1; ii < naxis; ii++)
+        {
+            npix = npix*naxes[ii];   /* calc number of pixels in the array */
+            (fptr->Fptr)->imgnaxis[ii] = naxes[ii];
+        }
+    }
+
+    /*
+       now we know everything about the array; just fill in the parameters:
+       the next HDU begins in the next logical block after the data
+    */
+
+    (fptr->Fptr)->headstart[ (fptr->Fptr)->curhdu + 1] =
+         (fptr->Fptr)->datastart + 
+         ( ((LONGLONG) pcount + npix) * bytlen * gcount + 2879) / 2880 * 2880;
+
+    /*
+      initialize the fictitious heap starting address (immediately following
+      the array data) and a zero length heap.  This is used to find the
+      end of the data when checking the fill values in the last block. 
+    */
+    (fptr->Fptr)->heapstart = (npix + pcount) * bytlen * gcount;
+    (fptr->Fptr)->heapsize = 0;
+
+    (fptr->Fptr)->compressimg = 0;  /* this is not a compressed image */
+
+    if (naxis == 0)
+    {
+        (fptr->Fptr)->rowlength = 0;    /* rows have zero length */
+        (fptr->Fptr)->tfield = 0;       /* table has no fields   */
+
+        /* free the tile-compressed image cache, if it exists */
+        if ((fptr->Fptr)->tiledata) {
+	       free((fptr->Fptr)->tiledata);
+	       (fptr->Fptr)->tiledata = 0;
+	       (fptr->Fptr)->tilerow = 0;
+	       (fptr->Fptr)->tiledatasize = 0;
+	       (fptr->Fptr)->tiletype = 0;
+        }
+
+        if ((fptr->Fptr)->tilenullarray) {
+	       free((fptr->Fptr)->tilenullarray);
+	       (fptr->Fptr)->tilenullarray = 0;
+        }
+
+        if ((fptr->Fptr)->tableptr)
+           free((fptr->Fptr)->tableptr); /* free memory for the old CHDU */
+
+        (fptr->Fptr)->tableptr = 0;     /* set a null table structure pointer */
+        (fptr->Fptr)->numrows = 0;
+        (fptr->Fptr)->origrows = 0;
+    }
+    else
+    {
+      /*
+        The primary array is actually interpreted as a binary table.  There
+        are two columns: the first column contains the group parameters if any.
+        The second column contains the primary array of data as a single vector
+        column element. In the case of 'random grouped' format, each group
+        is stored in a separate row of the table.
+      */
+        /* the number of rows is equal to the number of groups */
+        (fptr->Fptr)->numrows = gcount;
+        (fptr->Fptr)->origrows = gcount;
+
+        (fptr->Fptr)->rowlength = (npix + pcount) * bytlen; /* total size */
+        (fptr->Fptr)->tfield = 2;  /* 2 fields: group params and the image */
+
+        /* free the tile-compressed image cache, if it exists */
+
+        /* free the tile-compressed image cache, if it exists */
+        if ((fptr->Fptr)->tiledata) {
+	       free((fptr->Fptr)->tiledata);
+	       (fptr->Fptr)->tiledata = 0;
+	       (fptr->Fptr)->tilerow = 0;
+	       (fptr->Fptr)->tiledatasize = 0;
+	       (fptr->Fptr)->tiletype = 0;
+        }
+
+        if ((fptr->Fptr)->tilenullarray) {
+	       free((fptr->Fptr)->tilenullarray);
+	       (fptr->Fptr)->tilenullarray = 0;
+        }
+
+        if ((fptr->Fptr)->tableptr)
+           free((fptr->Fptr)->tableptr); /* free memory for the old CHDU */
+
+        colptr = (tcolumn *) calloc(2, sizeof(tcolumn) ) ;
+
+        if (!colptr)
+        {
+          ffpmsg
+          ("malloc failed to get memory for FITS array descriptors (ffpinit)");
+          (fptr->Fptr)->tableptr = 0;  /* set a null table structure pointer */
+          return(*status = ARRAY_TOO_BIG);
+        }
+
+        /* copy the table structure address to the fitsfile structure */
+        (fptr->Fptr)->tableptr = colptr; 
+
+        /* the first column represents the group parameters, if any */
+        colptr->tbcol = 0;
+        colptr->tdatatype = ttype;
+        colptr->twidth = bytlen;
+        colptr->trepeat = (LONGLONG) pcount;
+        colptr->tscale = 1.;
+        colptr->tzero = 0.;
+        colptr->tnull = blank;
+
+        colptr++;  /* increment pointer to the second column */
+
+        /* the second column represents the image array */
+        colptr->tbcol = pcount * bytlen; /* col starts after the group parms */
+        colptr->tdatatype = ttype; 
+        colptr->twidth = bytlen;
+        colptr->trepeat = npix;
+        colptr->tscale = bscale;
+        colptr->tzero = bzero;
+        colptr->tnull = blank;
+    }
+
+    /* reset next keyword pointer to the start of the header */
+    (fptr->Fptr)->nextkey = (fptr->Fptr)->headstart[ (fptr->Fptr)->curhdu ];
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffainit(fitsfile *fptr,      /* I - FITS file pointer */
+            int *status)         /* IO - error status     */
+{
+/*
+  initialize the parameters defining the structure of an ASCII table 
+*/
+    int  ii, nspace;
+    long tfield;
+    LONGLONG pcount, rowlen, nrows, tbcoln;
+    tcolumn *colptr = 0;
+    char name[FLEN_KEYWORD], value[FLEN_VALUE], comm[FLEN_COMMENT];
+    char message[FLEN_ERRMSG], errmsg[81];
+
+    if (*status > 0)
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    (fptr->Fptr)->hdutype = ASCII_TBL;  /* set that this is an ASCII table */
+    (fptr->Fptr)->headend = (fptr->Fptr)->logfilesize;  /* set max size */
+
+    /* get table parameters and test that the header is a valid: */
+    if (ffgttb(fptr, &rowlen, &nrows, &pcount, &tfield, status) > 0)  
+       return(*status);
+
+    if (pcount != 0)
+    {
+       ffpmsg("PCOUNT keyword not equal to 0 in ASCII table (ffainit).");
+       sprintf(errmsg, "  PCOUNT = %ld", (long) pcount);
+       ffpmsg(errmsg);
+       return(*status = BAD_PCOUNT);
+    }
+
+    (fptr->Fptr)->rowlength = rowlen; /* store length of a row */
+    (fptr->Fptr)->tfield = tfield; /* store number of table fields in row */
+
+     /* free the tile-compressed image cache, if it exists */
+     if ((fptr->Fptr)->tiledata) {
+	       free((fptr->Fptr)->tiledata);
+	       (fptr->Fptr)->tiledata = 0;
+	       (fptr->Fptr)->tilerow = 0;
+	       (fptr->Fptr)->tiledatasize = 0;
+	       (fptr->Fptr)->tiletype = 0;
+     }
+
+     if ((fptr->Fptr)->tilenullarray) {
+	       free((fptr->Fptr)->tilenullarray);
+	       (fptr->Fptr)->tilenullarray = 0;
+     }
+
+
+    if ((fptr->Fptr)->tableptr)
+       free((fptr->Fptr)->tableptr); /* free memory for the old CHDU */
+
+    /* mem for column structures ; space is initialized = 0 */
+    if (tfield > 0)
+    {
+      colptr = (tcolumn *) calloc(tfield, sizeof(tcolumn) );
+      if (!colptr)
+      {
+        ffpmsg
+        ("malloc failed to get memory for FITS table descriptors (ffainit)");
+        (fptr->Fptr)->tableptr = 0;  /* set a null table structure pointer */
+        return(*status = ARRAY_TOO_BIG);
+      }
+    }
+
+    /* copy the table structure address to the fitsfile structure */
+    (fptr->Fptr)->tableptr = colptr; 
+
+    /*  initialize the table field parameters */
+    for (ii = 0; ii < tfield; ii++, colptr++)
+    {
+        colptr->ttype[0] = '\0';  /* null column name */
+        colptr->tscale = 1.;
+        colptr->tzero  = 0.;
+        colptr->strnull[0] = ASCII_NULL_UNDEFINED;  /* null value undefined */
+        colptr->tbcol = -1;          /* initialize to illegal value */
+        colptr->tdatatype = -9999;   /* initialize to illegal value */
+    }
+
+    /*
+      Initialize the fictitious heap starting address (immediately following
+      the table data) and a zero length heap.  This is used to find the
+      end of the table data when checking the fill values in the last block. 
+      There is no special data following an ASCII table.
+    */
+    (fptr->Fptr)->numrows = nrows;
+    (fptr->Fptr)->origrows = nrows;
+    (fptr->Fptr)->heapstart = rowlen * nrows;
+    (fptr->Fptr)->heapsize = 0;
+
+    (fptr->Fptr)->compressimg = 0;  /* this is not a compressed image */
+
+    /* now search for the table column keywords and the END keyword */
+
+    for (nspace = 0, ii = 8; 1; ii++)  /* infinite loop  */
+    {
+        ffgkyn(fptr, ii, name, value, comm, status);
+
+        /* try to ignore minor syntax errors */
+        if (*status == NO_QUOTE)
+        {
+            strcat(value, "'");
+            *status = 0;
+        }
+        else if (*status == BAD_KEYCHAR)
+        {
+            *status = 0;
+        }
+
+        if (*status == END_OF_FILE)
+        {
+            ffpmsg("END keyword not found in ASCII table header (ffainit).");
+            return(*status = NO_END);
+        }
+        else if (*status > 0)
+            return(*status);
+
+        else if (name[0] == 'T')   /* keyword starts with 'T' ? */
+            ffgtbp(fptr, name, value, status); /* test if column keyword */
+
+        else if (!FSTRCMP(name, "END"))  /* is this the END keyword? */
+            break;
+
+        if (!name[0] && !value[0] && !comm[0])  /* a blank keyword? */
+            nspace++;
+
+        else
+            nspace = 0;
+    }
+
+    /* test that all required keywords were found and have legal values */
+    colptr = (fptr->Fptr)->tableptr;
+    for (ii = 0; ii < tfield; ii++, colptr++)
+    {
+        tbcoln = colptr->tbcol;  /* the starting column number (zero based) */
+
+        if (colptr->tdatatype == -9999)
+        {
+            ffkeyn("TFORM", ii+1, name, status);  /* construct keyword name */
+            sprintf(message,"Required %s keyword not found (ffainit).", name);
+            ffpmsg(message);
+            return(*status = NO_TFORM);
+        }
+
+        else if (tbcoln == -1)
+        {
+            ffkeyn("TBCOL", ii+1, name, status); /* construct keyword name */
+            sprintf(message,"Required %s keyword not found (ffainit).", name);
+            ffpmsg(message);
+            return(*status = NO_TBCOL);
+        }
+
+        else if ((fptr->Fptr)->rowlength != 0 && 
+                (tbcoln < 0 || tbcoln >= (fptr->Fptr)->rowlength ) )
+        {
+            ffkeyn("TBCOL", ii+1, name, status);  /* construct keyword name */
+            sprintf(message,"Value of %s keyword out of range: %ld (ffainit).",
+            name, (long) tbcoln);
+            ffpmsg(message);
+            return(*status = BAD_TBCOL);
+        }
+
+        else if ((fptr->Fptr)->rowlength != 0 && 
+                 tbcoln + colptr->twidth > (fptr->Fptr)->rowlength )
+        {
+            sprintf(message,"Column %d is too wide to fit in table (ffainit)",
+            ii+1);
+            ffpmsg(message);
+            sprintf(message, " TFORM = %s and NAXIS1 = %ld",
+                    colptr->tform, (long) (fptr->Fptr)->rowlength);
+            ffpmsg(message);
+            return(*status = COL_TOO_WIDE);
+        }
+    }
+
+    /*
+      now we know everything about the table; just fill in the parameters:
+      the 'END' record is 80 bytes before the current position, minus
+      any trailing blank keywords just before the END keyword.
+    */
+    (fptr->Fptr)->headend = (fptr->Fptr)->nextkey - (80 * (nspace + 1));
+ 
+    /* the data unit begins at the beginning of the next logical block */
+    (fptr->Fptr)->datastart = (((fptr->Fptr)->nextkey - 80) / 2880 + 1) 
+                              * 2880;
+
+    /* the next HDU begins in the next logical block after the data  */
+    (fptr->Fptr)->headstart[ (fptr->Fptr)->curhdu + 1] =
+         (fptr->Fptr)->datastart +
+         ( ((LONGLONG)rowlen * nrows + 2879) / 2880 * 2880 );
+
+    /* reset next keyword pointer to the start of the header */
+    (fptr->Fptr)->nextkey = (fptr->Fptr)->headstart[ (fptr->Fptr)->curhdu ];
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffbinit(fitsfile *fptr,     /* I - FITS file pointer */
+            int *status)        /* IO - error status     */
+{
+/*
+  initialize the parameters defining the structure of a binary table 
+*/
+    int  ii, nspace;
+    long tfield;
+    LONGLONG pcount, rowlen, nrows, totalwidth;
+    tcolumn *colptr = 0;
+    char name[FLEN_KEYWORD], value[FLEN_VALUE], comm[FLEN_COMMENT];
+    char message[FLEN_ERRMSG];
+
+    if (*status > 0)
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    (fptr->Fptr)->hdutype = BINARY_TBL;  /* set that this is a binary table */
+    (fptr->Fptr)->headend = (fptr->Fptr)->logfilesize;  /* set max size */
+
+    /* get table parameters and test that the header is valid: */
+    if (ffgttb(fptr, &rowlen, &nrows, &pcount, &tfield, status) > 0)
+       return(*status);
+
+    (fptr->Fptr)->rowlength =  rowlen; /* store length of a row */
+    (fptr->Fptr)->tfield = tfield; /* store number of table fields in row */
+
+
+    /* free the tile-compressed image cache, if it exists */
+    if ((fptr->Fptr)->tiledata) {
+	       free((fptr->Fptr)->tiledata);
+	       (fptr->Fptr)->tiledata = 0;
+	       (fptr->Fptr)->tilerow = 0;
+	       (fptr->Fptr)->tiledatasize = 0;
+	       (fptr->Fptr)->tiletype = 0;
+    }
+
+    if ((fptr->Fptr)->tilenullarray) {
+	       free((fptr->Fptr)->tilenullarray);
+	       (fptr->Fptr)->tilenullarray = 0;
+    }
+
+    if ((fptr->Fptr)->tableptr)
+       free((fptr->Fptr)->tableptr); /* free memory for the old CHDU */
+
+    /* mem for column structures ; space is initialized = 0  */
+    if (tfield > 0)
+    {
+      colptr = (tcolumn *) calloc(tfield, sizeof(tcolumn) );
+      if (!colptr)
+      {
+        ffpmsg
+        ("malloc failed to get memory for FITS table descriptors (ffbinit)");
+        (fptr->Fptr)->tableptr = 0;  /* set a null table structure pointer */
+        return(*status = ARRAY_TOO_BIG);
+      }
+    }
+
+    /* copy the table structure address to the fitsfile structure */
+    (fptr->Fptr)->tableptr = colptr; 
+
+    /* initialize the table field parameters */
+    for (ii = 0; ii < tfield; ii++, colptr++)
+    {
+        colptr->ttype[0] = '\0';  /* null column name */
+        colptr->tscale = 1.;
+        colptr->tzero  = 0.;
+        colptr->tnull  = NULL_UNDEFINED; /* (integer) null value undefined */
+        colptr->tdatatype = -9999;   /* initialize to illegal value */
+        colptr->trepeat = 1;
+        colptr->strnull[0] = '\0'; /* for ASCII string columns (TFORM = rA) */
+    }
+
+    /*
+      Initialize the heap starting address (immediately following
+      the table data) and the size of the heap.  This is used to find the
+      end of the table data when checking the fill values in the last block. 
+    */
+    (fptr->Fptr)->numrows = nrows;
+    (fptr->Fptr)->origrows = nrows;
+    (fptr->Fptr)->heapstart = rowlen * nrows;
+    (fptr->Fptr)->heapsize = pcount;
+
+    (fptr->Fptr)->compressimg = 0;  /* initialize as not a compressed image */
+
+    /* now search for the table column keywords and the END keyword */
+
+    for (nspace = 0, ii = 8; 1; ii++)  /* infinite loop  */
+    {
+        ffgkyn(fptr, ii, name, value, comm, status);
+
+        /* try to ignore minor syntax errors */
+        if (*status == NO_QUOTE)
+        {
+            strcat(value, "'");
+            *status = 0;
+        }
+        else if (*status == BAD_KEYCHAR)
+        {
+            *status = 0;
+        }
+
+        if (*status == END_OF_FILE)
+        {
+            ffpmsg("END keyword not found in binary table header (ffbinit).");
+            return(*status = NO_END);
+        }
+        else if (*status > 0)
+            return(*status);
+
+        else if (name[0] == 'T')   /* keyword starts with 'T' ? */
+            ffgtbp(fptr, name, value, status); /* test if column keyword */
+
+        else if (!FSTRCMP(name, "ZIMAGE"))
+        {
+            if (value[0] == 'T')
+                (fptr->Fptr)->compressimg = 1; /* this is a compressed image */
+        }
+        else if (!FSTRCMP(name, "END"))  /* is this the END keyword? */
+            break;
+
+
+        if (!name[0] && !value[0] && !comm[0])  /* a blank keyword? */
+            nspace++;
+
+        else
+            nspace = 0; /* reset number of consecutive spaces before END */
+    }
+
+    /* test that all the required keywords were found and have legal values */
+    colptr = (fptr->Fptr)->tableptr;  /* set pointer to first column */
+
+    for (ii = 0; ii < tfield; ii++, colptr++)
+    {
+        if (colptr->tdatatype == -9999)
+        {
+            ffkeyn("TFORM", ii+1, name, status);  /* construct keyword name */
+            sprintf(message,"Required %s keyword not found (ffbinit).", name);
+            ffpmsg(message);
+            return(*status = NO_TFORM);
+        }
+    }
+
+    /*
+      now we know everything about the table; just fill in the parameters:
+      the 'END' record is 80 bytes before the current position, minus
+      any trailing blank keywords just before the END keyword.
+    */
+
+    (fptr->Fptr)->headend = (fptr->Fptr)->nextkey - (80 * (nspace + 1));
+ 
+    /* the data unit begins at the beginning of the next logical block */
+    (fptr->Fptr)->datastart = (((fptr->Fptr)->nextkey - 80) / 2880 + 1) 
+                              * 2880;
+
+    /* the next HDU begins in the next logical block after the data  */
+    (fptr->Fptr)->headstart[ (fptr->Fptr)->curhdu + 1] = 
+         (fptr->Fptr)->datastart +
+         ( (rowlen * nrows + pcount + 2879) / 2880 * 2880 );
+
+    /* determine the byte offset to the beginning of each column */
+    ffgtbc(fptr, &totalwidth, status);
+
+    if (totalwidth != rowlen)
+    {
+        sprintf(message,
+        "NAXIS1 = %ld is not equal to the sum of column widths: %ld", 
+        (long) rowlen, (long) totalwidth);
+        ffpmsg(message);
+        *status = BAD_ROW_WIDTH;
+    }
+
+    /* reset next keyword pointer to the start of the header */
+    (fptr->Fptr)->nextkey = (fptr->Fptr)->headstart[ (fptr->Fptr)->curhdu ];
+
+    if ( (fptr->Fptr)->compressimg == 1) /*  Is this a compressed image */
+        imcomp_get_compressed_image_par(fptr, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgabc(int tfields,     /* I - number of columns in the table           */
+           char **tform,    /* I - value of TFORMn keyword for each column  */
+           int space,       /* I - number of spaces to leave between cols   */
+           long *rowlen,    /* O - total width of a table row               */
+           long *tbcol,     /* O - starting byte in row for each column     */
+           int *status)     /* IO - error status                            */
+/*
+  calculate the starting byte offset of each column of an ASCII table
+  and the total length of a row, in bytes.  The input space value determines
+  how many blank spaces to leave between each column (1 is recommended).
+*/
+{
+    int ii, datacode, decims;
+    long width;
+
+    if (*status > 0)
+        return(*status);
+
+    *rowlen=0;
+
+    if (tfields <= 0)
+        return(*status);
+
+    tbcol[0] = 1;
+
+    for (ii = 0; ii < tfields; ii++)
+    {
+        tbcol[ii] = *rowlen + 1;    /* starting byte in row of column */
+
+        ffasfm(tform[ii], &datacode, &width, &decims, status);
+
+        *rowlen += (width + space);  /* total length of row */
+    }
+
+    *rowlen -= space;  /*  don't add space after the last field */
+
+    return (*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgtbc(fitsfile *fptr,    /* I - FITS file pointer          */
+           LONGLONG *totalwidth,  /* O - total width of a table row */
+           int *status)       /* IO - error status              */
+{
+/*
+  calculate the starting byte offset of each column of a binary table.
+  Use the values of the datatype code and repeat counts in the
+  column structure. Return the total length of a row, in bytes.
+*/
+    int tfields, ii;
+    LONGLONG nbytes;
+    tcolumn *colptr;
+    char message[FLEN_ERRMSG], *cptr;
+
+    if (*status > 0)
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+        if ( ffrdef(fptr, status) > 0)               /* rescan header */
+            return(*status);
+
+    tfields = (fptr->Fptr)->tfield;
+    colptr = (fptr->Fptr)->tableptr;  /* point to first column structure */
+
+    *totalwidth = 0;
+
+    for (ii = 0; ii < tfields; ii++, colptr++)
+    {
+        colptr->tbcol = *totalwidth;  /* byte offset in row to this column */
+
+        if (colptr->tdatatype == TSTRING)
+        {
+            nbytes =  colptr->trepeat;   /* one byte per char */
+        }
+        else if (colptr->tdatatype == TBIT)
+        {
+            nbytes = ( colptr->trepeat + 7) / 8;
+        }
+        else if (colptr->tdatatype > 0)
+        {
+            nbytes =  colptr->trepeat * (colptr->tdatatype / 10);
+        }
+        else  {
+	
+	  cptr = colptr->tform;
+	  while (isdigit(*cptr)) cptr++;
+	
+	  if (*cptr == 'P')  
+	   /* this is a 'P' variable length descriptor (neg. tdatatype) */
+            nbytes = colptr->trepeat * 8;
+	  else if (*cptr == 'Q') 
+	   /* this is a 'Q' variable length descriptor (neg. tdatatype) */
+            nbytes = colptr->trepeat * 16;
+
+	  else {
+		sprintf(message,
+		"unknown binary table column type: %s", colptr->tform);
+		ffpmsg(message);
+		*status = BAD_TFORM;
+		return(*status);
+	  }
+ 	}
+
+       *totalwidth = *totalwidth + nbytes;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgtbp(fitsfile *fptr,     /* I - FITS file pointer   */
+           char *name,         /* I - name of the keyword */
+           char *value,        /* I - value string of the keyword */
+           int *status)        /* IO - error status       */
+{
+/*
+  Get TaBle Parameter.  The input keyword name begins with the letter T.
+  Test if the keyword is one of the table column definition keywords
+  of an ASCII or binary table. If so, decode it and update the value 
+  in the structure.
+*/
+    int tstatus, datacode, decimals;
+    long width, repeat, nfield, ivalue;
+    LONGLONG jjvalue;
+    double dvalue;
+    char tvalue[FLEN_VALUE], *loc;
+    char message[FLEN_ERRMSG];
+    tcolumn *colptr;
+
+    if (*status > 0)
+        return(*status);
+
+    tstatus = 0;
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    if(!FSTRNCMP(name + 1, "TYPE", 4) )
+    {
+        /* get the index number */
+        if( ffc2ii(name + 5, &nfield, &tstatus) > 0) /* read index no. */
+            return(*status);    /* must not be an indexed keyword */
+
+        if (nfield < 1 || nfield > (fptr->Fptr)->tfield ) /* out of range */
+            return(*status);
+
+        colptr = (fptr->Fptr)->tableptr;        /* get pointer to columns */
+        colptr = colptr + nfield - 1;   /* point to the correct column */
+
+        if (ffc2s(value, tvalue, &tstatus) > 0)  /* remove quotes */
+            return(*status);
+
+        strcpy(colptr->ttype, tvalue);  /* copy col name to structure */
+    }
+    else if(!FSTRNCMP(name + 1, "FORM", 4) )
+    {
+        /* get the index number */
+        if( ffc2ii(name + 5, &nfield, &tstatus) > 0) /* read index no. */
+            return(*status);    /* must not be an indexed keyword */
+
+        if (nfield < 1 || nfield > (fptr->Fptr)->tfield )  /* out of range */
+            return(*status);
+
+        colptr = (fptr->Fptr)->tableptr;        /* get pointer to columns */
+        colptr = colptr + nfield - 1;   /* point to the correct column */
+
+        if (ffc2s(value, tvalue, &tstatus) > 0)  /* remove quotes */
+            return(*status);
+
+        strncpy(colptr->tform, tvalue, 9);  /* copy TFORM to structure */
+        colptr->tform[9] = '\0';            /* make sure it is terminated */
+
+        if ((fptr->Fptr)->hdutype == ASCII_TBL)  /* ASCII table */
+        {
+          if (ffasfm(tvalue, &datacode, &width, &decimals, status) > 0)
+              return(*status);  /* bad format code */
+
+          colptr->tdatatype = TSTRING; /* store datatype code */
+          colptr->trepeat = 1;      /* field repeat count == 1 */
+          colptr->twidth = width;   /* the width of the field, in bytes */
+        }
+        else  /* binary table */
+        {
+          if (ffbnfm(tvalue, &datacode, &repeat, &width, status) > 0)
+              return(*status);  /* bad format code */
+
+          colptr->tdatatype = datacode; /* store datatype code */
+          colptr->trepeat = (LONGLONG) repeat;     /* field repeat count  */
+
+          /* Don't overwrite the unit string width if it was previously */
+	  /* set by a TDIMn keyword and has a legal value */
+          if (datacode == TSTRING) {
+	    if (colptr->twidth == 0 || colptr->twidth > repeat)
+              colptr->twidth = width;   /*  width of a unit string */
+
+          } else {
+              colptr->twidth = width;   /*  width of a unit value in chars */
+          }
+        }
+    }
+    else if(!FSTRNCMP(name + 1, "BCOL", 4) )
+    {
+        /* get the index number */
+        if( ffc2ii(name + 5, &nfield, &tstatus) > 0) /* read index no. */
+            return(*status);    /* must not be an indexed keyword */
+
+        if (nfield < 1 || nfield > (fptr->Fptr)->tfield )  /* out of range */
+            return(*status);
+
+        colptr = (fptr->Fptr)->tableptr;        /* get pointer to columns */
+        colptr = colptr + nfield - 1;   /* point to the correct column */
+
+        if ((fptr->Fptr)->hdutype == BINARY_TBL)
+            return(*status);  /* binary tables don't have TBCOL keywords */
+
+        if (ffc2ii(value, &ivalue, status) > 0)
+        {
+            sprintf(message,
+            "Error reading value of %s as an integer: %s", name, value);
+            ffpmsg(message);
+            return(*status);
+        }
+        colptr->tbcol = ivalue - 1; /* convert to zero base */
+    }
+    else if(!FSTRNCMP(name + 1, "SCAL", 4) )
+    {
+        /* get the index number */
+        if( ffc2ii(name + 5, &nfield, &tstatus) > 0) /* read index no. */
+            return(*status);    /* must not be an indexed keyword */
+
+        if (nfield < 1 || nfield > (fptr->Fptr)->tfield )  /* out of range */
+            return(*status);
+
+        colptr = (fptr->Fptr)->tableptr;        /* get pointer to columns */
+        colptr = colptr + nfield - 1;   /* point to the correct column */
+
+        if (ffc2dd(value, &dvalue, &tstatus) > 0)
+        {
+            sprintf(message,
+            "Error reading value of %s as a double: %s", name, value);
+            ffpmsg(message);
+
+            /* ignore this error, so don't return error status */
+            return(*status);
+        }
+        colptr->tscale = dvalue;
+    }
+    else if(!FSTRNCMP(name + 1, "ZERO", 4) )
+    {
+        /* get the index number */
+        if( ffc2ii(name + 5, &nfield, &tstatus) > 0) /* read index no. */
+            return(*status);    /* must not be an indexed keyword */
+
+        if (nfield < 1 || nfield > (fptr->Fptr)->tfield )  /* out of range */
+            return(*status);
+
+        colptr = (fptr->Fptr)->tableptr;        /* get pointer to columns */
+        colptr = colptr + nfield - 1;   /* point to the correct column */
+
+        if (ffc2dd(value, &dvalue, &tstatus) > 0)
+        {
+            sprintf(message,
+            "Error reading value of %s as a double: %s", name, value);
+            ffpmsg(message);
+
+            /* ignore this error, so don't return error status */
+            return(*status);
+        }
+        colptr->tzero = dvalue;
+    }
+    else if(!FSTRNCMP(name + 1, "NULL", 4) )
+    {
+        /* get the index number */
+        if( ffc2ii(name + 5, &nfield, &tstatus) > 0) /* read index no. */
+            return(*status);    /* must not be an indexed keyword */
+
+        if (nfield < 1 || nfield > (fptr->Fptr)->tfield )  /* out of range */
+            return(*status);
+
+        colptr = (fptr->Fptr)->tableptr;        /* get pointer to columns */
+        colptr = colptr + nfield - 1;   /* point to the correct column */
+
+        if ((fptr->Fptr)->hdutype == ASCII_TBL)  /* ASCII table */
+        {
+            if (ffc2s(value, tvalue, &tstatus) > 0)  /* remove quotes */
+                return(*status);
+
+            strncpy(colptr->strnull, tvalue, 17);  /* copy TNULL string */
+            colptr->strnull[17] = '\0';  /* terminate the strnull field */
+
+        }
+        else  /* binary table */
+        {
+            if (ffc2jj(value, &jjvalue, &tstatus) > 0) 
+            {
+                sprintf(message,
+                "Error reading value of %s as an integer: %s", name, value);
+                ffpmsg(message);
+
+                /* ignore this error, so don't return error status */
+                return(*status);
+            }
+            colptr->tnull = jjvalue; /* null value for integer column */
+        }
+    }
+    else if(!FSTRNCMP(name + 1, "DIM", 3) )
+    {
+        if ((fptr->Fptr)->hdutype == ASCII_TBL)  /* ASCII table */
+            return(*status);  /* ASCII tables don't support TDIMn keyword */ 
+
+        /* get the index number */
+        if( ffc2ii(name + 4, &nfield, &tstatus) > 0) /* read index no. */
+            return(*status);    /* must not be an indexed keyword */
+
+        if (nfield < 1 || nfield > (fptr->Fptr)->tfield )  /* out of range */
+            return(*status);
+
+        colptr = (fptr->Fptr)->tableptr;     /* get pointer to columns */
+        colptr = colptr + nfield - 1;   /* point to the correct column */
+
+        /* uninitialized columns have tdatatype set = -9999 */
+        if (colptr->tdatatype != -9999 && colptr->tdatatype != TSTRING)
+	    return(*status);     /* this is not an ASCII string column */
+	   
+        loc = strchr(value, '(' );  /* find the opening parenthesis */
+        if (!loc)
+            return(*status);   /* not a proper TDIM keyword */
+
+        loc++;
+        width = strtol(loc, &loc, 10);  /* read size of first dimension */
+        if (colptr->trepeat != 1 && colptr->trepeat < width)
+	    return(*status);  /* string length is greater than column width */
+
+        colptr->twidth = width;   /* set width of a unit string in chars */
+    }
+    else if (!FSTRNCMP(name + 1, "HEAP", 4) )
+    {
+        if ((fptr->Fptr)->hdutype == ASCII_TBL)  /* ASCII table */
+            return(*status);  /* ASCII tables don't have a heap */ 
+
+        if (ffc2jj(value, &jjvalue, &tstatus) > 0) 
+        {
+            sprintf(message,
+            "Error reading value of %s as an integer: %s", name, value);
+            ffpmsg(message);
+
+            /* ignore this error, so don't return error status */
+            return(*status);
+        }
+        (fptr->Fptr)->heapstart = jjvalue; /* starting byte of the heap */
+        return(*status);
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcprll( fitsfile *fptr, /* I - FITS file pointer                      */
+        int colnum,     /* I - column number (1 = 1st column of table)      */
+        LONGLONG firstrow,  /* I - first row (1 = 1st row of table)         */
+        LONGLONG firstelem, /* I - first element within vector (1 = 1st)    */
+        LONGLONG nelem, /* I - number of elements to read or write          */
+        int writemode,  /* I - = 1 if writing data, = 0 if reading data     */
+                        /*     If = 2, then writing data, but don't modify  */
+                        /*     the returned values of repeat and incre.     */
+                        /*     If = -1, then reading data in reverse        */
+                        /*     direction.                                   */
+        double *scale,  /* O - FITS scaling factor (TSCALn keyword value)   */
+        double *zero,   /* O - FITS scaling zero pt (TZEROn keyword value)  */
+        char *tform,    /* O - ASCII column format: value of TFORMn keyword */
+        long *twidth,   /* O - width of ASCII column (characters)           */
+        int *tcode,     /* O - column datatype code: I*4=41, R*4=42, etc    */
+        int *maxelem,   /* O - max number of elements that fit in buffer    */
+        LONGLONG *startpos,/* O - offset in file to starting row & column      */
+        LONGLONG *elemnum, /* O - starting element number ( 0 = 1st element)   */
+        long *incre,    /* O - byte offset between elements within a row    */
+        LONGLONG *repeat,  /* O - number of elements in a row (vector column)  */
+        LONGLONG *rowlen,  /* O - length of a row, in bytes                    */
+        int  *hdutype,  /* O - HDU type: 0, 1, 2 = primary, table, bintable */
+        LONGLONG *tnull,    /* O - null value for integer columns               */
+        char *snull,    /* O - null value for ASCII table columns           */
+        int *status)    /* IO - error status                                */
+/*
+  Get Column PaRameters, and test starting row and element numbers for 
+  validity.  This is a workhorse routine that is call by nearly every
+  other routine that reads or writes to FITS files.
+*/
+{
+    int nulpos, rangecheck = 1, tstatus = 0;
+    LONGLONG datastart, endpos;
+    long nblock;
+    LONGLONG heapoffset, lrepeat, endrow, nrows, tbcol;
+    char message[81];
+    tcolumn *colptr;
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu) {
+        /* reset position to the correct HDU if necessary */
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    } else if ((fptr->Fptr)->datastart == DATA_UNDEFINED) {
+        /* rescan header if data structure is undefined */
+        if ( ffrdef(fptr, status) > 0)               
+            return(*status);
+
+    } else if (writemode > 0) {
+
+	/* Only terminate the header with the END card if */
+	/* writing to the stdout stream (don't have random access). */
+
+	/* Initialize STREAM_DRIVER to be the device number for */
+	/* writing FITS files directly out to the stdout stream. */
+	/* This only needs to be done once and is thread safe. */
+	if (STREAM_DRIVER <= 0 || STREAM_DRIVER > 40) {
+            urltype2driver("stream://", &STREAM_DRIVER);
+        }
+
+        if (((fptr->Fptr)->driver == STREAM_DRIVER)) {
+	    if ((fptr->Fptr)->ENDpos != 
+	       maxvalue((fptr->Fptr)->headend , (fptr->Fptr)->datastart -2880)) {
+	           ffwend(fptr, status);
+	    } 
+	}
+    }
+
+    /* Do sanity check of input parameters */
+    if (firstrow < 1)
+    {
+        if ((fptr->Fptr)->hdutype == IMAGE_HDU) /*  Primary Array or IMAGE */
+        {
+          sprintf(message, "Image group number is less than 1: %.0f",
+                (double) firstrow);
+          ffpmsg(message);
+          return(*status = BAD_ROW_NUM);
+        }
+        else
+        {
+          sprintf(message, "Starting row number is less than 1: %.0f",
+                (double) firstrow);
+          ffpmsg(message);
+          return(*status = BAD_ROW_NUM);
+        }
+    }
+    else if ((fptr->Fptr)->hdutype != ASCII_TBL && firstelem < 1)
+    {
+        sprintf(message, "Starting element number less than 1: %ld",
+               (long) firstelem);
+        ffpmsg(message);
+        return(*status = BAD_ELEM_NUM);
+    }
+    else if (nelem < 0)
+    {
+        sprintf(message, "Tried to read or write less than 0 elements: %.0f",
+            (double) nelem);
+        ffpmsg(message);
+        return(*status = NEG_BYTES);
+    }
+    else if (colnum < 1 || colnum > (fptr->Fptr)->tfield)
+    {
+        sprintf(message, "Specified column number is out of range: %d",
+                colnum);
+        ffpmsg(message);
+        sprintf(message, "  There are %d columns in this table.",
+                (fptr->Fptr)->tfield );
+        ffpmsg(message);
+
+        return(*status = BAD_COL_NUM);
+    }
+
+    /*  copy relevant parameters from the structure */
+
+    *hdutype = (fptr->Fptr)->hdutype;    /* image, ASCII table, or BINTABLE  */
+    *rowlen   = (fptr->Fptr)->rowlength; /* width of the table, in bytes     */
+    datastart = (fptr->Fptr)->datastart; /* offset in file to start of table */
+
+    colptr  = (fptr->Fptr)->tableptr;    /* point to first column */
+    colptr += (colnum - 1);      /* offset to correct column structure */
+
+    *scale    = colptr->tscale;  /* value scaling factor;    default = 1.0 */
+    *zero     = colptr->tzero;   /* value scaling zeropoint; default = 0.0 */
+    *tnull    = colptr->tnull;   /* null value for integer columns         */
+    tbcol     = colptr->tbcol;   /* offset to start of column within row   */
+    *twidth   = colptr->twidth;  /* width of a single datum, in bytes      */
+    *incre    = colptr->twidth;  /* increment between datums, in bytes     */
+
+    *tcode    = colptr->tdatatype;
+    *repeat   = colptr->trepeat;
+
+    strcpy(tform, colptr->tform);    /* value of TFORMn keyword            */
+    strcpy(snull, colptr->strnull);  /* null value for ASCII table columns */
+
+    if (*hdutype == ASCII_TBL && snull[0] == '\0')
+    {
+     /* In ASCII tables, a null value is equivalent to all spaces */
+
+       strcpy(snull, "                 ");   /* maximum of 17 spaces */
+       nulpos = minvalue(17, *twidth);      /* truncate to width of column */
+       snull[nulpos] = '\0';
+    }
+
+    /* Special case:  interpret writemode = -1 as reading data, but */
+    /* don't do error check for exceeding the range of pixels  */
+    if (writemode == -1)
+    {
+      writemode = 0;
+      rangecheck = 0;
+    }
+
+    /* Special case: interprete 'X' column as 'B' */
+    if (abs(*tcode) == TBIT)
+    {
+        *tcode  = *tcode / TBIT * TBYTE;
+        *repeat = (*repeat + 7) / 8;
+    }
+
+    /* Special case: support the 'rAw' format in BINTABLEs */
+    if (*hdutype == BINARY_TBL && *tcode == TSTRING) {
+       *repeat = *repeat / *twidth;  /* repeat = # of unit strings in field */
+    }
+    else if (*hdutype == BINARY_TBL && *tcode == -TSTRING) {
+       /* variable length string */
+       *incre = 1;
+       *twidth = (long) nelem;
+    }
+
+    if (*hdutype == ASCII_TBL)
+        *elemnum = 0;   /* ASCII tables don't have vector elements */
+    else
+        *elemnum = firstelem - 1;
+
+    /* interprete complex and double complex as pairs of floats or doubles */
+    if (abs(*tcode) >= TCOMPLEX)
+    {
+        if (*tcode > 0)
+          *tcode = (*tcode + 1) / 2;
+        else
+          *tcode = (*tcode - 1) / 2;
+
+        *repeat  = *repeat * 2;
+        *twidth  = *twidth / 2;
+        *incre   = *incre  / 2;
+    }
+
+    /* calculate no. of pixels that fit in buffer */
+    /* allow for case where floats are 8 bytes long */
+    if (abs(*tcode) == TFLOAT)
+       *maxelem = DBUFFSIZE / sizeof(float);
+    else if (abs(*tcode) == TDOUBLE)
+       *maxelem = DBUFFSIZE / sizeof(double);
+    else if (abs(*tcode) == TSTRING)
+    {
+       *maxelem = (DBUFFSIZE - 1)/ *twidth; /* leave room for final \0 */
+       if (*maxelem == 0) {
+            sprintf(message,
+        "ASCII string column is too wide: %ld; max supported width is %d",
+                   *twidth,  DBUFFSIZE - 1);
+            ffpmsg(message);
+            return(*status = COL_TOO_WIDE);
+        }
+    }
+    else
+       *maxelem = DBUFFSIZE / *twidth; 
+
+    /* calc starting byte position to 1st element of col  */
+    /*  (this does not apply to variable length columns)  */
+    *startpos = datastart + ((LONGLONG)(firstrow - 1) * *rowlen) + tbcol;
+
+    if (*hdutype == IMAGE_HDU && writemode) /*  Primary Array or IMAGE */
+    { /*
+        For primary arrays, set the repeat count greater than the total
+        number of pixels to be written.  This prevents an out-of-range
+        error message in cases where the final image array size is not
+        yet known or defined.
+      */
+        if (*repeat < *elemnum + nelem)
+            *repeat = *elemnum + nelem; 
+    }
+    else if (*tcode > 0)     /*  Fixed length table column  */
+    {
+        if (*elemnum >= *repeat)
+        {
+            sprintf(message,
+        "First element to write is too large: %ld; max allowed value is %ld",
+                   (long) ((*elemnum) + 1), (long) *repeat);
+            ffpmsg(message);
+            return(*status = BAD_ELEM_NUM);
+        }
+
+        /* last row number to be read or written */
+        endrow = ((*elemnum + nelem - 1) / *repeat) + firstrow;
+
+        if (writemode)
+        {
+            /* check if we are writing beyond the current end of table */
+            if ((endrow > (fptr->Fptr)->numrows) && (nelem > 0) )
+            {
+                /* if there are more HDUs following the current one, or */
+                /* if there is a data heap, then we must insert space */
+                /* for the new rows.  */
+                if ( !((fptr->Fptr)->lasthdu) || (fptr->Fptr)->heapsize > 0)
+                {
+                    nrows = endrow - ((fptr->Fptr)->numrows);
+                    if (ffirow(fptr, (fptr->Fptr)->numrows, nrows, status) > 0)
+                    {
+                       sprintf(message,
+                       "Failed to add space for %.0f new rows in table.",
+                       (double) nrows);
+                       ffpmsg(message);
+                       return(*status);
+                    }
+                }
+                else
+                {
+                  /* update heap starting address */
+                  (fptr->Fptr)->heapstart += 
+                  ((LONGLONG)(endrow - (fptr->Fptr)->numrows) * 
+                          (fptr->Fptr)->rowlength );
+
+                  (fptr->Fptr)->numrows = endrow; /* update number of rows */
+                }
+            }
+        }
+        else  /* reading from the file */
+        {
+          if ( endrow > (fptr->Fptr)->numrows && rangecheck)
+          {
+            if (*hdutype == IMAGE_HDU) /*  Primary Array or IMAGE */
+            {
+              if (firstrow > (fptr->Fptr)->numrows)
+              {
+                sprintf(message, 
+                  "Attempted to read from group %ld of the HDU,", (long) firstrow);
+                ffpmsg(message);
+
+                sprintf(message, 
+                  "however the HDU only contains %ld group(s).",
+                   (long) ((fptr->Fptr)->numrows) );
+                ffpmsg(message);
+              }
+              else
+              {
+                ffpmsg("Attempt to read past end of array:");
+                sprintf(message, 
+                  "  Image has  %ld elements;", (long) *repeat);
+                ffpmsg(message);
+
+                sprintf(message, 
+                "  Tried to read %ld elements starting at element %ld.",
+                (long) nelem, (long) firstelem);
+                ffpmsg(message);
+              }
+            }
+            else
+            {
+              ffpmsg("Attempt to read past end of table:");
+              sprintf(message, 
+                "  Table has %.0f rows with %.0f elements per row;",
+                    (double) ((fptr->Fptr)->numrows), (double) *repeat);
+              ffpmsg(message);
+
+              sprintf(message, 
+              "  Tried to read %.0f elements starting at row %.0f, element %.0f.",
+              (double) nelem, (double) firstrow, (double) ((*elemnum) + 1));
+              ffpmsg(message);
+
+            }
+            return(*status = BAD_ROW_NUM);
+          }
+        }
+
+        if (*repeat == 1 && nelem > 1 && writemode != 2)
+        { /*
+            When accessing a scalar column, fool the calling routine into
+            thinking that this is a vector column with very big elements.
+            This allows multiple values (up to the maxelem number of elements
+            that will fit in the buffer) to be read or written with a single
+            routine call, which increases the efficiency.
+
+            If writemode == 2, then the calling program does not want to
+            have this efficiency trick applied.
+          */           
+            *incre = (long) *rowlen;
+            *repeat = nelem;
+        }
+    }
+    else    /*  Variable length Binary Table column */
+    {
+      *tcode *= (-1);  
+
+      if (writemode)    /* return next empty heap address for writing */
+      {
+
+        *repeat = nelem + *elemnum; /* total no. of elements in the field */
+
+        /* first, check if we are overwriting an existing row, and */
+        /* if so, if the existing space is big enough for the new vector */
+
+        if ( firstrow <= (fptr->Fptr)->numrows )
+        {
+          ffgdesll(fptr, colnum, firstrow, &lrepeat, &heapoffset, &tstatus);
+          if (!tstatus)
+          {
+            if (colptr->tdatatype <= -TCOMPLEX)
+              lrepeat = lrepeat * 2;  /* no. of float or double values */
+            else if (colptr->tdatatype == -TBIT)
+              lrepeat = (lrepeat + 7) / 8;  /* convert from bits to bytes */
+
+            if (lrepeat >= *repeat)  /* enough existing space? */
+            {
+              *startpos = datastart + heapoffset + (fptr->Fptr)->heapstart;
+
+              /*  write the descriptor into the fixed length part of table */
+              if (colptr->tdatatype <= -TCOMPLEX)
+              {
+                /* divide repeat count by 2 to get no. of complex values */
+                ffpdes(fptr, colnum, firstrow, *repeat / 2, 
+                      heapoffset, status);
+              }
+              else
+              {
+                ffpdes(fptr, colnum, firstrow, *repeat,
+                      heapoffset, status);
+              }
+              return(*status);
+            }
+          }
+        }
+
+        /* Add more rows to the table, if writing beyond the end. */
+        /* It is necessary to shift the heap down in this case */
+        if ( firstrow > (fptr->Fptr)->numrows)
+        {
+            nrows = firstrow - ((fptr->Fptr)->numrows);
+            if (ffirow(fptr, (fptr->Fptr)->numrows, nrows, status) > 0)
+            {
+                sprintf(message,
+                "Failed to add space for %.0f new rows in table.",
+                       (double) nrows);
+                ffpmsg(message);
+                return(*status);
+            }
+        }
+
+        /*  calculate starting position (for writing new data) in the heap */
+        *startpos = datastart + (fptr->Fptr)->heapstart + 
+                    (fptr->Fptr)->heapsize;
+
+        /*  write the descriptor into the fixed length part of table */
+        if (colptr->tdatatype <= -TCOMPLEX)
+        {
+          /* divide repeat count by 2 to get no. of complex values */
+          ffpdes(fptr, colnum, firstrow, *repeat / 2, 
+                (fptr->Fptr)->heapsize, status);
+        }
+        else
+        {
+          ffpdes(fptr, colnum, firstrow, *repeat, (fptr->Fptr)->heapsize,
+                 status);
+        }
+
+        /* If this is not the last HDU in the file, then check if */
+        /* extending the heap would overwrite the following header. */
+        /* If so, then have to insert more blocks. */
+        if ( !((fptr->Fptr)->lasthdu) )
+        {
+            endpos = datastart + (fptr->Fptr)->heapstart + 
+                     (fptr->Fptr)->heapsize + ( *repeat * (*incre));
+
+            if (endpos > (fptr->Fptr)->headstart[ (fptr->Fptr)->curhdu + 1])
+            {
+                /* calc the number of blocks that need to be added */
+                nblock = (long) (((endpos - 1 - 
+                         (fptr->Fptr)->headstart[ (fptr->Fptr)->curhdu + 1] ) 
+                         / 2880) + 1);
+
+                if (ffiblk(fptr, nblock, 1, status) > 0) /* insert blocks */
+                {
+                  sprintf(message,
+       "Failed to extend the size of the variable length heap by %ld blocks.",
+                   nblock);
+                   ffpmsg(message);
+                   return(*status);
+                }
+            }
+        }
+
+        /* increment the address to the next empty heap position */
+        (fptr->Fptr)->heapsize += ( *repeat * (*incre)); 
+      }
+      else    /*  get the read start position in the heap */
+      {
+        if ( firstrow > (fptr->Fptr)->numrows)
+        {
+            ffpmsg("Attempt to read past end of table");
+            sprintf(message, 
+                "  Table has %.0f rows and tried to read row %.0f.",
+                (double) ((fptr->Fptr)->numrows), (double) firstrow);
+            ffpmsg(message);
+            return(*status = BAD_ROW_NUM);
+        }
+
+        ffgdesll(fptr, colnum, firstrow, &lrepeat, &heapoffset, status);
+        *repeat = lrepeat;
+
+        if (colptr->tdatatype <= -TCOMPLEX)
+            *repeat = *repeat * 2;  /* no. of float or double values */
+        else if (colptr->tdatatype == -TBIT)
+            *repeat = (*repeat + 7) / 8;  /* convert from bits to bytes */
+
+        if (*elemnum >= *repeat)
+        {
+            sprintf(message, 
+         "Starting element to read in variable length column is too large: %ld",
+                    (long) firstelem);
+            ffpmsg(message);
+            sprintf(message, 
+         "  This row only contains %ld elements", (long) *repeat);
+            ffpmsg(message);
+            return(*status = BAD_ELEM_NUM);
+        }
+
+        *startpos = datastart + heapoffset + (fptr->Fptr)->heapstart;
+      }
+    }
+    return(*status);
+}
+/*---------------------------------------------------------------------------*/
+int fftheap(fitsfile *fptr, /* I - FITS file pointer                         */
+           LONGLONG *heapsz,   /* O - current size of the heap               */
+           LONGLONG *unused,   /* O - no. of unused bytes in the heap        */
+           LONGLONG *overlap,  /* O - no. of bytes shared by > 1 descriptors */
+           int  *valid,     /* O - are all the heap addresses valid?         */
+           int *status)     /* IO - error status                             */
+/*
+  Tests the contents of the binary table variable length array heap.
+  Returns the number of bytes that are currently not pointed to by any
+  of the descriptors, and also the number of bytes that are pointed to
+  by more than one descriptor.  It returns valid = FALSE if any of the
+  descriptors point to addresses that are out of the bounds of the
+  heap.
+*/
+{
+    int jj, typecode, pixsize;
+    long ii, kk, theapsz, nbytes;
+    LONGLONG repeat, offset, tunused = 0, toverlap = 0;
+    char *buffer, message[81];
+
+    if (*status > 0)
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if ( fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    /* rescan header to make sure everything is up to date */
+    else if ( ffrdef(fptr, status) > 0)               
+        return(*status);
+
+    if (valid) *valid = TRUE;
+    if (heapsz) *heapsz = (fptr->Fptr)->heapsize;
+    if (unused) *unused = 0;
+    if (overlap) *overlap = 0;
+    
+    /* return if this is not a binary table HDU or if the heap is empty */
+    if ( (fptr->Fptr)->hdutype != BINARY_TBL || (fptr->Fptr)->heapsize == 0 )
+        return(*status);
+
+    if ((fptr->Fptr)->heapsize > LONG_MAX) {
+        ffpmsg("Heap is too big to test ( > 2**31 bytes). (fftheap)");
+        return(*status = MEMORY_ALLOCATION);
+    }
+
+    theapsz = (long) (fptr->Fptr)->heapsize;
+    buffer = calloc(1, theapsz);     /* allocate temp space */
+    if (!buffer )
+    {
+        sprintf(message,"Failed to allocate buffer to test the heap");
+        ffpmsg(message);
+        return(*status = MEMORY_ALLOCATION);
+    }
+
+    /* loop over all cols */
+    for (jj = 1; jj <= (fptr->Fptr)->tfield && *status <= 0; jj++)
+    {
+        ffgtcl(fptr, jj, &typecode, NULL, NULL, status);
+        if (typecode > 0)
+           continue;        /* ignore fixed length columns */
+
+        pixsize = -typecode / 10;
+
+        for (ii = 1; ii <= (fptr->Fptr)->numrows; ii++)
+        {
+            ffgdesll(fptr, jj, ii, &repeat, &offset, status);
+            if (typecode == -TBIT)
+                nbytes = (long) (repeat + 7) / 8;
+            else
+                nbytes = (long) repeat * pixsize;
+
+            if (offset < 0 || offset + nbytes > theapsz)
+            {
+                if (valid) *valid = FALSE;  /* address out of bounds */
+                sprintf(message,
+                "Descriptor in row %ld, column %d has invalid heap address",
+                ii, jj);
+                ffpmsg(message);
+            }
+            else
+            {
+                for (kk = 0; kk < nbytes; kk++)
+                    buffer[kk + offset]++;   /* increment every used byte */
+            }
+        }
+    }
+
+    for (kk = 0; kk < theapsz; kk++)
+    {
+        if (buffer[kk] == 0)
+            tunused++;
+        else if (buffer[kk] > 1)
+            toverlap++;
+    }
+
+    if (heapsz) *heapsz = theapsz;
+    if (unused) *unused = tunused;
+    if (overlap) *overlap = toverlap;
+
+    free(buffer);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffcmph(fitsfile *fptr,  /* I -FITS file pointer                         */
+           int *status)     /* IO - error status                            */
+/*
+  compress the binary table heap by reordering the contents heap and
+  recovering any unused space
+*/
+{
+    fitsfile *tptr;
+    int jj, typecode, pixsize, valid;
+    long ii, buffsize = 10000, nblock, nbytes;
+    LONGLONG  unused, overlap;
+    LONGLONG repeat, offset;
+    char *buffer, *tbuff, comm[FLEN_COMMENT];
+    char message[81];
+    LONGLONG pcount;
+    LONGLONG readheapstart, writeheapstart, endpos, t1heapsize, t2heapsize;
+
+    if (*status > 0)
+        return(*status);
+
+    /* get information about the current heap */
+    fftheap(fptr, NULL, &unused, &overlap, &valid, status);
+
+    if (!valid)
+       return(*status = BAD_HEAP_PTR);  /* bad heap pointers */
+
+    /* return if this is not a binary table HDU or if the heap is OK as is */
+    if ( (fptr->Fptr)->hdutype != BINARY_TBL || (fptr->Fptr)->heapsize == 0 ||
+         (unused == 0 && overlap == 0) || *status > 0 )
+        return(*status);
+
+    /* copy the current HDU to a temporary file in memory */
+    if (ffinit( &tptr, "mem://tempheapfile", status) )
+    {
+        sprintf(message,"Failed to create temporary file for the heap");
+        ffpmsg(message);
+        return(*status);
+    }
+    if ( ffcopy(fptr, tptr, 0, status) )
+    {
+        sprintf(message,"Failed to create copy of the heap");
+        ffpmsg(message);
+        ffclos(tptr, status);
+        return(*status);
+    }
+
+    buffer = (char *) malloc(buffsize);  /* allocate initial buffer */
+    if (!buffer)
+    {
+        sprintf(message,"Failed to allocate buffer to copy the heap");
+        ffpmsg(message);
+        ffclos(tptr, status);
+        return(*status = MEMORY_ALLOCATION);
+    }
+    
+    readheapstart  = (tptr->Fptr)->datastart + (tptr->Fptr)->heapstart;
+    writeheapstart = (fptr->Fptr)->datastart + (fptr->Fptr)->heapstart;
+
+    t1heapsize = (fptr->Fptr)->heapsize;  /* save original heap size */
+    (fptr->Fptr)->heapsize = 0;  /* reset heap to zero */
+
+    /* loop over all cols */
+    for (jj = 1; jj <= (fptr->Fptr)->tfield && *status <= 0; jj++)
+    {
+        ffgtcl(tptr, jj, &typecode, NULL, NULL, status);
+        if (typecode > 0)
+           continue;        /* ignore fixed length columns */
+
+        pixsize = -typecode / 10;
+
+        /* copy heap data, row by row */
+        for (ii = 1; ii <= (fptr->Fptr)->numrows; ii++)
+        {
+            ffgdesll(tptr, jj, ii, &repeat, &offset, status);
+            if (typecode == -TBIT)
+                nbytes = (long) (repeat + 7) / 8;
+            else
+                nbytes = (long) repeat * pixsize;
+
+            /* increase size of buffer if necessary to read whole array */
+            if (nbytes > buffsize)
+            {
+                tbuff = realloc(buffer, nbytes);
+
+                if (tbuff)
+                {
+                    buffer = tbuff;
+                    buffsize = nbytes;
+                }
+                else
+                    *status = MEMORY_ALLOCATION;
+            }
+
+            /* If this is not the last HDU in the file, then check if */
+            /* extending the heap would overwrite the following header. */
+            /* If so, then have to insert more blocks. */
+            if ( !((fptr->Fptr)->lasthdu) )
+            {
+              endpos = writeheapstart + (fptr->Fptr)->heapsize + nbytes;
+
+              if (endpos > (fptr->Fptr)->headstart[ (fptr->Fptr)->curhdu + 1])
+              {
+                /* calc the number of blocks that need to be added */
+                nblock = (long) (((endpos - 1 - 
+                         (fptr->Fptr)->headstart[ (fptr->Fptr)->curhdu + 1] ) 
+                         / 2880) + 1);
+
+                if (ffiblk(fptr, nblock, 1, status) > 0) /* insert blocks */
+                {
+                  sprintf(message,
+       "Failed to extend the size of the variable length heap by %ld blocks.",
+                   nblock);
+                   ffpmsg(message);
+                }
+              }
+            }
+
+            /* read arrray of bytes from temporary copy */
+            ffmbyt(tptr, readheapstart + offset, REPORT_EOF, status);
+            ffgbyt(tptr, nbytes, buffer, status);
+
+            /* write arrray of bytes back to original file */
+            ffmbyt(fptr, writeheapstart + (fptr->Fptr)->heapsize, 
+                    IGNORE_EOF, status);
+            ffpbyt(fptr, nbytes, buffer, status);
+
+            /* write descriptor */
+            ffpdes(fptr, jj, ii, repeat, 
+                   (fptr->Fptr)->heapsize, status);
+
+            (fptr->Fptr)->heapsize += nbytes; /* update heapsize */
+
+            if (*status > 0)
+            {
+               free(buffer);
+               ffclos(tptr, status);
+               return(*status);
+            }
+        }
+    }
+
+    free(buffer);
+    ffclos(tptr, status);
+
+    /* delete any empty blocks at the end of the HDU */
+    nblock = (long) (( (fptr->Fptr)->headstart[ (fptr->Fptr)->curhdu + 1] -
+             (writeheapstart + (fptr->Fptr)->heapsize) ) / 2880);
+
+    if (nblock > 0)
+    {
+       t2heapsize = (fptr->Fptr)->heapsize;  /* save new heap size */
+       (fptr->Fptr)->heapsize = t1heapsize;  /* restore  original heap size */
+
+       ffdblk(fptr, nblock, status);
+       (fptr->Fptr)->heapsize = t2heapsize;  /* reset correct heap size */
+    }
+
+    /* update the PCOUNT value (size of heap) */
+    ffmaky(fptr, 2, status);         /* reset to beginning of header */
+
+    ffgkyjj(fptr, "PCOUNT", &pcount, comm, status);
+    if ((fptr->Fptr)->heapsize != pcount)
+    {
+        ffmkyj(fptr, "PCOUNT", (fptr->Fptr)->heapsize, comm, status);
+    }
+    ffrdef(fptr, status);  /* rescan new HDU structure */
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgdes(fitsfile *fptr, /* I - FITS file pointer                         */
+           int colnum,     /* I - column number (1 = 1st column of table)   */
+           LONGLONG rownum,    /* I - row number (1 = 1st row of table)         */
+           long *length,   /* O - number of elements in the row             */
+           long *heapaddr, /* O - heap pointer to the data                  */
+           int *status)    /* IO - error status                             */
+/*
+  get (read) the variable length vector descriptor from the table.
+*/
+{
+    LONGLONG lengthjj, heapaddrjj;
+    
+    if (ffgdesll(fptr, colnum, rownum, &lengthjj, &heapaddrjj, status) > 0)
+        return(*status);
+
+    /* convert the temporary 8-byte values to 4-byte values */
+    /* check for overflow */
+    if (length) {
+        if (lengthjj > LONG_MAX)
+	    *status = NUM_OVERFLOW;
+	else
+            *length = (long) lengthjj;
+    }
+    
+    if (heapaddr) {
+        if (heapaddrjj > LONG_MAX)
+	    *status = NUM_OVERFLOW;
+	else
+            *heapaddr = (long) heapaddrjj;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgdesll(fitsfile *fptr,   /* I - FITS file pointer                       */
+           int colnum,         /* I - column number (1 = 1st column of table) */
+           LONGLONG rownum,        /* I - row number (1 = 1st row of table)       */
+           LONGLONG *length,   /* O - number of elements in the row           */
+           LONGLONG *heapaddr, /* O - heap pointer to the data                */
+           int *status)        /* IO - error status                           */
+/*
+  get (read) the variable length vector descriptor from the binary table.
+  This is similar to ffgdes, except it supports the full 8-byte range of the
+  length and offset values in 'Q' columns, as well as 'P' columns.
+*/
+{
+    LONGLONG bytepos;
+    unsigned int descript4[2] = {0,0};
+    LONGLONG descript8[2] = {0,0};
+    tcolumn *colptr;
+
+    if (*status > 0)
+       return(*status);
+       
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+        if ( ffrdef(fptr, status) > 0)               /* rescan header */
+            return(*status);
+
+    colptr = (fptr->Fptr)->tableptr;  /* point to first column structure */
+    colptr += (colnum - 1);   /* offset to the correct column */
+
+    if (colptr->tdatatype >= 0) {
+        *status = NOT_VARI_LEN;
+        return(*status);
+    }
+
+    bytepos = (fptr->Fptr)->datastart + 
+                  ((fptr->Fptr)->rowlength * (rownum - 1)) +
+                   colptr->tbcol;
+
+    if (colptr->tform[0] == 'P' || colptr->tform[1] == 'P')
+    {
+        /* read 4-byte descriptor */
+        if (ffgi4b(fptr, bytepos, 2, 4, (INT32BIT *) descript4, status) <= 0) 
+        {
+           if (length)
+             *length = (LONGLONG) descript4[0];   /* 1st word is the length  */
+           if (heapaddr)
+             *heapaddr = (LONGLONG) descript4[1]; /* 2nd word is the address */
+        }
+
+    }
+    else  /* this is for 'Q' columns */
+    {
+        /* read 8 byte descriptor */
+        if (ffgi8b(fptr, bytepos, 2, 8, (long *) descript8, status) <= 0) 
+        {
+           if (length)
+             *length = descript8[0];   /* 1st word is the length  */
+           if (heapaddr)
+             *heapaddr = descript8[1]; /* 2nd word is the address */
+        }
+    }     
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgdess(fitsfile *fptr, /* I - FITS file pointer                        */
+           int colnum,     /* I - column number (1 = 1st column of table)   */
+           LONGLONG firstrow,  /* I - first row  (1 = 1st row of table)         */
+           LONGLONG nrows,     /* I - number or rows to read                    */
+           long *length,   /* O - number of elements in the row             */
+           long *heapaddr, /* O - heap pointer to the data                  */
+           int *status)    /* IO - error status                             */
+/*
+  get (read) a range of variable length vector descriptors from the table.
+*/
+{
+    LONGLONG rowsize, bytepos;
+    long  ii;
+    INT32BIT descript4[2] = {0,0};
+    LONGLONG descript8[2] = {0,0};
+    tcolumn *colptr;
+
+    if (*status > 0)
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+        if ( ffrdef(fptr, status) > 0)               /* rescan header */
+            return(*status);
+
+    colptr = (fptr->Fptr)->tableptr;  /* point to first column structure */
+    colptr += (colnum - 1);   /* offset to the correct column */
+
+    if (colptr->tdatatype >= 0) {
+        *status = NOT_VARI_LEN;
+        return(*status);
+    }
+    
+    rowsize = (fptr->Fptr)->rowlength;
+    bytepos = (fptr->Fptr)->datastart + 
+                  (rowsize  * (firstrow - 1)) +
+                  colptr->tbcol;
+
+    if (colptr->tform[0] == 'P' || colptr->tform[1] == 'P')
+    {
+        /* read 4-byte descriptors */
+        for (ii = 0; ii < nrows; ii++)
+        {
+	    /* read descriptors */
+            if (ffgi4b(fptr, bytepos, 2, 4, descript4, status) <= 0)
+	    { 
+              if (length) {
+                *length =   (long) descript4[0];   /* 1st word is the length  */
+                length++;
+	      }
+
+              if (heapaddr) {
+                *heapaddr = (long) descript4[1];   /* 2nd word is the address */
+                heapaddr++;
+	      }
+              bytepos += rowsize;
+	    }
+	    else
+	      return(*status);
+        }
+    }
+    else  /* this is for 'Q' columns */
+    {
+        /* read 8-byte descriptors */
+        for (ii = 0; ii < nrows; ii++)
+        {
+	    /* read descriptors */
+            if (ffgi8b(fptr, bytepos, 2, 8, (long *) descript8, status) <= 0)
+	    { 
+              if (length) {
+	        if (descript8[0] > LONG_MAX)*status = NUM_OVERFLOW;
+                *length =   (long) descript8[0];   /* 1st word is the length  */
+                length++;
+	      }
+              if (heapaddr) {
+	        if (descript8[1] > LONG_MAX)*status = NUM_OVERFLOW;
+                *heapaddr = (long) descript8[1];   /* 2nd word is the address */
+                heapaddr++;
+	      }
+              bytepos += rowsize;
+	    }
+	    else
+	      return(*status);
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgdessll(fitsfile *fptr, /* I - FITS file pointer                      */
+           int colnum,     /* I - column number (1 = 1st column of table)   */
+           LONGLONG firstrow,  /* I - first row  (1 = 1st row of table)         */
+           LONGLONG nrows,     /* I - number or rows to read                    */
+           LONGLONG *length,   /* O - number of elements in the row         */
+           LONGLONG *heapaddr, /* O - heap pointer to the data              */
+           int *status)    /* IO - error status                             */
+/*
+  get (read) a range of variable length vector descriptors from the table.
+*/
+{
+    LONGLONG rowsize, bytepos;
+    long  ii;
+    unsigned int descript4[2] = {0,0};
+    LONGLONG descript8[2] = {0,0};
+    tcolumn *colptr;
+
+    if (*status > 0)
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+        if ( ffrdef(fptr, status) > 0)               /* rescan header */
+            return(*status);
+
+    colptr = (fptr->Fptr)->tableptr;  /* point to first column structure */
+    colptr += (colnum - 1);           /* offset to the correct column */
+
+    if (colptr->tdatatype >= 0) {
+        *status = NOT_VARI_LEN;
+        return(*status);
+    }
+
+    rowsize = (fptr->Fptr)->rowlength;
+    bytepos = (fptr->Fptr)->datastart + 
+                  (rowsize  * (firstrow - 1)) +
+                  colptr->tbcol;
+
+    if (colptr->tform[0] == 'P' || colptr->tform[1] == 'P')
+    {
+        /* read 4-byte descriptors */
+        for (ii = 0; ii < nrows; ii++)
+        {
+	    /* read descriptors */
+            if (ffgi4b(fptr, bytepos, 2, 4, (INT32BIT *) descript4, status) <= 0)
+	    { 
+              if (length) {
+                *length =   (LONGLONG) descript4[0];   /* 1st word is the length  */
+                length++;
+	      }
+
+              if (heapaddr) {
+                *heapaddr = (LONGLONG) descript4[1];   /* 2nd word is the address */
+                heapaddr++;
+	      }
+              bytepos += rowsize;
+	    }
+	    else
+	      return(*status);
+        }
+    }
+    else  /* this is for 'Q' columns */
+    {
+        /* read 8-byte descriptors */
+        for (ii = 0; ii < nrows; ii++)
+        {
+	    /* read descriptors */
+	    /* cast to type (long *) even though it is actually (LONGLONG *) */
+            if (ffgi8b(fptr, bytepos, 2, 8, (long *) descript8, status) <= 0)
+	    { 
+              if (length) {
+                *length =   descript8[0];   /* 1st word is the length  */
+                length++;
+	      }
+
+              if (heapaddr) {
+                *heapaddr = descript8[1];   /* 2nd word is the address */
+                heapaddr++;
+	      }
+              bytepos += rowsize;
+	    }
+	    else
+	      return(*status);
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpdes(fitsfile *fptr,  /* I - FITS file pointer                         */
+           int colnum,      /* I - column number (1 = 1st column of table)   */
+           LONGLONG rownum,     /* I - row number (1 = 1st row of table)         */
+           LONGLONG length,    /* I - number of elements in the row             */
+           LONGLONG heapaddr,  /* I - heap pointer to the data                  */
+           int *status)     /* IO - error status                             */
+/*
+  put (write) the variable length vector descriptor to the table.
+*/
+{
+    LONGLONG bytepos;
+    unsigned int descript4[2];
+    LONGLONG descript8[2];
+    tcolumn *colptr;
+
+    if (*status > 0)
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+        if ( ffrdef(fptr, status) > 0)               /* rescan header */
+            return(*status);
+
+    colptr = (fptr->Fptr)->tableptr;  /* point to first column structure */
+    colptr += (colnum - 1);   /* offset to the correct column */
+
+    if (colptr->tdatatype >= 0)
+        *status = NOT_VARI_LEN;
+
+    bytepos = (fptr->Fptr)->datastart + 
+                  ((fptr->Fptr)->rowlength * (rownum - 1)) +
+                  colptr->tbcol;
+
+    ffmbyt(fptr, bytepos, IGNORE_EOF, status); /* move to element */
+
+    if (colptr->tform[0] == 'P' || colptr->tform[1] == 'P')
+    {
+        if (length   > UINT_MAX || length   < 0 ||
+            heapaddr > UINT_MAX || heapaddr < 0) {
+            ffpmsg("P variable length column descriptor is out of range");
+	    *status = NUM_OVERFLOW;
+            return(*status);
+        }
+           
+        descript4[0] = (unsigned int) length;   /* 1st word is the length  */
+        descript4[1] = (unsigned int) heapaddr; /* 2nd word is the address */
+ 
+        ffpi4b(fptr, 2, 4, (INT32BIT *) descript4, status); /* write the descriptor */
+    }
+    else /* this is a 'Q' descriptor column */
+    {
+        descript8[0] =  length;   /* 1st word is the length  */
+        descript8[1] =  heapaddr; /* 2nd word is the address */
+ 
+        ffpi8b(fptr, 2, 8, (long *) descript8, status); /* write the descriptor */
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffchdu(fitsfile *fptr,      /* I - FITS file pointer */
+           int *status)         /* IO - error status     */
+{
+/*
+  close the current HDU.  If we have write access to the file, then:
+    - write the END keyword and pad header with blanks if necessary
+    - check the data fill values, and rewrite them if not correct
+*/
+    char message[FLEN_ERRMSG];
+    int stdriver;
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+    {
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+        /* no need to do any further updating of the HDU */
+    }
+    else if ((fptr->Fptr)->writemode == 1)
+    {
+        urltype2driver("stream://", &stdriver);
+
+        /* don't rescan header in special case of writing to stdout */
+        if (((fptr->Fptr)->driver != stdriver)) 
+             ffrdef(fptr, status); 
+
+        if ((fptr->Fptr)->heapsize > 0) {
+          ffuptf(fptr, status);  /* update the variable length TFORM values */
+        }
+	
+        ffpdfl(fptr, status);  /* insure correct data fill values */
+    }
+
+    if ((fptr->Fptr)->open_count == 1)
+    {
+    /* free memory for the CHDU structure only if no other files are using it */
+        if ((fptr->Fptr)->tableptr)
+        {
+            free((fptr->Fptr)->tableptr);
+           (fptr->Fptr)->tableptr = NULL;
+
+            /* free the tile-compressed image cache, if it exists */
+            if ((fptr->Fptr)->tiledata) {
+	       free((fptr->Fptr)->tiledata);
+	       (fptr->Fptr)->tiledata = 0;
+	       (fptr->Fptr)->tilerow = 0;
+	       (fptr->Fptr)->tiledatasize = 0;
+	       (fptr->Fptr)->tiletype = 0;
+            }
+
+            if ((fptr->Fptr)->tilenullarray) {
+	       free((fptr->Fptr)->tilenullarray);
+	       (fptr->Fptr)->tilenullarray = 0;
+            }
+        }
+    }
+
+    if (*status > 0 && *status != NO_CLOSE_ERROR)
+    {
+        sprintf(message,
+        "Error while closing HDU number %d (ffchdu).", (fptr->Fptr)->curhdu);
+        ffpmsg(message);
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffuptf(fitsfile *fptr,      /* I - FITS file pointer */
+           int *status)         /* IO - error status     */
+/*
+  Update the value of the TFORM keywords for the variable length array
+  columns to make sure they all have the form 1Px(len) or Px(len) where
+  'len' is the maximum length of the vector in the table (e.g., '1PE(400)')
+*/
+{
+    int ii;
+    long tflds;
+    LONGLONG length, addr, maxlen, naxis2, jj;
+    char comment[FLEN_COMMENT], keyname[FLEN_KEYWORD];
+    char tform[FLEN_VALUE], newform[FLEN_VALUE], lenval[40];
+    char card[FLEN_CARD];
+    char message[FLEN_ERRMSG];
+    char *tmp;
+
+    ffmaky(fptr, 2, status);         /* reset to beginning of header */
+    ffgkyjj(fptr, "NAXIS2", &naxis2, comment, status);
+    ffgkyj(fptr, "TFIELDS", &tflds, comment, status);
+
+    for (ii = 1; ii <= tflds; ii++)        /* loop over all the columns */
+    {
+      ffkeyn("TFORM", ii, keyname, status);          /* construct name */
+      if (ffgkys(fptr, keyname, tform, comment, status) > 0)
+      {
+        sprintf(message,
+        "Error while updating variable length vector TFORMn values (ffuptf).");
+        ffpmsg(message);
+        return(*status);
+      }
+      /* is this a variable array length column ? */
+      if (tform[0] == 'P' || tform[1] == 'P' || tform[0] == 'Q' || tform[1] == 'Q')
+      {
+          /* get the max length */
+          maxlen = 0;
+          for (jj=1; jj <= naxis2; jj++)
+          {
+            ffgdesll(fptr, ii, jj, &length, &addr, status);
+
+	    if (length > maxlen)
+	         maxlen = length;
+          }
+
+          /* construct the new keyword value */
+          strcpy(newform, "'");
+          tmp = strchr(tform, '(');  /* truncate old length, if present */
+          if (tmp) *tmp = 0;       
+          strcat(newform, tform);
+
+          /* print as double, because the string-to-64-bit */
+          /* conversion is platform dependent (%lld, %ld, %I64d) */
+
+          sprintf(lenval, "(%.0f)", (double) maxlen);
+
+          strcat(newform,lenval);
+          while(strlen(newform) < 9)
+             strcat(newform," ");   /* append spaces 'till length = 8 */
+          strcat(newform,"'" );     /* append closing parenthesis */
+          /* would be simpler to just call ffmkyj here, but this */
+          /* would force linking in all the modkey & putkey routines */
+          ffmkky(keyname, newform, comment, card, status);  /* make new card */
+          ffmkey(fptr, card, status);   /* replace last read keyword */
+      }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffrdef(fitsfile *fptr,      /* I - FITS file pointer */
+           int *status)         /* IO - error status     */
+/*
+  ReDEFine the structure of a data unit.  This routine re-reads
+  the CHDU header keywords to determine the structure and length of the
+  current data unit.  This redefines the start of the next HDU.
+*/
+{
+    int dummy, tstatus = 0;
+    LONGLONG naxis2;
+    LONGLONG pcount;
+    char card[FLEN_CARD], comm[FLEN_COMMENT], valstring[FLEN_VALUE];
+
+    if (*status > 0)
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+    {
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    }
+    else if ((fptr->Fptr)->writemode == 1) /* write access to the file? */
+    {
+        /* don't need to check NAXIS2 and PCOUNT if data hasn't been written */
+        if ((fptr->Fptr)->datastart != DATA_UNDEFINED)
+        {
+          /* update NAXIS2 keyword if more rows were written to the table */
+          /* and if the user has not explicitly reset the NAXIS2 value */
+          if ((fptr->Fptr)->hdutype != IMAGE_HDU)
+          {
+            ffmaky(fptr, 2, status);
+            if (ffgkyjj(fptr, "NAXIS2", &naxis2, comm, &tstatus) > 0)
+            {
+                /* Couldn't read NAXIS2 (odd!);  in certain circumstances */
+                /* this may be normal, so ignore the error. */
+                naxis2 = (fptr->Fptr)->numrows;
+            }
+
+            if ((fptr->Fptr)->numrows > naxis2
+              && (fptr->Fptr)->origrows == naxis2)
+              /* if origrows is not equal to naxis2, then the user must */
+              /* have manually modified the NAXIS2 keyword value, and */
+              /* we will assume that the current value is correct. */
+            {
+              /* would be simpler to just call ffmkyj here, but this */
+              /* would force linking in all the modkey & putkey routines */
+
+              /* print as double because the 64-bit int conversion */
+              /* is platform dependent (%lld, %ld, %I64 )          */
+
+              sprintf(valstring, "%.0f", (double) ((fptr->Fptr)->numrows));
+
+              ffmkky("NAXIS2", valstring, comm, card, status);
+              ffmkey(fptr, card, status);
+            }
+          }
+
+          /* if data has been written to variable length columns in a  */
+          /* binary table, then we may need to update the PCOUNT value */
+          if ((fptr->Fptr)->heapsize > 0)
+          {
+            ffmaky(fptr, 2, status);
+            ffgkyjj(fptr, "PCOUNT", &pcount, comm, status);
+            if ((fptr->Fptr)->heapsize != pcount)
+            {
+              ffmkyj(fptr, "PCOUNT", (fptr->Fptr)->heapsize, comm, status);
+            }
+          }
+        }
+
+        if (ffwend(fptr, status) <= 0)     /* rewrite END keyword and fill */
+        {
+            ffrhdu(fptr, &dummy, status);  /* re-scan the header keywords  */
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffhdef(fitsfile *fptr,      /* I - FITS file pointer                    */
+           int morekeys,        /* I - reserve space for this many keywords */
+           int *status)         /* IO - error status                        */
+/*
+  based on the number of keywords which have already been written,
+  plus the number of keywords to reserve space for, we then can
+  define where the data unit should start (it must start at the
+  beginning of a 2880-byte logical block).
+
+  This routine will only have any effect if the starting location of the
+  data unit following the header is not already defined.  In any case,
+  it is always possible to add more keywords to the header even if the
+  data has already been written.  It is just more efficient to reserve
+  the space in advance.
+*/
+{
+    LONGLONG delta;
+
+    if (*status > 0 || morekeys < 1)
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+    {
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    }
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+    {
+      ffrdef(fptr, status);
+
+      /* ffrdef defines the offset to datastart and the start of */
+      /* the next HDU based on the number of existing keywords. */
+      /* We need to increment both of these values based on */
+      /* the number of new keywords to be added.  */
+
+      delta = (((fptr->Fptr)->headend + (morekeys * 80)) / 2880 + 1)
+                                * 2880 - (fptr->Fptr)->datastart; 
+              
+      (fptr->Fptr)->datastart += delta;
+
+      (fptr->Fptr)->headstart[ (fptr->Fptr)->curhdu + 1] += delta;
+
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffwend(fitsfile *fptr,       /* I - FITS file pointer */
+            int *status)         /* IO - error status     */
+/*
+  write the END card and following fill (space chars) in the current header
+*/
+{
+    int ii, tstatus;
+    LONGLONG endpos;
+    long nspace;
+    char blankkey[FLEN_CARD], endkey[FLEN_CARD], keyrec[FLEN_CARD] = "";
+
+    if (*status > 0)
+        return(*status);
+
+    endpos = (fptr->Fptr)->headend;
+
+    /* we assume that the HDUposition == curhdu in all cases */
+
+    /*  calc the data starting position if not currently defined */
+    if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+        (fptr->Fptr)->datastart = ( endpos / 2880 + 1 ) * 2880;
+
+    /* calculate the number of blank keyword slots in the header */
+    nspace = (long) (( (fptr->Fptr)->datastart - endpos ) / 80);
+
+    /* construct a blank and END keyword (80 spaces )  */
+    strcpy(blankkey, "                                        ");
+    strcat(blankkey, "                                        ");
+    strcpy(endkey, "END                                     ");
+    strcat(endkey, "                                        ");
+  
+    /* check if header is already correctly terminated with END and fill */
+    tstatus=0;
+    ffmbyt(fptr, endpos, REPORT_EOF, &tstatus); /* move to header end */
+    for (ii=0; ii < nspace; ii++)
+    {
+        ffgbyt(fptr, 80, keyrec, &tstatus);  /* get next keyword */
+        if (tstatus) break;
+        if (strncmp(keyrec, blankkey, 80) && strncmp(keyrec, endkey, 80))
+            break;
+    }
+
+    if (ii == nspace && !tstatus)
+    {
+        /* check if the END keyword exists at the correct position */
+        endpos=maxvalue( endpos, ( (fptr->Fptr)->datastart - 2880 ) );
+        ffmbyt(fptr, endpos, REPORT_EOF, &tstatus);  /* move to END position */
+        ffgbyt(fptr, 80, keyrec, &tstatus); /* read the END keyword */
+        if ( !strncmp(keyrec, endkey, 80) && !tstatus) {
+
+            /* store this position, for later reference */
+            (fptr->Fptr)->ENDpos = endpos;
+
+            return(*status);    /* END card was already correct */
+         }
+    }
+
+    /* header was not correctly terminated, so write the END and blank fill */
+    endpos = (fptr->Fptr)->headend;
+    ffmbyt(fptr, endpos, IGNORE_EOF, status); /* move to header end */
+    for (ii=0; ii < nspace; ii++)
+        ffpbyt(fptr, 80, blankkey, status);  /* write the blank keywords */
+
+    /*
+    The END keyword must either be placed immediately after the last
+    keyword that was written (as indicated by the headend value), or
+    must be in the first 80 bytes of the 2880-byte FITS record immediately 
+    preceeding the data unit, whichever is further in the file. The
+    latter will occur if space has been reserved for more header keywords
+    which have not yet been written.
+    */
+
+    endpos=maxvalue( endpos, ( (fptr->Fptr)->datastart - 2880 ) );
+    ffmbyt(fptr, endpos, REPORT_EOF, status);  /* move to END position */
+
+    ffpbyt(fptr, 80, endkey, status); /*  write the END keyword to header */
+    
+    /* store this position, for later reference */
+    (fptr->Fptr)->ENDpos = endpos;
+
+    if (*status > 0)
+        ffpmsg("Error while writing END card (ffwend).");
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpdfl(fitsfile *fptr,      /* I - FITS file pointer */
+           int *status)         /* IO - error status     */
+/*
+  Write the Data Unit Fill values if they are not already correct.
+  The fill values are used to fill out the last 2880 byte block of the HDU.
+  Fill the data unit with zeros or blanks depending on the type of HDU
+  from the end of the data to the end of the current FITS 2880 byte block
+*/
+{
+    char chfill, fill[2880];
+    LONGLONG fillstart;
+    int nfill, tstatus, ii;
+
+    if (*status > 0)
+        return(*status);
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        return(*status);      /* fill has already been correctly written */
+
+    if ((fptr->Fptr)->heapstart == 0)
+        return(*status);      /* null data unit, so there is no fill */
+
+    fillstart = (fptr->Fptr)->datastart + (fptr->Fptr)->heapstart +
+                (fptr->Fptr)->heapsize;
+
+    nfill = (long) ((fillstart + 2879) / 2880 * 2880 - fillstart);
+
+    if ((fptr->Fptr)->hdutype == ASCII_TBL)
+        chfill = 32;         /* ASCII tables are filled with spaces */
+    else
+        chfill = 0;          /* all other extensions are filled with zeros */
+
+    tstatus = 0;
+
+    if (!nfill)  /* no fill bytes; just check that entire table exists */
+    {
+        fillstart--;
+        nfill = 1;
+        ffmbyt(fptr, fillstart, REPORT_EOF, &tstatus); /* move to last byte */
+        ffgbyt(fptr, nfill, fill, &tstatus);           /* get the last byte */
+
+        if (tstatus == 0)
+            return(*status);  /* no EOF error, so everything is OK */
+    }
+    else
+    {
+        ffmbyt(fptr, fillstart, REPORT_EOF, &tstatus); /* move to fill area */
+        ffgbyt(fptr, nfill, fill, &tstatus);           /* get the fill bytes */
+
+        if (tstatus == 0)
+        {
+            for (ii = 0; ii < nfill; ii++)
+            {
+                if (fill[ii] != chfill)
+                    break;
+            }
+
+            if (ii == nfill)
+                return(*status);   /* all the fill values were correct */
+        }
+    }
+
+    /* fill values are incorrect or have not been written, so write them */
+
+    memset(fill, chfill, nfill);  /* fill the buffer with the fill value */
+
+    ffmbyt(fptr, fillstart, IGNORE_EOF, status); /* move to fill area */
+    ffpbyt(fptr, nfill, fill, status); /* write the fill bytes */
+
+    if (*status > 0)
+        ffpmsg("Error writing Data Unit fill bytes (ffpdfl).");
+
+    return(*status);
+}
+/**********************************************************************
+   ffchfl : Check Header Fill values
+
+      Check that the header unit is correctly filled with blanks from
+      the END card to the end of the current FITS 2880-byte block
+
+         Function parameters:
+            fptr     Fits file pointer
+            status   output error status
+
+    Translated ftchfl into C by Peter Wilson, Oct. 1997
+**********************************************************************/
+int ffchfl( fitsfile *fptr, int *status)
+{
+   int nblank,i,gotend;
+   LONGLONG endpos;
+   char rec[FLEN_CARD];
+   char *blanks="                                                                                ";  /*  80 spaces  */
+
+   if( *status > 0 ) return (*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+   /*   calculate the number of blank keyword slots in the header  */
+
+   endpos=(fptr->Fptr)->headend;
+   nblank=(long) (((fptr->Fptr)->datastart-endpos)/80);
+
+   /*   move the i/o pointer to the end of the header keywords   */
+
+   ffmbyt(fptr,endpos,TRUE,status);
+
+   /*   find the END card (there may be blank keywords perceeding it)   */
+
+   gotend=FALSE;
+   for(i=0;i<nblank;i++) {
+      ffgbyt(fptr,80,rec,status);
+      if( !strncmp(rec, "END     ", 8) ) {
+         if( gotend ) {
+            /*   There is a duplicate END record   */
+            *status=BAD_HEADER_FILL;
+            ffpmsg("Warning: Header fill area contains duplicate END card:");
+         }
+         gotend=TRUE;
+         if( strncmp( rec+8, blanks+8, 72) ) {
+            /*   END keyword has extra characters   */
+            *status=END_JUNK;
+            ffpmsg(
+            "Warning: END keyword contains extraneous non-blank characters:");
+         }
+      } else if( gotend ) {
+         if( strncmp( rec, blanks, 80 ) ) {
+            /*   The fill area contains extraneous characters   */
+            *status=BAD_HEADER_FILL;
+            ffpmsg(
+         "Warning: Header fill area contains extraneous non-blank characters:");
+         }
+      }
+
+      if( *status > 0 ) {
+         rec[FLEN_CARD - 1] = '\0';  /* make sure string is null terminated */
+         ffpmsg(rec);
+         return( *status );
+      }
+   }
+   return( *status );
+}
+
+/**********************************************************************
+   ffcdfl : Check Data Unit Fill values
+
+      Check that the data unit is correctly filled with zeros or
+      blanks from the end of the data to the end of the current
+      FITS 2880 byte block
+
+         Function parameters:
+            fptr     Fits file pointer
+            status   output error status
+
+    Translated ftcdfl into C by Peter Wilson, Oct. 1997
+**********************************************************************/
+int ffcdfl( fitsfile *fptr, int *status)
+{
+   int nfill,i;
+   LONGLONG filpos;
+   char chfill,chbuff[2880];
+
+   if( *status > 0 ) return( *status );
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+   /*   check if the data unit is null   */
+   if( (fptr->Fptr)->heapstart==0 ) return( *status );
+
+   /* calculate starting position of the fill bytes, if any */
+   filpos = (fptr->Fptr)->datastart 
+          + (fptr->Fptr)->heapstart 
+          + (fptr->Fptr)->heapsize;
+
+   /*   calculate the number of fill bytes   */
+   nfill = (long) ((filpos + 2879) / 2880 * 2880 - filpos);
+   if( nfill == 0 ) return( *status );
+
+   /*   move to the beginning of the fill bytes   */
+   ffmbyt(fptr, filpos, FALSE, status);
+
+   if( ffgbyt(fptr, nfill, chbuff, status) > 0)
+   {
+      ffpmsg("Error reading data unit fill bytes (ffcdfl).");
+      return( *status );
+   }
+
+   if( (fptr->Fptr)->hdutype==ASCII_TBL )
+      chfill = 32;         /* ASCII tables are filled with spaces */
+   else
+      chfill = 0;          /* all other extensions are filled with zeros */
+   
+   /*   check for all zeros or blanks   */
+   
+   for(i=0;i<nfill;i++) {
+      if( chbuff[i] != chfill ) {
+         *status=BAD_DATA_FILL;
+         if( (fptr->Fptr)->hdutype==ASCII_TBL )
+            ffpmsg("Warning: remaining bytes following ASCII table data are not filled with blanks.");
+         else
+            ffpmsg("Warning: remaining bytes following data are not filled with zeros.");
+         return( *status );
+      }
+   }
+   return( *status );
+}
+/*--------------------------------------------------------------------------*/
+int ffcrhd(fitsfile *fptr,      /* I - FITS file pointer */
+           int *status)         /* IO - error status     */
+/*
+  CReate Header Data unit:  Create, initialize, and move the i/o pointer
+  to a new extension appended to the end of the FITS file.
+*/
+{
+    int  tstatus = 0;
+    LONGLONG bytepos, *ptr;
+
+    if (*status > 0)
+        return(*status);
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    /* If the current header is empty, we don't have to do anything */
+    if ((fptr->Fptr)->headend == (fptr->Fptr)->headstart[(fptr->Fptr)->curhdu] )
+        return(*status);
+
+    while (ffmrhd(fptr, 1, 0, &tstatus) == 0);  /* move to end of file */
+
+    if ((fptr->Fptr)->maxhdu == (fptr->Fptr)->MAXHDU)
+    {
+        /* allocate more space for the headstart array */
+        ptr = (LONGLONG*) realloc( (fptr->Fptr)->headstart,
+                        ((fptr->Fptr)->MAXHDU + 1001) * sizeof(LONGLONG) );
+
+        if (ptr == NULL)
+           return (*status = MEMORY_ALLOCATION);
+        else {
+          (fptr->Fptr)->MAXHDU = (fptr->Fptr)->MAXHDU + 1000;
+          (fptr->Fptr)->headstart = ptr;
+        }
+    }
+
+    if (ffchdu(fptr, status) <= 0)  /* close the current HDU */
+    {
+      bytepos = (fptr->Fptr)->headstart[(fptr->Fptr)->maxhdu + 1]; /* last */
+      ffmbyt(fptr, bytepos, IGNORE_EOF, status);  /* move file ptr to it */
+      (fptr->Fptr)->maxhdu++;       /* increment the known number of HDUs */
+      (fptr->Fptr)->curhdu = (fptr->Fptr)->maxhdu; /* set current HDU loc */
+      fptr->HDUposition    = (fptr->Fptr)->maxhdu; /* set current HDU loc */
+      (fptr->Fptr)->nextkey = bytepos;    /* next keyword = start of header */
+      (fptr->Fptr)->headend = bytepos;          /* end of header */
+      (fptr->Fptr)->datastart = DATA_UNDEFINED; /* start data unit undefined */
+
+       /* any other needed resets */
+       
+       /* reset the dithering offset that may have been calculated for the */
+       /* previous HDU back to the requested default value */
+       (fptr->Fptr)->dither_offset = (fptr->Fptr)->request_dither_offset;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffdblk(fitsfile *fptr,      /* I - FITS file pointer                    */
+           long nblocks,        /* I - number of 2880-byte blocks to delete */
+           int *status)         /* IO - error status                        */
+/*
+  Delete the specified number of 2880-byte blocks from the end
+  of the CHDU by shifting all following extensions up this 
+  number of blocks.
+*/
+{
+    char buffer[2880];
+    int tstatus, ii;
+    LONGLONG readpos, writepos;
+
+    if (*status > 0 || nblocks <= 0)
+        return(*status);
+
+    tstatus = 0;
+    /* pointers to the read and write positions */
+
+    readpos = (fptr->Fptr)->datastart + 
+                   (fptr->Fptr)->heapstart + 
+                   (fptr->Fptr)->heapsize;
+    readpos = ((readpos + 2879) / 2880) * 2880; /* start of block */
+
+/*  the following formula is wrong because the current data unit
+    may have been extended without updating the headstart value
+    of the following HDU.
+    
+    readpos = (fptr->Fptr)->headstart[((fptr->Fptr)->curhdu) + 1];
+*/
+    writepos = readpos - ((LONGLONG)nblocks * 2880);
+
+    while ( !ffmbyt(fptr, readpos, REPORT_EOF, &tstatus) &&
+            !ffgbyt(fptr, 2880L, buffer, &tstatus) )
+    {
+        ffmbyt(fptr, writepos, REPORT_EOF, status);
+        ffpbyt(fptr, 2880L, buffer, status);
+
+        if (*status > 0)
+        {
+           ffpmsg("Error deleting FITS blocks (ffdblk)");
+           return(*status);
+        }
+        readpos  += 2880;  /* increment to next block to transfer */
+        writepos += 2880;
+    }
+
+    /* now fill the last nblock blocks with zeros */
+    memset(buffer, 0, 2880);
+    ffmbyt(fptr, writepos, REPORT_EOF, status);
+
+    for (ii = 0; ii < nblocks; ii++)
+        ffpbyt(fptr, 2880L, buffer, status);
+
+    /* move back before the deleted blocks, since they may be deleted */
+    /*   and we do not want to delete the current active buffer */
+    ffmbyt(fptr, writepos - 1, REPORT_EOF, status);
+
+    /* truncate the file to the new size, if supported on this device */
+    fftrun(fptr, writepos, status);
+
+    /* recalculate the starting location of all subsequent HDUs */
+    for (ii = (fptr->Fptr)->curhdu; ii <= (fptr->Fptr)->maxhdu; ii++)
+         (fptr->Fptr)->headstart[ii + 1] -= ((LONGLONG)nblocks * 2880);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffghdt(fitsfile *fptr,      /* I - FITS file pointer             */
+           int *exttype,        /* O - type of extension, 0, 1, or 2 */
+                                /*  for IMAGE_HDU, ASCII_TBL, or BINARY_TBL */
+           int *status)         /* IO - error status                 */
+/*
+  Return the type of the CHDU. This returns the 'logical' type of the HDU,
+  not necessarily the physical type, so in the case of a compressed image
+  stored in a binary table, this will return the type as an Image, not a
+  binary table.
+*/
+{
+    if (*status > 0)
+        return(*status);
+
+    if (fptr->HDUposition == 0 && (fptr->Fptr)->headend == 0) { 
+         /* empty primary array is alway an IMAGE_HDU */
+         *exttype = IMAGE_HDU;
+    }
+    else {
+ 
+        /* reset position to the correct HDU if necessary */
+        if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        {
+            ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+        }
+        else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+        {
+            /* rescan header if data structure is undefined */
+            if ( ffrdef(fptr, status) > 0)               
+                return(*status);
+        }
+
+        *exttype = (fptr->Fptr)->hdutype; /* return the type of HDU */
+
+        /*  check if this is a compressed image */
+        if ((fptr->Fptr)->compressimg)
+            *exttype = IMAGE_HDU;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_is_reentrant(void)
+/*
+   Was CFITSIO compiled with the -D_REENTRANT flag?  1 = yes, 0 = no.
+   Note that specifying the -D_REENTRANT flag is required, but may not be 
+   sufficient, to ensure that CFITSIO can be safely used in a multi-threaded 
+   environoment.
+*/
+{
+#ifdef _REENTRANT
+       return(1);
+#else
+       return(0);
+#endif
+}
+/*--------------------------------------------------------------------------*/
+int fits_is_compressed_image(fitsfile *fptr,  /* I - FITS file pointer  */
+                 int *status)                 /* IO - error status      */
+/*
+   Returns TRUE if the CHDU is a compressed image, else returns zero.
+*/
+{
+    if (*status > 0)
+        return(0);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+    {
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    }
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+    {
+        /* rescan header if data structure is undefined */
+        if ( ffrdef(fptr, status) > 0)               
+            return(*status);
+    }
+
+    /*  check if this is a compressed image */
+    if ((fptr->Fptr)->compressimg)
+         return(1);
+
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int ffgipr(fitsfile *infptr,   /* I - FITS file pointer                     */
+        int maxaxis,           /* I - max number of axes to return          */
+        int *bitpix,           /* O - image data type                       */
+        int *naxis,            /* O - image dimension (NAXIS value)         */
+        long *naxes,           /* O - size of image dimensions              */
+        int *status)           /* IO - error status      */
+
+/*
+    get the datatype and size of the input image
+*/
+{
+
+    if (*status > 0)
+        return(*status);
+
+    /* don't return the parameter if a null pointer was given */
+
+    if (bitpix)
+      fits_get_img_type(infptr, bitpix, status);  /* get BITPIX value */
+
+    if (naxis)
+      fits_get_img_dim(infptr, naxis, status);    /* get NAXIS value */
+
+    if (naxes)
+      fits_get_img_size(infptr, maxaxis, naxes, status); /* get NAXISn values */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgiprll(fitsfile *infptr,   /* I - FITS file pointer                   */
+        int maxaxis,           /* I - max number of axes to return          */
+        int *bitpix,           /* O - image data type                       */
+        int *naxis,            /* O - image dimension (NAXIS value)         */
+        LONGLONG *naxes,       /* O - size of image dimensions              */
+        int *status)           /* IO - error status      */
+
+/*
+    get the datatype and size of the input image
+*/
+{
+
+    if (*status > 0)
+        return(*status);
+
+    /* don't return the parameter if a null pointer was given */
+
+    if (bitpix)
+      fits_get_img_type(infptr, bitpix, status);  /* get BITPIX value */
+
+    if (naxis)
+      fits_get_img_dim(infptr, naxis, status);    /* get NAXIS value */
+
+    if (naxes)
+      fits_get_img_sizell(infptr, maxaxis, naxes, status); /* get NAXISn values */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgidt( fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  *imgtype,   /* O - image data type                         */
+            int  *status)    /* IO - error status                           */
+/*
+  Get the datatype of the image (= BITPIX keyword for normal image, or
+  ZBITPIX for a compressed image)
+*/
+{
+    if (*status > 0)
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+        if ( ffrdef(fptr, status) > 0)               /* rescan header */
+            return(*status);
+
+    /* reset to beginning of header */
+    ffmaky(fptr, 1, status);  /* simply move to beginning of header */
+
+    if ((fptr->Fptr)->hdutype == IMAGE_HDU)
+    {
+        ffgky(fptr, TINT, "BITPIX", imgtype, NULL, status);
+    }
+    else if ((fptr->Fptr)->compressimg)
+    {
+        /* this is a binary table containing a compressed image */
+        ffgky(fptr, TINT, "ZBITPIX", imgtype, NULL, status);
+    }
+    else
+    {
+        *status = NOT_IMAGE;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgiet( fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  *imgtype,   /* O - image data type                         */
+            int  *status)    /* IO - error status                           */
+/*
+  Get the effective datatype of the image (= BITPIX keyword for normal image,
+  or ZBITPIX for a compressed image)
+*/
+{
+    int tstatus;
+    long lngscale, lngzero = 0;
+    double bscale, bzero, min_val, max_val;
+
+    if (*status > 0)
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+        if ( ffrdef(fptr, status) > 0)               /* rescan header */
+            return(*status);
+
+    /* reset to beginning of header */
+    ffmaky(fptr, 2, status);  /* simply move to beginning of header */
+
+    if ((fptr->Fptr)->hdutype == IMAGE_HDU)
+    {
+        ffgky(fptr, TINT, "BITPIX", imgtype, NULL, status);
+    }
+    else if ((fptr->Fptr)->compressimg)
+    {
+        /* this is a binary table containing a compressed image */
+        ffgky(fptr, TINT, "ZBITPIX", imgtype, NULL, status);
+    }
+    else
+    {
+        *status = NOT_IMAGE;
+        return(*status);
+
+    }
+
+    /* check if the BSCALE and BZERO keywords are defined, which might
+       change the effective datatype of the image  */
+    tstatus = 0;
+    ffgky(fptr, TDOUBLE, "BSCALE", &bscale, NULL, &tstatus);
+    if (tstatus)
+           bscale = 1.0;
+
+    tstatus = 0;
+    ffgky(fptr, TDOUBLE, "BZERO", &bzero, NULL, &tstatus);
+    if (tstatus)
+           bzero = 0.0;
+
+    if (bscale == 1.0 && bzero == 0.0)  /* no scaling */
+        return(*status);
+
+    switch (*imgtype)
+    {
+      case BYTE_IMG:   /* 8-bit image */
+        min_val = 0.;
+        max_val = 255.0;
+        break;
+
+      case SHORT_IMG:
+        min_val = -32768.0;
+        max_val =  32767.0;
+        break;
+        
+      case LONG_IMG:
+
+        min_val = -2147483648.0;
+        max_val =  2147483647.0;
+        break;
+        
+      default:  /* don't have to deal with other data types */
+        return(*status);
+    }
+
+    if (bscale >= 0.) {
+        min_val = bzero + bscale * min_val;
+        max_val = bzero + bscale * max_val;
+    } else {
+        max_val = bzero + bscale * min_val;
+        min_val = bzero + bscale * max_val;
+    }
+    if (bzero < 2147483648.)  /* don't exceed range of 32-bit integer */
+       lngzero = (long) bzero;
+    lngscale = (long) bscale;
+
+    if ((bzero != 2147483648.) && /* special value that exceeds integer range */
+       (lngzero != bzero || lngscale != bscale)) { /* not integers? */
+       /* floating point scaled values; just decide on required precision */
+       if (*imgtype == BYTE_IMG || *imgtype == SHORT_IMG)
+          *imgtype = FLOAT_IMG;
+       else
+         *imgtype = DOUBLE_IMG;
+
+    /*
+       In all the remaining cases, BSCALE and BZERO are integers,
+       and not equal to 1 and 0, respectively.  
+    */
+
+    } else if ((min_val == -128.) && (max_val == 127.)) {
+       *imgtype = SBYTE_IMG;
+
+    } else if ((min_val >= -32768.0) && (max_val <= 32767.0)) {
+       *imgtype = SHORT_IMG;
+
+    } else if ((min_val >= 0.0) && (max_val <= 65535.0)) {
+       *imgtype = USHORT_IMG;
+
+    } else if ((min_val >= -2147483648.0) && (max_val <= 2147483647.0)) {
+       *imgtype = LONG_IMG;
+
+    } else if ((min_val >= 0.0) && (max_val < 4294967296.0)) {
+       *imgtype = ULONG_IMG;
+
+    } else {  /* exceeds the range of a 32-bit integer */
+       *imgtype = DOUBLE_IMG;
+    }   
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgidm( fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  *naxis  ,   /* O - image dimension (NAXIS value)           */
+            int  *status)    /* IO - error status                           */
+/*
+  Get the dimension of the image (= NAXIS keyword for normal image, or
+  ZNAXIS for a compressed image)
+  These values are cached for faster access.
+*/
+{
+    if (*status > 0)
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+        if ( ffrdef(fptr, status) > 0)               /* rescan header */
+            return(*status);
+
+    if ((fptr->Fptr)->hdutype == IMAGE_HDU)
+    {
+        *naxis = (fptr->Fptr)->imgdim;
+    }
+    else if ((fptr->Fptr)->compressimg)
+    {
+        *naxis = (fptr->Fptr)->zndim;
+    }
+    else
+    {
+        *status = NOT_IMAGE;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgisz( fitsfile *fptr,  /* I - FITS file pointer                       */
+            int nlen,        /* I - number of axes to return                */
+            long  *naxes,    /* O - size of image dimensions                */
+            int  *status)    /* IO - error status                           */
+/*
+  Get the size of the image dimensions (= NAXISn keywords for normal image, or
+  ZNAXISn for a compressed image)
+  These values are cached for faster access.
+
+*/
+{
+    int ii, naxis;
+
+    if (*status > 0)
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+        if ( ffrdef(fptr, status) > 0)               /* rescan header */
+            return(*status);
+
+    if ((fptr->Fptr)->hdutype == IMAGE_HDU)
+    {
+        naxis = minvalue((fptr->Fptr)->imgdim, nlen);
+        for (ii = 0; ii < naxis; ii++)
+        {
+            naxes[ii] = (long) (fptr->Fptr)->imgnaxis[ii];
+        }
+    }
+    else if ((fptr->Fptr)->compressimg)
+    {
+        naxis = minvalue( (fptr->Fptr)->zndim, nlen);
+        for (ii = 0; ii < naxis; ii++)
+        {
+            naxes[ii] = (long) (fptr->Fptr)->znaxis[ii];
+        }
+    }
+    else
+    {
+        *status = NOT_IMAGE;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgiszll( fitsfile *fptr,  /* I - FITS file pointer                     */
+            int nlen,          /* I - number of axes to return              */
+            LONGLONG  *naxes,  /* O - size of image dimensions              */
+            int  *status)      /* IO - error status                         */
+/*
+  Get the size of the image dimensions (= NAXISn keywords for normal image, or
+  ZNAXISn for a compressed image)
+*/
+{
+    int ii, naxis;
+
+    if (*status > 0)
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+        if ( ffrdef(fptr, status) > 0)               /* rescan header */
+            return(*status);
+
+    if ((fptr->Fptr)->hdutype == IMAGE_HDU)
+    {
+        naxis = minvalue((fptr->Fptr)->imgdim, nlen);
+        for (ii = 0; ii < naxis; ii++)
+        {
+            naxes[ii] = (fptr->Fptr)->imgnaxis[ii];
+        }
+    }
+    else if ((fptr->Fptr)->compressimg)
+    {
+        naxis = minvalue( (fptr->Fptr)->zndim, nlen);
+        for (ii = 0; ii < naxis; ii++)
+        {
+            naxes[ii] = (fptr->Fptr)->znaxis[ii];
+        }
+    }
+    else
+    {
+        *status = NOT_IMAGE;
+    }
+
+    return(*status);
+}/*--------------------------------------------------------------------------*/
+int ffmahd(fitsfile *fptr,      /* I - FITS file pointer             */
+           int hdunum,          /* I - number of the HDU to move to  */
+           int *exttype,        /* O - type of extension, 0, 1, or 2 */
+           int *status)         /* IO - error status                 */
+/*
+  Move to Absolute Header Data unit.  Move to the specified HDU
+  and read the header to initialize the table structure.  Note that extnum 
+  is one based, so the primary array is extnum = 1.
+*/
+{
+    int moveto, tstatus;
+    char message[FLEN_ERRMSG];
+    LONGLONG *ptr;
+
+    if (*status > 0)
+        return(*status);
+    else if (hdunum < 1 )
+        return(*status = BAD_HDU_NUM);
+    else if (hdunum >= (fptr->Fptr)->MAXHDU )
+    {
+        /* allocate more space for the headstart array */
+        ptr = (LONGLONG*) realloc( (fptr->Fptr)->headstart,
+                        (hdunum + 1001) * sizeof(LONGLONG) ); 
+
+        if (ptr == NULL)
+           return (*status = MEMORY_ALLOCATION);
+        else {
+          (fptr->Fptr)->MAXHDU = hdunum + 1000; 
+          (fptr->Fptr)->headstart = ptr;
+        }
+    }
+
+    /* set logical HDU position to the actual position, in case they differ */
+    fptr->HDUposition = (fptr->Fptr)->curhdu;
+
+    while( ((fptr->Fptr)->curhdu) + 1 != hdunum) /* at the correct HDU? */
+    {
+        /* move directly to the extension if we know that it exists,
+           otherwise move to the highest known extension.  */
+        
+        moveto = minvalue(hdunum - 1, ((fptr->Fptr)->maxhdu) + 1);
+
+        /* test if HDU exists */
+        if ((fptr->Fptr)->headstart[moveto] < (fptr->Fptr)->logfilesize )
+        {
+            if (ffchdu(fptr, status) <= 0)  /* close out the current HDU */
+            {
+                if (ffgext(fptr, moveto, exttype, status) > 0)
+                {   /* failed to get the requested extension */
+
+                    tstatus = 0;
+                    ffrhdu(fptr, exttype, &tstatus); /* restore the CHDU */
+                }
+            }
+        }
+        else
+            *status = END_OF_FILE;
+
+        if (*status > 0)
+        {
+            if (*status != END_OF_FILE)
+            {
+                /* don't clutter up the message stack in the common case of */
+                /* simply hitting the end of file (often an expected error) */
+
+                sprintf(message,
+                "Failed to move to HDU number %d (ffmahd).", hdunum);
+                ffpmsg(message);
+            }
+            return(*status);
+        }
+    }
+
+    /* return the type of HDU; tile compressed images which are stored */
+    /* in a binary table will return exttype = IMAGE_HDU, not BINARY_TBL */
+    if (exttype != NULL)
+        ffghdt(fptr, exttype, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffmrhd(fitsfile *fptr,      /* I - FITS file pointer                    */
+           int hdumov,          /* I - rel. no. of HDUs to move by (+ or -) */ 
+           int *exttype,        /* O - type of extension, 0, 1, or 2        */
+           int *status)         /* IO - error status                        */
+/*
+  Move a Relative number of Header Data units.  Offset to the specified
+  extension and read the header to initialize the HDU structure. 
+*/
+{
+    int extnum;
+
+    if (*status > 0)
+        return(*status);
+
+    extnum = fptr->HDUposition + 1 + hdumov;  /* the absolute HDU number */
+    ffmahd(fptr, extnum, exttype, status);  /* move to the HDU */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffmnhd(fitsfile *fptr,      /* I - FITS file pointer                    */
+           int exttype,         /* I - desired extension type               */
+           char *hduname,       /* I - desired EXTNAME value for the HDU    */
+           int hduver,          /* I - desired EXTVERS value for the HDU    */
+           int *status)         /* IO - error status                        */
+/*
+  Move to the next HDU with a given extension type (IMAGE_HDU, ASCII_TBL,
+  BINARY_TBL, or ANY_HDU), extension name (EXTNAME or HDUNAME keyword),
+  and EXTVERS keyword values.  If hduvers = 0, then move to the first HDU
+  with the given type and name regardless of EXTVERS value.  If no matching
+  HDU is found in the file, then the current open HDU will remain unchanged.
+*/
+{
+    char extname[FLEN_VALUE];
+    int ii, hdutype, alttype, extnum, tstatus, match, exact;
+    int slen, putback = 0, chopped = 0;
+    long extver;
+
+    if (*status > 0)
+        return(*status);
+
+    extnum = fptr->HDUposition + 1;  /* save the current HDU number */
+
+    /*
+       This is a kludge to deal with a special case where the
+       user specified a hduname that ended with a # character, which
+       CFITSIO previously interpreted as a flag to mean "don't copy any
+       other HDUs in the file into the virtual file in memory.  If the
+       remaining hduname does not end with a # character (meaning that
+       the user originally entered a hduname ending in 2 # characters)
+       then there is the possibility that the # character should be
+       treated literally, if the actual EXTNAME also ends with a #.
+       Setting putback = 1 means that we need to test for this case later on.
+    */
+        
+    if ((fptr->Fptr)->only_one) {  /* if true, name orignally ended with a # */
+       slen = strlen(hduname);
+       if (hduname[slen - 1] != '#') /* This will fail if real EXTNAME value */
+           putback = 1;              /*  ends with 2 # characters. */
+    } 
+
+    for (ii=1; 1; ii++)    /* loop over all HDUs until EOF */
+    {
+        tstatus = 0;
+        if (ffmahd(fptr, ii, &hdutype, &tstatus))  /* move to next HDU */
+        {
+           ffmahd(fptr, extnum, 0, status); /* restore original file position */
+           return(*status = BAD_HDU_NUM);   /* couldn't find desired HDU */
+        }
+
+        alttype = -1; 
+        if (fits_is_compressed_image(fptr, status))
+            alttype = BINARY_TBL;
+        
+        /* Does this HDU have a matching type? */
+        if (exttype == ANY_HDU || hdutype == exttype || hdutype == alttype)
+        {
+          ffmaky(fptr, 2, status); /* reset to the 2nd keyword in the header */
+          if (ffgkys(fptr, "EXTNAME", extname, 0, &tstatus) <= 0) /* get keyword */
+          {
+               if (putback) {          /* more of the kludge */
+                   /* test if the EXTNAME value ends with a #;  if so, chop it  */
+		   /* off before comparing the strings */
+		   chopped = 0;
+	           slen = strlen(extname);
+		   if (extname[slen - 1] == '#') {
+		       extname[slen - 1] = '\0'; 
+                       chopped = 1;
+                   }
+               }
+
+               /* see if the strings are an exact match */
+               ffcmps(extname, hduname, CASEINSEN, &match, &exact);
+          }
+
+          /* if EXTNAME keyword doesn't exist, or it does not match, then try HDUNAME */
+          if (tstatus || !exact)
+	  {
+               tstatus = 0;
+               if (ffgkys(fptr, "HDUNAME", extname, 0, &tstatus) <= 0)
+	       {
+                   if (putback) {          /* more of the kludge */
+		       chopped = 0;
+	               slen = strlen(extname);
+		       if (extname[slen - 1] == '#') {
+		           extname[slen - 1] = '\0';  /* chop off the # */
+                           chopped = 1;
+                       }
+                   }
+
+                   /* see if the strings are an exact match */
+                   ffcmps(extname, hduname, CASEINSEN, &match, &exact);
+               }
+          }
+
+          if (!tstatus && exact)    /* found a matching name */
+          {
+             if (hduver)  /* need to check if version numbers match? */
+             {
+                if (ffgkyj(fptr, "EXTVER", &extver, 0, &tstatus) > 0)
+                    extver = 1;  /* assume default EXTVER value */
+
+                if ( (int) extver == hduver)
+                {
+                    if (chopped) {
+                        /* The # was literally part of the name, not a flag */
+	                (fptr->Fptr)->only_one = 0;  
+                    }
+                    return(*status);    /* found matching name and vers */
+                }
+             }
+             else
+             {
+                 if (chopped) {
+                     /* The # was literally part of the name, not a flag */
+	            (fptr->Fptr)->only_one = 0;  
+                 }
+                 return(*status);    /* found matching name */
+             }
+          }  /* end of !tstatus && exact */
+
+        }  /* end of matching HDU type */
+    }  /* end of loop over HDUs */
+}
+/*--------------------------------------------------------------------------*/
+int ffthdu(fitsfile *fptr,      /* I - FITS file pointer                    */
+           int *nhdu,            /* O - number of HDUs in the file           */
+           int *status)         /* IO - error status                        */
+/*
+  Return the number of HDUs that currently exist in the file.
+*/
+{
+    int ii, extnum, tstatus;
+
+    if (*status > 0)
+        return(*status);
+
+    extnum = fptr->HDUposition + 1;  /* save the current HDU number */
+    *nhdu = extnum - 1;
+
+    /* if the CHDU is empty or not completely defined, just return */
+    if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+        return(*status);
+
+    tstatus = 0;
+
+    /* loop until EOF */
+    for (ii=extnum; ffmahd(fptr, ii, 0, &tstatus) <= 0; ii++)
+    {
+        *nhdu = ii;
+    }
+
+    ffmahd(fptr, extnum, 0, status);       /* restore orig file position */
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgext(fitsfile *fptr,      /* I - FITS file pointer                */
+           int hdunum,          /* I - no. of HDU to move get (0 based) */ 
+           int *exttype,        /* O - type of extension, 0, 1, or 2    */
+           int *status)         /* IO - error status                    */
+/*
+  Get Extension.  Move to the specified extension and initialize the
+  HDU structure.
+*/
+{
+    int xcurhdu, xmaxhdu;
+    LONGLONG xheadend;
+
+    if (*status > 0)
+        return(*status);
+
+    if (ffmbyt(fptr, (fptr->Fptr)->headstart[hdunum], REPORT_EOF, status) <= 0)
+    {
+        /* temporarily save current values, in case of error */
+        xcurhdu = (fptr->Fptr)->curhdu;
+        xmaxhdu = (fptr->Fptr)->maxhdu;
+        xheadend = (fptr->Fptr)->headend;
+
+        /* set new parameter values */
+        (fptr->Fptr)->curhdu = hdunum;
+        fptr->HDUposition    = hdunum;
+        (fptr->Fptr)->maxhdu = maxvalue((fptr->Fptr)->maxhdu, hdunum);
+        (fptr->Fptr)->headend = (fptr->Fptr)->logfilesize; /* set max size */
+
+        if (ffrhdu(fptr, exttype, status) > 0)
+        {   /* failed to get the new HDU, so restore previous values */
+            (fptr->Fptr)->curhdu = xcurhdu;
+            fptr->HDUposition    = xcurhdu;
+            (fptr->Fptr)->maxhdu = xmaxhdu;
+            (fptr->Fptr)->headend = xheadend;
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffiblk(fitsfile *fptr,      /* I - FITS file pointer               */
+           long nblock,         /* I - no. of blocks to insert         */ 
+           int headdata,        /* I - insert where? 0=header, 1=data  */
+                                /*     -1=beginning of file            */
+           int *status)         /* IO - error status                   */
+/*
+   insert 2880-byte blocks at the end of the current header or data unit
+*/
+{
+    int tstatus, savehdu, typhdu;
+    LONGLONG insertpt, jpoint;
+    long ii, nshift;
+    char charfill;
+    char buff1[2880], buff2[2880];
+    char *inbuff, *outbuff, *tmpbuff;
+    char card[FLEN_CARD];
+
+    if (*status > 0 || nblock <= 0)
+        return(*status);
+        
+    tstatus = *status;
+
+    if (headdata == 0 || (fptr->Fptr)->hdutype == ASCII_TBL)
+        charfill = 32;  /* headers and ASCII tables have space (32) fill */
+    else
+        charfill = 0;   /* images and binary tables have zero fill */
+
+    if (headdata == 0)  
+        insertpt = (fptr->Fptr)->datastart;  /* insert just before data, or */
+    else if (headdata == -1)
+    {
+        insertpt = 0;
+        strcpy(card, "XTENSION= 'IMAGE   '          / IMAGE extension");
+    }
+    else                                     /* at end of data, */
+    {
+        insertpt = (fptr->Fptr)->datastart + 
+                   (fptr->Fptr)->heapstart + 
+                   (fptr->Fptr)->heapsize;
+        insertpt = ((insertpt + 2879) / 2880) * 2880; /* start of block */
+
+       /* the following formula is wrong because the current data unit
+          may have been extended without updating the headstart value
+          of the following HDU.
+       */
+       /* insertpt = (fptr->Fptr)->headstart[(fptr->Fptr)->curhdu + 1]; */
+    }
+
+    inbuff  = buff1;   /* set pointers to input and output buffers */
+    outbuff = buff2;
+
+    memset(outbuff, charfill, 2880); /* initialize buffer with fill */
+
+    if (nblock == 1)  /* insert one block */
+    {
+      if (headdata == -1)
+        ffmrec(fptr, 1, card, status);    /* change SIMPLE -> XTENSION */
+
+      ffmbyt(fptr, insertpt, REPORT_EOF, status);  /* move to 1st point */
+      ffgbyt(fptr, 2880, inbuff, status);  /* read first block of bytes */
+
+      while (*status <= 0)
+      {
+        ffmbyt(fptr, insertpt, REPORT_EOF, status);  /* insert point */
+        ffpbyt(fptr, 2880, outbuff, status);  /* write the output buffer */
+
+        if (*status > 0)
+            return(*status);
+
+        tmpbuff = inbuff;   /* swap input and output pointers */
+        inbuff = outbuff;
+        outbuff = tmpbuff;
+        insertpt += 2880;  /* increment insert point by 1 block */
+
+        ffmbyt(fptr, insertpt, REPORT_EOF, status);  /* move to next block */
+        ffgbyt(fptr, 2880, inbuff, status);  /* read block of bytes */
+      }
+
+      *status = tstatus;  /* reset status value */
+      ffmbyt(fptr, insertpt, IGNORE_EOF, status); /* move back to insert pt */
+      ffpbyt(fptr, 2880, outbuff, status);  /* write the final block */
+    }
+
+    else   /*  inserting more than 1 block */
+
+    {
+        savehdu = (fptr->Fptr)->curhdu;  /* save the current HDU number */
+        tstatus = *status;
+        while(*status <= 0)  /* find the last HDU in file */
+              ffmrhd(fptr, 1, &typhdu, status);
+
+        if (*status == END_OF_FILE)
+        {
+            *status = tstatus;
+        }
+
+        ffmahd(fptr, savehdu + 1, &typhdu, status);  /* move back to CHDU */
+        if (headdata == -1)
+          ffmrec(fptr, 1, card, status); /* NOW change SIMPLE -> XTENSION */
+
+        /* number of 2880-byte blocks that have to be shifted down */
+        nshift = (long) (((fptr->Fptr)->headstart[(fptr->Fptr)->maxhdu + 1] - insertpt)
+                 / 2880);
+        /* position of last block in file to be shifted */
+        jpoint =  (fptr->Fptr)->headstart[(fptr->Fptr)->maxhdu + 1] - 2880;
+
+        /* move all the blocks starting at end of file working backwards */
+        for (ii = 0; ii < nshift; ii++)
+        {
+            /* move to the read start position */
+            if (ffmbyt(fptr, jpoint, REPORT_EOF, status) > 0)
+                return(*status);
+
+            ffgbyt(fptr, 2880, inbuff,status);  /* read one record */
+
+            /* move forward to the write postion */
+            ffmbyt(fptr, jpoint + ((LONGLONG) nblock * 2880), IGNORE_EOF, status);
+
+            ffpbyt(fptr, 2880, inbuff, status);  /* write the record */
+
+            jpoint -= 2880;
+        }
+
+        /* move back to the write start postion (might be EOF) */
+        ffmbyt(fptr, insertpt, IGNORE_EOF, status);
+
+        for (ii = 0; ii < nblock; ii++)   /* insert correct fill value */
+             ffpbyt(fptr, 2880, outbuff, status);
+    }
+
+    if (headdata == 0)         /* update data start address */
+      (fptr->Fptr)->datastart += ((LONGLONG) nblock * 2880);
+
+    /* update following HDU addresses */
+    for (ii = (fptr->Fptr)->curhdu; ii <= (fptr->Fptr)->maxhdu; ii++)
+         (fptr->Fptr)->headstart[ii + 1] += ((LONGLONG) nblock * 2880);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgkcl(char *tcard)
+
+/*
+   Return the type classification of the input header record
+
+   TYP_STRUC_KEY: SIMPLE, BITPIX, NAXIS, NAXISn, EXTEND, BLOCKED,
+                  GROUPS, PCOUNT, GCOUNT, END
+                  XTENSION, TFIELDS, TTYPEn, TBCOLn, TFORMn, THEAP,
+                   and the first 4 COMMENT keywords in the primary array
+                   that define the FITS format.
+
+   TYP_CMPRS_KEY:
+            The experimental keywords used in the compressed image format
+                  ZIMAGE, ZCMPTYPE, ZNAMEn, ZVALn, ZTILEn, 
+                  ZBITPIX, ZNAXISn, ZSCALE, ZZERO, ZBLANK,
+                  EXTNAME = 'COMPRESSED_IMAGE'
+		  ZSIMPLE, ZTENSION, ZEXTEND, ZBLOCKED, ZPCOUNT, ZGCOUNT
+
+   TYP_SCAL_KEY:  BSCALE, BZERO, TSCALn, TZEROn
+
+   TYP_NULL_KEY:  BLANK, TNULLn
+
+   TYP_DIM_KEY:   TDIMn
+
+   TYP_RANG_KEY:  TLMINn, TLMAXn, TDMINn, TDMAXn, DATAMIN, DATAMAX
+
+   TYP_UNIT_KEY:  BUNIT, TUNITn
+
+   TYP_DISP_KEY:  TDISPn
+
+   TYP_HDUID_KEY: EXTNAME, EXTVER, EXTLEVEL, HDUNAME, HDUVER, HDULEVEL
+
+   TYP_CKSUM_KEY  CHECKSUM, DATASUM
+
+   TYP_WCS_KEY:
+           Primary array:
+                  WCAXES, CTYPEn, CUNITn, CRVALn, CRPIXn, CROTAn, CDELTn
+                  CDj_is, PVj_ms, LONPOLEs, LATPOLEs
+  
+           Pixel list:
+                  TCTYPn, TCTYns, TCUNIn, TCUNns, TCRVLn, TCRVns, TCRPXn, TCRPks,
+                  TCDn_k, TCn_ks, TPVn_m, TPn_ms, TCDLTn, TCROTn
+
+           Bintable vector:
+                  jCTYPn, jCTYns, jCUNIn, jCUNns, jCRVLn, jCRVns, iCRPXn, iCRPns,
+                  jiCDn, jiCDns, jPVn_m, jPn_ms, jCDLTn, jCROTn
+                
+   TYP_REFSYS_KEY:
+                   EQUINOXs, EPOCH, MJD-OBSs, RADECSYS, RADESYSs
+
+   TYP_COMM_KEY:  COMMENT, HISTORY, (blank keyword)
+
+   TYP_CONT_KEY:  CONTINUE
+
+   TYP_USER_KEY:  all other keywords
+
+*/ 
+{
+    char card[20], *card1, *card5;
+
+    card[0] = '\0';
+    strncat(card, tcard, 8);   /* copy the keyword name */
+    strcat(card, "        "); /* append blanks to make at least 8 chars long */
+    ffupch(card);  /* make sure it is in upper case */
+
+    card1 = card + 1;  /* pointer to 2nd character */
+    card5 = card + 5;  /* pointer to 6th character */
+
+    /* the strncmp function is slow, so try to be more efficient */
+    if (*card == 'Z')
+    {
+	if (FSTRNCMP (card1, "IMAGE  ", 7) == 0)
+	    return (TYP_CMPRS_KEY);
+	else if (FSTRNCMP (card1, "CMPTYPE", 7) == 0)
+	    return (TYP_CMPRS_KEY);
+	else if (FSTRNCMP (card1, "NAME", 4) == 0)
+        {
+            if (*card5 >= '0' && *card5 <= '9')
+	        return (TYP_CMPRS_KEY);
+        }
+	else if (FSTRNCMP (card1, "VAL", 3) == 0)
+        {
+            if (*(card + 4) >= '0' && *(card + 4) <= '9')
+	        return (TYP_CMPRS_KEY);
+        }
+	else if (FSTRNCMP (card1, "TILE", 4) == 0)
+        {
+            if (*card5 >= '0' && *card5 <= '9')
+	        return (TYP_CMPRS_KEY);
+        }
+	else if (FSTRNCMP (card1, "BITPIX ", 7) == 0)
+	    return (TYP_CMPRS_KEY);
+	else if (FSTRNCMP (card1, "NAXIS", 5) == 0)
+        {
+            if ( ( *(card + 6) >= '0' && *(card + 6) <= '9' )
+             || (*(card + 6) == ' ') )
+	        return (TYP_CMPRS_KEY);
+        }
+	else if (FSTRNCMP (card1, "SCALE  ", 7) == 0)
+	    return (TYP_CMPRS_KEY);
+	else if (FSTRNCMP (card1, "ZERO   ", 7) == 0)
+	    return (TYP_CMPRS_KEY);
+	else if (FSTRNCMP (card1, "BLANK  ", 7) == 0)
+	    return (TYP_CMPRS_KEY);
+	else if (FSTRNCMP (card1, "SIMPLE ", 7) == 0)
+	    return (TYP_CMPRS_KEY);
+	else if (FSTRNCMP (card1, "TENSION", 7) == 0)
+	    return (TYP_CMPRS_KEY);
+	else if (FSTRNCMP (card1, "EXTEND ", 7) == 0)
+	    return (TYP_CMPRS_KEY);
+	else if (FSTRNCMP (card1, "BLOCKED", 7) == 0)
+	    return (TYP_CMPRS_KEY);
+	else if (FSTRNCMP (card1, "PCOUNT ", 7) == 0)
+	    return (TYP_CMPRS_KEY);
+	else if (FSTRNCMP (card1, "GCOUNT ", 7) == 0)
+	    return (TYP_CMPRS_KEY);
+    }
+    else if (*card == ' ')
+    {
+	return (TYP_COMM_KEY);
+    }
+    else if (*card == 'B')
+    {
+	if (FSTRNCMP (card1, "ITPIX  ", 7) == 0)
+	    return (TYP_STRUC_KEY);
+	if (FSTRNCMP (card1, "LOCKED ", 7) == 0)
+	    return (TYP_STRUC_KEY);
+
+	if (FSTRNCMP (card1, "LANK   ", 7) == 0)
+	    return (TYP_NULL_KEY);
+
+	if (FSTRNCMP (card1, "SCALE  ", 7) == 0)
+	    return (TYP_SCAL_KEY);
+	if (FSTRNCMP (card1, "ZERO   ", 7) == 0)
+	    return (TYP_SCAL_KEY);
+
+	if (FSTRNCMP (card1, "UNIT   ", 7) == 0)
+	    return (TYP_UNIT_KEY);
+    }
+    else if (*card == 'C')
+    {
+	if (FSTRNCMP (card1, "OMMENT",6) == 0)
+	{
+          /* new comment string starting Oct 2001 */
+	    if (FSTRNCMP (tcard, "COMMENT   and Astrophysics', volume 376, page 3",
+              47) == 0)
+	        return (TYP_STRUC_KEY);
+
+         /* original COMMENT strings from 1993 - 2001 */
+	    if (FSTRNCMP (tcard, "COMMENT   FITS (Flexible Image Transport System",
+              47) == 0)
+	        return (TYP_STRUC_KEY);
+	    if (FSTRNCMP (tcard, "COMMENT   Astrophysics Supplement Series v44/p3",
+              47) == 0)
+	        return (TYP_STRUC_KEY);
+	    if (FSTRNCMP (tcard, "COMMENT   Contact the NASA Science Office of St",
+              47) == 0)
+	        return (TYP_STRUC_KEY);
+	    if (FSTRNCMP (tcard, "COMMENT   FITS Definition document #100 and oth",
+              47) == 0)
+	        return (TYP_STRUC_KEY);
+
+            if (*(card + 7) == ' ')
+	        return (TYP_COMM_KEY);
+            else
+                return (TYP_USER_KEY);
+	}
+
+	if (FSTRNCMP (card1, "HECKSUM", 7) == 0)
+	    return (TYP_CKSUM_KEY);
+
+	if (FSTRNCMP (card1, "ONTINUE", 7) == 0)
+	    return (TYP_CONT_KEY);
+
+	if (FSTRNCMP (card1, "TYPE",4) == 0)
+        {
+            if (*card5 >= '0' && *card5 <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (FSTRNCMP (card1, "UNIT",4) == 0)
+        {
+            if (*card5 >= '0' && *card5 <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (FSTRNCMP (card1, "RVAL",4) == 0)
+        {
+            if (*card5 >= '0' && *card5 <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (FSTRNCMP (card1, "RPIX",4) == 0)
+        {
+            if (*card5 >= '0' && *card5 <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (FSTRNCMP (card1, "ROTA",4) == 0)
+        {
+            if (*card5 >= '0' && *card5 <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (FSTRNCMP (card1, "RDER",4) == 0)
+        {
+            if (*card5 >= '0' && *card5 <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (FSTRNCMP (card1, "SYER",4) == 0)
+        {
+            if (*card5 >= '0' && *card5 <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (FSTRNCMP (card1, "DELT",4) == 0)
+        {
+            if (*card5 >= '0' && *card5 <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (*card1 == 'D')
+        {
+            if (*(card + 2) >= '0' && *(card + 2) <= '9')
+	        return (TYP_WCS_KEY);
+        }
+    }
+    else if (*card == 'D')
+    {
+	if (FSTRNCMP (card1, "ATASUM ", 7) == 0)
+	    return (TYP_CKSUM_KEY);
+	if (FSTRNCMP (card1, "ATAMIN ", 7) == 0)
+	    return (TYP_RANG_KEY);
+	if (FSTRNCMP (card1, "ATAMAX ", 7) == 0)
+	    return (TYP_RANG_KEY);
+	if (FSTRNCMP (card1, "ATE-OBS", 7) == 0)
+	    return (TYP_REFSYS_KEY);    }
+    else if (*card == 'E')
+    {
+	if (FSTRNCMP (card1, "XTEND  ", 7) == 0)
+	    return (TYP_STRUC_KEY);
+	if (FSTRNCMP (card1, "ND     ", 7) == 0)
+	    return (TYP_STRUC_KEY);
+	if (FSTRNCMP (card1, "XTNAME ", 7) == 0)
+	{
+            /* check for special compressed image value */
+            if (FSTRNCMP(tcard, "EXTNAME = 'COMPRESSED_IMAGE'", 28) == 0)
+	      return (TYP_CMPRS_KEY);
+            else
+	      return (TYP_HDUID_KEY);
+	}
+	if (FSTRNCMP (card1, "XTVER  ", 7) == 0)
+	    return (TYP_HDUID_KEY);
+	if (FSTRNCMP (card1, "XTLEVEL", 7) == 0)
+	    return (TYP_HDUID_KEY);
+
+	if (FSTRNCMP (card1, "QUINOX", 6) == 0)
+	    return (TYP_REFSYS_KEY);
+	if (FSTRNCMP (card1, "QUI",3) == 0)
+        {
+            if (*(card+4) >= '0' && *(card+4) <= '9')
+	        return (TYP_REFSYS_KEY);
+        }
+	if (FSTRNCMP (card1, "POCH   ", 7) == 0)
+	    return (TYP_REFSYS_KEY);
+    }
+    else if (*card == 'G')
+    {
+	if (FSTRNCMP (card1, "COUNT  ", 7) == 0)
+	    return (TYP_STRUC_KEY);
+	if (FSTRNCMP (card1, "ROUPS  ", 7) == 0)
+	    return (TYP_STRUC_KEY);
+    }
+    else if (*card == 'H')
+    {
+	if (FSTRNCMP (card1, "DUNAME ", 7) == 0)
+	    return (TYP_HDUID_KEY);
+	if (FSTRNCMP (card1, "DUVER  ", 7) == 0)
+	    return (TYP_HDUID_KEY);
+	if (FSTRNCMP (card1, "DULEVEL", 7) == 0)
+	    return (TYP_HDUID_KEY);
+
+	if (FSTRNCMP (card1, "ISTORY",6) == 0)
+        {
+            if (*(card + 7) == ' ')
+	        return (TYP_COMM_KEY);
+            else
+                return (TYP_USER_KEY);
+        }
+    }
+    else if (*card == 'L')
+    {
+	if (FSTRNCMP (card1, "ONPOLE",6) == 0)
+	    return (TYP_WCS_KEY);
+	if (FSTRNCMP (card1, "ATPOLE",6) == 0)
+	    return (TYP_WCS_KEY);
+	if (FSTRNCMP (card1, "ONP",3) == 0)
+        {
+            if (*(card+4) >= '0' && *(card+4) <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (FSTRNCMP (card1, "ATP",3) == 0)
+        {
+            if (*(card+4) >= '0' && *(card+4) <= '9')
+	        return (TYP_WCS_KEY);
+        }
+    }
+    else if (*card == 'M')
+    {
+	if (FSTRNCMP (card1, "JD-OBS ", 7) == 0)
+	    return (TYP_REFSYS_KEY);
+	if (FSTRNCMP (card1, "JDOB",4) == 0)
+        {
+            if (*(card+5) >= '0' && *(card+5) <= '9')
+	        return (TYP_REFSYS_KEY);
+        }
+    }
+    else if (*card == 'N')
+    {
+	if (FSTRNCMP (card1, "AXIS", 4) == 0)
+        {
+            if ((*card5 >= '0' && *card5 <= '9')
+             || (*card5 == ' '))
+	        return (TYP_STRUC_KEY);
+        }
+    }
+    else if (*card == 'P')
+    {
+	if (FSTRNCMP (card1, "COUNT  ", 7) == 0)
+	    return (TYP_STRUC_KEY);
+	if (*card1 == 'C')
+        {
+            if (*(card + 2) >= '0' && *(card + 2) <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (*card1 == 'V')
+        {
+            if (*(card + 2) >= '0' && *(card + 2) <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (*card1 == 'S')
+        {
+            if (*(card + 2) >= '0' && *(card + 2) <= '9')
+	        return (TYP_WCS_KEY);
+        }
+    }
+    else if (*card == 'R')
+    {
+	if (FSTRNCMP (card1, "ADECSYS", 7) == 0)
+	    return (TYP_REFSYS_KEY);
+	if (FSTRNCMP (card1, "ADESYS", 6) == 0)
+	    return (TYP_REFSYS_KEY);
+	if (FSTRNCMP (card1, "ADE",3) == 0)
+        {
+            if (*(card+4) >= '0' && *(card+4) <= '9')
+	        return (TYP_REFSYS_KEY);
+        }
+    }
+    else if (*card == 'S')
+    {
+	if (FSTRNCMP (card1, "IMPLE  ", 7) == 0)
+	    return (TYP_STRUC_KEY);
+    }
+    else if (*card == 'T')
+    {
+	if (FSTRNCMP (card1, "TYPE", 4) == 0)
+        {
+            if (*card5 >= '0' && *card5 <= '9')
+	        return (TYP_STRUC_KEY);
+        }
+	else if (FSTRNCMP (card1, "FORM", 4) == 0)
+        {
+            if (*card5 >= '0' && *card5 <= '9')
+	        return (TYP_STRUC_KEY);
+        }
+	else if (FSTRNCMP (card1, "BCOL", 4) == 0)
+        {
+            if (*card5 >= '0' && *card5 <= '9')
+	        return (TYP_STRUC_KEY);
+        }
+	else if (FSTRNCMP (card1, "FIELDS ", 7) == 0)
+	    return (TYP_STRUC_KEY);
+	else if (FSTRNCMP (card1, "HEAP   ", 7) == 0)
+	    return (TYP_STRUC_KEY);
+
+	else if (FSTRNCMP (card1, "NULL", 4) == 0)
+        {
+            if (*card5 >= '0' && *card5 <= '9')
+	        return (TYP_NULL_KEY);
+        }
+
+	else if (FSTRNCMP (card1, "DIM", 3) == 0)
+        {
+            if (*(card + 4) >= '0' && *(card + 4) <= '9')
+ 	        return (TYP_DIM_KEY);
+        }
+
+	else if (FSTRNCMP (card1, "UNIT", 4) == 0)
+        {
+            if (*card5 >= '0' && *card5 <= '9')
+	        return (TYP_UNIT_KEY);
+        }
+
+	else if (FSTRNCMP (card1, "DISP", 4) == 0)
+        {
+            if (*card5 >= '0' && *card5 <= '9')
+	        return (TYP_DISP_KEY);
+        }
+
+	else if (FSTRNCMP (card1, "SCAL", 4) == 0)
+        {
+            if (*card5 >= '0' && *card5 <= '9')
+	        return (TYP_SCAL_KEY);
+        }
+	else if (FSTRNCMP (card1, "ZERO", 4) == 0)
+        {
+            if (*card5 >= '0' && *card5 <= '9')
+	        return (TYP_SCAL_KEY);
+        }
+
+	else if (FSTRNCMP (card1, "LMIN", 4) == 0)
+        {
+            if (*card5 >= '0' && *card5 <= '9')
+	        return (TYP_RANG_KEY);
+        }
+	else if (FSTRNCMP (card1, "LMAX", 4) == 0)
+        {
+            if (*card5 >= '0' && *card5 <= '9')
+	        return (TYP_RANG_KEY);
+        }
+	else if (FSTRNCMP (card1, "DMIN", 4) == 0)
+        {
+            if (*card5 >= '0' && *card5 <= '9')
+	        return (TYP_RANG_KEY);
+        }
+	else if (FSTRNCMP (card1, "DMAX", 4) == 0)
+        {
+            if (*card5 >= '0' && *card5 <= '9')
+	        return (TYP_RANG_KEY);
+        }
+
+	else if (FSTRNCMP (card1, "CTYP",4) == 0)
+        {
+            if (*card5 >= '0' && *card5 <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (FSTRNCMP (card1, "CTY",3) == 0)
+        {
+            if (*(card+4) >= '0' && *(card+4) <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (FSTRNCMP (card1, "CUNI",4) == 0)
+        {
+            if (*card5 >= '0' && *card5 <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (FSTRNCMP (card1, "CUN",3) == 0)
+        {
+            if (*(card+4) >= '0' && *(card+4) <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (FSTRNCMP (card1, "CRVL",4) == 0)
+        {
+            if (*card5 >= '0' && *card5 <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (FSTRNCMP (card1, "CRV",3) == 0)
+        {
+            if (*(card+4) >= '0' && *(card+4) <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (FSTRNCMP (card1, "CRPX",4) == 0)
+        {
+            if (*card5 >= '0' && *card5 <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (FSTRNCMP (card1, "CRP",3) == 0)
+        {
+            if (*(card+4) >= '0' && *(card+4) <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (FSTRNCMP (card1, "CROT",4) == 0)
+        {
+            if (*card5 >= '0' && *card5 <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (FSTRNCMP (card1, "CDLT",4) == 0)
+        {
+            if (*card5 >= '0' && *card5 <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (FSTRNCMP (card1, "CDE",3) == 0)
+        {
+            if (*(card+4) >= '0' && *(card+4) <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (FSTRNCMP (card1, "CRD",3) == 0)
+        {
+            if (*(card+4) >= '0' && *(card+4) <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (FSTRNCMP (card1, "CSY",3) == 0)
+        {
+            if (*(card+4) >= '0' && *(card+4) <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (FSTRNCMP (card1, "WCS",3) == 0)
+        {
+            if (*(card+4) >= '0' && *(card+4) <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (FSTRNCMP (card1, "C",1) == 0)
+        {
+            if (*(card + 2) >= '0' && *(card + 2) <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (FSTRNCMP (card1, "P",1) == 0)
+        {
+            if (*(card + 2) >= '0' && *(card + 2) <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (FSTRNCMP (card1, "V",1) == 0)
+        {
+            if (*(card + 2) >= '0' && *(card + 2) <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (FSTRNCMP (card1, "S",1) == 0)
+        {
+            if (*(card + 2) >= '0' && *(card + 2) <= '9')
+	        return (TYP_WCS_KEY);
+        }
+    }
+    else if (*card == 'X')
+    {
+	if (FSTRNCMP (card1, "TENSION", 7) == 0)
+	    return (TYP_STRUC_KEY);
+    }
+    else if (*card == 'W')
+    {
+	if (FSTRNCMP (card1, "CSAXES", 6) == 0)
+	    return (TYP_WCS_KEY);
+	if (FSTRNCMP (card1, "CSNAME", 6) == 0)
+	    return (TYP_WCS_KEY);
+	if (FSTRNCMP (card1, "CAX", 3) == 0)
+	{
+            if (*(card + 4) >= '0' && *(card + 4) <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (FSTRNCMP (card1, "CSN", 3) == 0)
+	{
+            if (*(card + 4) >= '0' && *(card + 4) <= '9')
+	        return (TYP_WCS_KEY);
+        }
+    }
+    
+    else if (*card >= '0' && *card <= '9')
+    {
+      if (*card1 == 'C')
+      {
+        if (FSTRNCMP (card1, "CTYP",4) == 0)
+        {
+            if (*card5 >= '0' && *card5 <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (FSTRNCMP (card1, "CTY",3) == 0)
+        {
+            if (*(card+4) >= '0' && *(card+4) <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (FSTRNCMP (card1, "CUNI",4) == 0)
+        {
+            if (*card5 >= '0' && *card5 <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (FSTRNCMP (card1, "CUN",3) == 0)
+        {
+            if (*(card+4) >= '0' && *(card+4) <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (FSTRNCMP (card1, "CRVL",4) == 0)
+        {
+            if (*card5 >= '0' && *card5 <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (FSTRNCMP (card1, "CRV",3) == 0)
+        {
+            if (*(card+4) >= '0' && *(card+4) <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (FSTRNCMP (card1, "CRPX",4) == 0)
+        {
+            if (*card5 >= '0' && *card5 <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (FSTRNCMP (card1, "CRP",3) == 0)
+        {
+            if (*(card+4) >= '0' && *(card+4) <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (FSTRNCMP (card1, "CROT",4) == 0)
+        {
+            if (*card5 >= '0' && *card5 <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (FSTRNCMP (card1, "CDLT",4) == 0)
+        {
+            if (*card5 >= '0' && *card5 <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (FSTRNCMP (card1, "CDE",3) == 0)
+        {
+            if (*(card+4) >= '0' && *(card+4) <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (FSTRNCMP (card1, "CRD",3) == 0)
+        {
+            if (*(card+4) >= '0' && *(card+4) <= '9')
+	        return (TYP_WCS_KEY);
+        }
+	else if (FSTRNCMP (card1, "CSY",3) == 0)
+        {
+            if (*(card+4) >= '0' && *(card+4) <= '9')
+	        return (TYP_WCS_KEY);
+        }
+      }
+      else if (FSTRNCMP (card1, "V",1) == 0)
+      {
+            if (*(card + 2) >= '0' && *(card + 2) <= '9')
+	        return (TYP_WCS_KEY);
+      }
+      else if (FSTRNCMP (card1, "S",1) == 0)
+      {
+            if (*(card + 2) >= '0' && *(card + 2) <= '9')
+	        return (TYP_WCS_KEY);
+      }
+      else if (*card1 >= '0' && *card1 <= '9')
+      {   /* 2 digits at beginning of keyword */
+	
+	    if ( (*(card + 2) == 'P') && (*(card + 3) == 'C') )
+	    {
+               if (*(card + 4) >= '0' && *(card + 4) <= '9')
+	        return (TYP_WCS_KEY);  /*  ijPCn keyword */
+            }
+	    else if ( (*(card + 2) == 'C') && (*(card + 3) == 'D') )
+	    {
+               if (*(card + 4) >= '0' && *(card + 4) <= '9')
+	        return (TYP_WCS_KEY);  /*  ijCDn keyword */
+            }
+      }
+      
+    }
+    
+    return (TYP_USER_KEY);  /* by default all others are user keywords */
+}
+/*--------------------------------------------------------------------------*/
+int ffdtyp(char *cval,  /* I - formatted string representation of the value */
+           char *dtype, /* O - datatype code: C, L, F, I, or X */
+          int *status)  /* IO - error status */
+/*
+  determine implicit datatype of input string.
+  This assumes that the string conforms to the FITS standard
+  for keyword values, so may not detect all invalid formats.
+*/
+{
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (cval[0] == '\0')
+        return(*status = VALUE_UNDEFINED);
+    else if (cval[0] == '\'')
+        *dtype = 'C';          /* character string starts with a quote */
+    else if (cval[0] == 'T' || cval[0] == 'F')
+        *dtype = 'L';          /* logical = T or F character */
+    else if (cval[0] == '(')
+        *dtype = 'X';          /* complex datatype "(1.2, -3.4)" */
+    else if (strchr(cval,'.'))
+        *dtype = 'F';          /* float usualy contains a decimal point */
+    else if (strchr(cval,'E') || strchr(cval,'D') )
+        *dtype = 'F';          /* exponential contains a E or D */
+    else
+        *dtype = 'I';          /* if none of the above assume it is integer */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffinttyp(char *cval,  /* I - formatted string representation of the integer */
+           int *dtype, /* O - datatype code: TBYTE, TSHORT, TUSHORT, etc */
+           int *negative, /* O - is cval negative? */
+           int *status)  /* IO - error status */
+/*
+  determine implicit datatype of input integer string.
+  This assumes that the string conforms to the FITS standard
+  for integer keyword value, so may not detect all invalid formats.
+*/
+{
+    int ii, len;
+    char *p;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    *dtype = 0;  /* initialize to NULL */
+    p = cval;
+
+    if (*p == '+') {
+        p++;   /* ignore leading + sign */
+    } else if (*p == '-') {
+        p++;
+	*negative = 1;   /* this is a negative number */
+    }
+
+    if (*p == '0') {
+        while (*p == '0') p++;  /* skip leading zeros */
+
+        if (*p == 0) {  /* the value is a string of 1 or more zeros */
+           *dtype  = TSBYTE;
+	   return(*status);
+        }
+    }
+
+    len = strlen(p);
+    for (ii = 0; ii < len; ii++)  {
+        if (!isdigit(*(p+ii))) {
+	    *status = BAD_INTKEY;
+	    return(*status);
+	}
+    }
+
+    /* check for unambiguous cases, based on length of the string */
+    if (len == 0) {
+        *status = VALUE_UNDEFINED;
+    } else if (len < 3) {
+        *dtype = TSBYTE;
+    } else if (len == 4) {
+	*dtype = TSHORT;
+    } else if (len > 5 && len < 10) {
+        *dtype = TINT;
+    } else if (len > 10 && len < 19) {
+        *dtype = TLONGLONG;
+    } else if (len > 19) {
+	*status = BAD_INTKEY;
+    } else {
+    
+      if (!(*negative)) {  /* positive integers */
+	if (len == 3) {
+	    if (strcmp(p,"127") <= 0 ) {
+	        *dtype = TSBYTE;
+	    } else if (strcmp(p,"255") <= 0 ) {
+	        *dtype = TBYTE;
+	    } else {
+	        *dtype = TSHORT;
+	    }
+	} else if (len == 5) {
+ 	    if (strcmp(p,"32767") <= 0 ) {
+	        *dtype = TSHORT;
+ 	    } else if (strcmp(p,"65535") <= 0 ) {
+	        *dtype = TUSHORT;
+	    } else {
+	        *dtype = TINT;
+	    }
+	} else if (len == 10) {
+	    if (strcmp(p,"2147483647") <= 0 ) {
+	        *dtype = TINT;
+	    } else if (strcmp(p,"4294967295") <= 0 ) {
+	        *dtype = TUINT;
+	    } else {
+	        *dtype = TLONGLONG;
+	    }
+	} else if (len == 19) {
+	    if (strcmp(p,"9223372036854775807") <= 0 ) {
+	        *dtype = TLONGLONG;
+	    } else {
+		*status = BAD_INTKEY;
+	    }
+	}
+
+      } else {  /* negative integers */
+	if (len == 3) {
+	    if (strcmp(p,"128") <= 0 ) {
+	        *dtype = TSBYTE;
+	    } else {
+	        *dtype = TSHORT;
+	    }
+	} else if (len == 5) {
+ 	    if (strcmp(p,"32768") <= 0 ) {
+	        *dtype = TSHORT;
+	    } else {
+	        *dtype = TINT;
+	    }
+	} else if (len == 10) {
+	    if (strcmp(p,"2147483648") <= 0 ) {
+	        *dtype = TINT;
+	    } else {
+	        *dtype = TLONGLONG;
+	    }
+	} else if (len == 19) {
+	    if (strcmp(p,"9223372036854775808") <= 0 ) {
+	        *dtype = TLONGLONG;
+	    } else {
+		*status = BAD_INTKEY;
+	    }
+	}
+      }
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffc2x(char *cval,   /* I - formatted string representation of the value */
+          char *dtype,  /* O - datatype code: C, L, F, I or X  */
+
+    /* Only one of the following will be defined, depending on datatype */
+          long *ival,    /* O - integer value       */
+          int *lval,     /* O - logical value       */
+          char *sval,    /* O - string value        */
+          double *dval,  /* O - double value        */
+
+          int *status)   /* IO - error status */
+/*
+  high level routine to convert formatted character string to its
+  intrinsic data type
+*/
+{
+    ffdtyp(cval, dtype, status);     /* determine the datatype */
+
+    if (*dtype == 'I')
+        ffc2ii(cval, ival, status);
+    else if (*dtype == 'F')
+        ffc2dd(cval, dval, status);
+    else if (*dtype == 'L')
+        ffc2ll(cval, lval, status);
+    else 
+        ffc2s(cval, sval, status);   /* C and X formats */
+        
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffc2xx(char *cval,   /* I - formatted string representation of the value */
+          char *dtype,  /* O - datatype code: C, L, F, I or X  */
+
+    /* Only one of the following will be defined, depending on datatype */
+          LONGLONG *ival, /* O - integer value       */
+          int *lval,     /* O - logical value       */
+          char *sval,    /* O - string value        */
+          double *dval,  /* O - double value        */
+
+          int *status)   /* IO - error status */
+/*
+  high level routine to convert formatted character string to its
+  intrinsic data type
+*/
+{
+    ffdtyp(cval, dtype, status);     /* determine the datatype */
+
+    if (*dtype == 'I')
+        ffc2jj(cval, ival, status);
+    else if (*dtype == 'F')
+        ffc2dd(cval, dval, status);
+    else if (*dtype == 'L')
+        ffc2ll(cval, lval, status);
+    else 
+        ffc2s(cval, sval, status);   /* C and X formats */
+        
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffc2i(char *cval,   /* I - string representation of the value */
+          long *ival,   /* O - numerical value of the input string */
+          int *status)  /* IO - error status */
+/*
+  convert formatted string to an integer value, doing implicit
+  datatype conversion if necessary.
+*/
+{
+    char dtype, sval[81], msg[81];
+    int lval;
+    double dval;
+    
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (cval[0] == '\0')
+        return(*status = VALUE_UNDEFINED);  /* null value string */
+        
+    /* convert the keyword to its native datatype */
+    ffc2x(cval, &dtype, ival, &lval, sval, &dval, status);
+
+    if (dtype == 'X' )
+    {
+            *status = BAD_INTKEY;
+    }
+    else if (dtype == 'C')
+    {
+            /* try reading the string as a number */
+            if (ffc2dd(sval, &dval, status) <= 0)
+            {
+              if (dval > (double) LONG_MAX || dval < (double) LONG_MIN)
+                *status = NUM_OVERFLOW;
+              else
+                *ival = (long) dval;
+            }
+    }
+    else if (dtype == 'F')
+    {
+            if (dval > (double) LONG_MAX || dval < (double) LONG_MIN)
+                *status = NUM_OVERFLOW;
+            else
+                *ival = (long) dval;
+    }
+    else if (dtype == 'L')
+    {
+            *ival = (long) lval;
+    }
+
+    if (*status > 0)
+    {
+            *ival = 0;
+            strcpy(msg,"Error in ffc2i evaluating string as an integer: ");
+            strncat(msg,cval,30);
+            ffpmsg(msg);
+            return(*status);
+    }
+
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffc2j(char *cval,     /* I - string representation of the value */
+          LONGLONG *ival, /* O - numerical value of the input string */
+          int *status)    /* IO - error status */
+/*
+  convert formatted string to a LONGLONG integer value, doing implicit
+  datatype conversion if necessary.
+*/
+{
+    char dtype, sval[81], msg[81];
+    int lval;
+    double dval;
+    
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (cval[0] == '\0')
+        return(*status = VALUE_UNDEFINED);  /* null value string */
+        
+    /* convert the keyword to its native datatype */
+    ffc2xx(cval, &dtype, ival, &lval, sval, &dval, status);
+
+    if (dtype == 'X' )
+    {
+            *status = BAD_INTKEY;
+    }
+    else if (dtype == 'C')
+    {
+            /* try reading the string as a number */
+            if (ffc2dd(sval, &dval, status) <= 0)
+            {
+              if (dval > (double) LONGLONG_MAX || dval < (double) LONGLONG_MIN)
+                *status = NUM_OVERFLOW;
+              else
+                *ival = (LONGLONG) dval;
+            }
+    }
+    else if (dtype == 'F')
+    {
+            if (dval > (double) LONGLONG_MAX || dval < (double) LONGLONG_MIN)
+                *status = NUM_OVERFLOW;
+            else
+                *ival = (LONGLONG) dval;
+    }
+    else if (dtype == 'L')
+    {
+            *ival = (LONGLONG) lval;
+    }
+
+    if (*status > 0)
+    {
+            *ival = 0;
+            strcpy(msg,"Error in ffc2j evaluating string as a long integer: ");
+            strncat(msg,cval,30);
+            ffpmsg(msg);
+            return(*status);
+    }
+
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffc2l(char *cval,  /* I - string representation of the value */
+         int *lval,    /* O - numerical value of the input string */
+         int *status)  /* IO - error status */
+/*
+  convert formatted string to a logical value, doing implicit
+  datatype conversion if necessary
+*/
+{
+    char dtype, sval[81], msg[81];
+    long ival;
+    double dval;
+    
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (cval[0] == '\0')
+        return(*status = VALUE_UNDEFINED);  /* null value string */
+
+    /* convert the keyword to its native datatype */
+    ffc2x(cval, &dtype, &ival, lval, sval, &dval, status);
+
+    if (dtype == 'C' || dtype == 'X' )
+        *status = BAD_LOGICALKEY;
+
+    if (*status > 0)
+    {
+            *lval = 0;
+            strcpy(msg,"Error in ffc2l evaluating string as a logical: ");
+            strncat(msg,cval,30);
+            ffpmsg(msg);
+            return(*status);
+    }
+
+    if (dtype == 'I')
+    {
+        if (ival)
+            *lval = 1;
+        else
+            *lval = 0;
+    }
+    else if (dtype == 'F')
+    {
+        if (dval)
+            *lval = 1;
+        else
+            *lval = 0;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffc2r(char *cval,   /* I - string representation of the value */
+          float *fval,  /* O - numerical value of the input string */
+          int *status)  /* IO - error status */
+/*
+  convert formatted string to a real float value, doing implicit
+  datatype conversion if necessary
+*/
+{
+    char dtype, sval[81], msg[81];
+    int lval;
+    
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (cval[0] == '\0')
+        return(*status = VALUE_UNDEFINED);  /* null value string */
+
+    ffdtyp(cval, &dtype, status);     /* determine the datatype */
+
+    if (dtype == 'I' || dtype == 'F')
+        ffc2rr(cval, fval, status);
+    else if (dtype == 'L')
+    {
+        ffc2ll(cval, &lval, status);
+        *fval = (float) lval;
+    }
+    else if (dtype == 'C')
+    {
+        /* try reading the string as a number */
+        ffc2s(cval, sval, status); 
+        ffc2rr(sval, fval, status);
+    }
+    else 
+        *status = BAD_FLOATKEY;
+
+    if (*status > 0)
+    {
+            *fval = 0.;
+            strcpy(msg,"Error in ffc2r evaluating string as a float: ");
+            strncat(msg,cval,30);
+            ffpmsg(msg);
+            return(*status);
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffc2d(char *cval,   /* I - string representation of the value */
+          double *dval, /* O - numerical value of the input string */
+          int *status)  /* IO - error status */
+/*
+  convert formatted string to a double value, doing implicit
+  datatype conversion if necessary
+*/
+{
+    char dtype, sval[81], msg[81];
+    int lval;
+    
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (cval[0] == '\0')
+        return(*status = VALUE_UNDEFINED);  /* null value string */
+
+    ffdtyp(cval, &dtype, status);     /* determine the datatype */
+
+    if (dtype == 'I' || dtype == 'F')
+        ffc2dd(cval, dval, status);
+    else if (dtype == 'L')
+    {
+        ffc2ll(cval, &lval, status);
+        *dval = (double) lval;
+    }
+    else if (dtype == 'C')
+    {
+        /* try reading the string as a number */
+        ffc2s(cval, sval, status); 
+        ffc2dd(sval, dval, status);
+    }
+    else 
+        *status = BAD_DOUBLEKEY;
+
+    if (*status > 0)
+    {
+            *dval = 0.;
+            strcpy(msg,"Error in ffc2d evaluating string as a double: ");
+            strncat(msg,cval,30);
+            ffpmsg(msg);
+            return(*status);
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffc2ii(char *cval,  /* I - string representation of the value */
+          long *ival,   /* O - numerical value of the input string */
+          int *status)  /* IO - error status */
+/*
+  convert null-terminated formatted string to an integer value
+*/
+{
+    char *loc, msg[81];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    errno = 0;
+    *ival = 0;
+    *ival = strtol(cval, &loc, 10);  /* read the string as an integer */
+
+    /* check for read error, or junk following the integer */
+    if (*loc != '\0' && *loc != ' ' ) 
+        *status = BAD_C2I;
+
+    if (errno == ERANGE)
+    {
+        strcpy(msg,"Range Error in ffc2ii converting string to long int: ");
+        strncat(msg,cval,25);
+        ffpmsg(msg);
+
+        *status = NUM_OVERFLOW;
+        errno = 0;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffc2jj(char *cval,  /* I - string representation of the value */
+          LONGLONG *ival,   /* O - numerical value of the input string */
+          int *status)  /* IO - error status */
+/*
+  convert null-terminated formatted string to an long long integer value
+*/
+{
+    char *loc, msg[81];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    errno = 0;
+    *ival = 0;
+
+#if defined(_MSC_VER)
+
+    /* Microsoft Visual C++ 6.0 does not have the strtoll function */
+    *ival =  _atoi64(cval);
+    loc = cval;
+    while (*loc == ' ') loc++;     /* skip spaces */
+    if    (*loc == '-') loc++;     /* skip minus sign */
+    if    (*loc == '+') loc++;     /* skip plus sign */
+    while (isdigit(*loc)) loc++;   /* skip digits */
+
+#elif (USE_LL_SUFFIX == 1)
+    *ival = strtoll(cval, &loc, 10);  /* read the string as an integer */
+#else
+    *ival = strtol(cval, &loc, 10);  /* read the string as an integer */
+#endif
+
+    /* check for read error, or junk following the integer */
+    if (*loc != '\0' && *loc != ' ' ) 
+        *status = BAD_C2I;
+
+    if (errno == ERANGE)
+    {
+        strcpy(msg,"Range Error in ffc2jj converting string to longlong int: ");
+        strncat(msg,cval,25);
+        ffpmsg(msg);
+
+        *status = NUM_OVERFLOW;
+        errno = 0;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffc2ll(char *cval,  /* I - string representation of the value: T or F */
+           int *lval,   /* O - numerical value of the input string: 1 or 0 */
+           int *status) /* IO - error status */
+/*
+  convert null-terminated formatted string to a logical value
+*/
+{
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (cval[0] == 'T')
+        *lval = 1;
+    else                
+        *lval = 0;        /* any character besides T is considered false */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffc2s(char *instr,  /* I - null terminated quoted input string */
+          char *outstr, /* O - null terminated output string without quotes */
+          int *status)  /* IO - error status */
+/*
+    convert an input quoted string to an unquoted string by removing
+    the leading and trailing quote character.  Also, replace any
+    pairs of single quote characters with just a single quote 
+    character (FITS used a pair of single quotes to represent
+    a literal quote character within the string).
+*/
+{
+    int jj;
+    size_t len, ii;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (instr[0] != '\'')
+    {
+        strcpy(outstr, instr);  /* no leading quote, so return input string */
+        return(*status);
+    }
+
+    len = strlen(instr);
+
+    for (ii=1, jj=0; ii < len; ii++, jj++)
+    {
+        if (instr[ii] == '\'')  /*  is this the closing quote?  */
+        {
+            if (instr[ii+1] == '\'')  /* 2 successive quotes? */
+                ii++;  /* copy only one of the quotes */
+            else
+                break;   /*  found the closing quote, so exit this loop  */
+        }
+        outstr[jj] = instr[ii];   /* copy the next character to the output */
+    }
+
+    outstr[jj] = '\0';             /*  terminate the output string  */
+
+    if (ii == len)
+    {
+        ffpmsg("This string value has no closing quote (ffc2s):");
+        ffpmsg(instr);
+        return(*status = 205);
+    }
+
+    for (jj--; jj >= 0; jj--)  /* replace trailing blanks with nulls */
+    {
+        if (outstr[jj] == ' ')
+            outstr[jj] = 0;
+        else
+            break;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffc2rr(char *cval,   /* I - string representation of the value */
+           float *fval,  /* O - numerical value of the input string */
+           int *status)  /* IO - error status */
+/*
+  convert null-terminated formatted string to a float value
+*/
+{
+    char *loc, msg[81], tval[73];
+    struct lconv *lcc = 0;
+    static char decimalpt = 0;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (!decimalpt) { /* only do this once for efficiency */
+       lcc = localeconv();   /* set structure containing local decimal point symbol */
+       decimalpt = *(lcc->decimal_point);
+    }
+
+    errno = 0;
+    *fval = 0.;
+
+    if (strchr(cval, 'D') || decimalpt == ',')  {
+        /* strtod expects a comma, not a period, as the decimal point */
+        strcpy(tval, cval);
+
+        /*  The C language does not support a 'D'; replace with 'E' */
+        if (loc = strchr(tval, 'D')) *loc = 'E';
+
+        if (decimalpt == ',')  {
+            /* strtod expects a comma, not a period, as the decimal point */
+            if (loc = strchr(tval, '.'))  *loc = ',';   
+        }
+
+        *fval = (float) strtod(tval, &loc);  /* read the string as an float */
+    } else {
+        *fval = (float) strtod(cval, &loc);
+    }
+
+    /* check for read error, or junk following the value */
+    if (*loc != '\0' && *loc != ' ' )
+    {
+        strcpy(msg,"Error in ffc2rr converting string to float: ");
+        strncat(msg,cval,30);
+        ffpmsg(msg);
+
+        *status = BAD_C2F;   
+    }
+
+    if (errno == ERANGE)
+    {
+        strcpy(msg,"Error in ffc2rr converting string to float: ");
+        strncat(msg,cval,30);
+        ffpmsg(msg);
+
+        *status = NUM_OVERFLOW;
+        errno = 0;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffc2dd(char *cval,   /* I - string representation of the value */
+           double *dval, /* O - numerical value of the input string */
+           int *status)  /* IO - error status */
+/*
+  convert null-terminated formatted string to a double value
+*/
+{
+    char *loc, msg[81], tval[73];
+    struct lconv *lcc = 0;
+    static char decimalpt = 0;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (!decimalpt) { /* only do this once for efficiency */
+       lcc = localeconv();   /* set structure containing local decimal point symbol */
+       decimalpt = *(lcc->decimal_point);
+    }
+   
+    errno = 0;
+    *dval = 0.;
+
+    if (strchr(cval, 'D') || decimalpt == ',') {
+        /* need to modify a temporary copy of the string before parsing it */
+        strcpy(tval, cval);
+        /*  The C language does not support a 'D'; replace with 'E' */
+        if (loc = strchr(tval, 'D')) *loc = 'E';
+
+        if (decimalpt == ',')  {
+            /* strtod expects a comma, not a period, as the decimal point */
+            if (loc = strchr(tval, '.'))  *loc = ',';   
+        }
+    
+        *dval = strtod(tval, &loc);  /* read the string as an double */
+    } else {
+        *dval = strtod(cval, &loc);
+    }
+
+    /* check for read error, or junk following the value */
+    if (*loc != '\0' && *loc != ' ' )
+    {
+        strcpy(msg,"Error in ffc2dd converting string to double: ");
+        strncat(msg,cval,30);
+        ffpmsg(msg);
+
+        *status = BAD_C2D;   
+    }
+
+    if (errno == ERANGE)
+    {
+        strcpy(msg,"Error in ffc2dd converting string to double: ");
+        strncat(msg,cval,30);
+        ffpmsg(msg);
+
+        *status = NUM_OVERFLOW;
+        errno = 0;
+    }
+
+    return(*status);
+}
diff --git a/src/plugins/cfitsio/fitsio.h b/src/plugins/cfitsio/fitsio.h
new file mode 100644
index 0000000..9425630
--- /dev/null
+++ b/src/plugins/cfitsio/fitsio.h
@@ -0,0 +1,1930 @@
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+/*
+
+Copyright (Unpublished--all rights reserved under the copyright laws of
+the United States), U.S. Government as represented by the Administrator
+of the National Aeronautics and Space Administration.  No copyright is
+claimed in the United States under Title 17, U.S. Code.
+
+Permission to freely use, copy, modify, and distribute this software
+and its documentation without fee is hereby granted, provided that this
+copyright notice and disclaimer of warranty appears in all copies.
+
+DISCLAIMER:
+
+THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND,
+EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO,
+ANY WARRANTY THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY
+IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+PURPOSE, AND FREEDOM FROM INFRINGEMENT, AND ANY WARRANTY THAT THE
+DOCUMENTATION WILL CONFORM TO THE SOFTWARE, OR ANY WARRANTY THAT THE
+SOFTWARE WILL BE ERROR FREE.  IN NO EVENT SHALL NASA BE LIABLE FOR ANY
+DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT, INDIRECT, SPECIAL OR
+CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM, OR IN ANY WAY
+CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
+CONTRACT, TORT , OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY
+PERSONS OR PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED
+FROM, OR AROSE OUT OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR
+SERVICES PROVIDED HEREUNDER."
+
+*/
+
+#ifndef _FITSIO_H
+#define _FITSIO_H
+
+#define CFITSIO_VERSION 3.30
+#define CFITSIO_MINOR 30
+#define CFITSIO_MAJOR 3
+
+#include <stdio.h>
+
+/* the following was provided by Michael Greason (GSFC) to fix a */
+/*  C/Fortran compatibility problem on an SGI Altix system running */
+/*  SGI ProPack 4 [this is a Novell SuSE Enterprise 9 derivative]  */
+/*  and using the Intel C++ and Fortran compilers (version 9.1)  */
+#if defined(__INTEL_COMPILER) && defined(__itanium__)
+#  define mipsFortran 1
+#  define _MIPS_SZLONG 64
+#endif
+
+#if defined(linux) || defined(__APPLE__) || defined(__sgi)
+#  include <sys/types.h>  /* apparently needed on debian linux systems */
+#endif                    /* to define off_t                           */
+
+#include <stdlib.h>  /* apparently needed to define size_t with gcc 2.8.1 */
+#include <limits.h>  /* needed for LLONG_MAX and INT64_MAX definitions */
+
+/* Define the datatype for variables which store file offset values. */
+/* The newer 'off_t' datatype should be used for this purpose, but some */
+/* older compilers do not recognize this type, in which case we use 'long' */
+/* instead.  Note that _OFF_T is defined (or not) in stdio.h depending */
+/* on whether _LARGEFILE_SOURCE is defined in sys/feature_tests.h  */
+/* (at least on Solaris platforms using cc)  */
+
+/*  Debian systems require the 2nd test, below,         */
+/*  i.e, "(defined(linux) && defined(__off_t_defined))" */
+#if defined(_OFF_T) || (defined(linux) && defined(__off_t_defined)) || defined(_MIPS_SZLONG) || defined(__APPLE__) || defined(_AIX)
+#    define OFF_T off_t
+#elif defined(_MSC_VER) &&  (_MSC_VER>= 1400)
+#    define OFF_T long long
+#else
+#    define OFF_T long
+#endif
+
+/* this block determines if the the string function name is 
+    strtol or strtoll, and whether to use %ld or %lld in printf statements */
+
+/* 
+   The following 2 cases for that Athon64 were removed on 4 Jan 2006;  
+   they appear to be incorrect now that LONGLONG is always typedef'ed 
+   to 'long long'
+    ||  defined(__ia64__)   \
+    ||  defined(__x86_64__) \
+*/
+#if (defined(__alpha) && ( defined(__unix__) || defined(__NetBSD__) )) \
+    ||  defined(__sparcv9) || (defined(__sparc__) && defined(__arch64__))  \
+    ||  defined(__powerpc64__) || defined(__64BIT__) \
+    ||  (defined(_MIPS_SZLONG) &&  _MIPS_SZLONG == 64) \
+    ||  defined( _MSC_VER)|| defined(__BORLANDC__)
+    
+#   define USE_LL_SUFFIX 0
+#else
+#   define USE_LL_SUFFIX 1
+#endif
+
+/* 
+   Determine what 8-byte integer data type is available.
+  'long long' is now supported by most compilers, but
+  older MS Visual C++ compilers before V7.0 use '__int64' instead.
+*/
+
+#ifndef LONGLONG_TYPE   /* this may have been previously defined */
+#if defined(_MSC_VER)   /* Microsoft Visual C++ */
+
+#if (_MSC_VER < 1300)   /* versions earlier than V7.0 do not have 'long long' */
+    typedef __int64 LONGLONG;
+#else                   /* newer versions do support 'long long' */
+    typedef long long LONGLONG; 
+#endif
+
+#elif defined( __BORLANDC__)  /* for the Borland 5.5 compiler, in particular */
+    typedef __int64 LONGLONG;
+#else
+    typedef long long LONGLONG; 
+#endif
+
+#define LONGLONG_TYPE
+#endif  
+
+#ifndef LONGLONG_MAX
+
+#ifdef LLONG_MAX
+/* Linux and Solaris definition */
+#define LONGLONG_MAX LLONG_MAX
+#define LONGLONG_MIN LLONG_MIN
+
+#elif defined(LONG_LONG_MAX)
+#define LONGLONG_MAX LONG_LONG_MAX
+#define LONGLONG_MIN LONG_LONG_MIN
+
+#elif defined(__LONG_LONG_MAX__)
+/* Mac OS X & CYGWIN defintion */
+#define LONGLONG_MAX __LONG_LONG_MAX__
+#define LONGLONG_MIN (-LONGLONG_MAX -1LL)
+
+#elif defined(INT64_MAX)
+/* windows definition */
+#define LONGLONG_MAX INT64_MAX
+#define LONGLONG_MIN INT64_MIN
+
+#elif defined(_I64_MAX)
+/* windows definition */
+#define LONGLONG_MAX _I64_MAX
+#define LONGLONG_MIN _I64_MIN
+
+#elif (defined(__alpha) && ( defined(__unix__) || defined(__NetBSD__) )) \
+    ||  defined(__sparcv9)  \
+    ||  defined(__ia64__)   \
+    ||  defined(__x86_64__) \
+    ||  defined(_SX)        \
+    ||  defined(__powerpc64__) || defined(__64BIT__) \
+    ||  (defined(_MIPS_SZLONG) &&  _MIPS_SZLONG == 64)
+/* sizeof(long) = 64 */
+#define LONGLONG_MAX  9223372036854775807L /* max 64-bit integer */
+#define LONGLONG_MIN (-LONGLONG_MAX -1L)   /* min 64-bit integer */
+
+#else
+/*  define a default value, even if it is never used */
+#define LONGLONG_MAX  9223372036854775807LL /* max 64-bit integer */
+#define LONGLONG_MIN (-LONGLONG_MAX -1LL)   /* min 64-bit integer */
+
+#endif
+#endif  /* end of ndef LONGLONG_MAX section */
+
+
+/* ================================================================= */
+
+
+/*  The following exclusion if __CINT__ is defined is needed for ROOT */
+#ifndef __CINT__
+#include "longnam.h"
+#endif
+ 
+#define NIOBUF  40  /* number of IO buffers to create (default = 40) */
+          /* !! Significantly increasing NIOBUF may degrade performance !! */
+
+#define IOBUFLEN 2880    /* size in bytes of each IO buffer (DONT CHANGE!) */
+
+/* global variables */
+ 
+#define FLEN_FILENAME 1025 /* max length of a filename  */
+#define FLEN_KEYWORD   72  /* max length of a keyword (HIERARCH convention) */
+#define FLEN_CARD      81  /* length of a FITS header card */
+#define FLEN_VALUE     71  /* max length of a keyword value string */
+#define FLEN_COMMENT   73  /* max length of a keyword comment string */
+#define FLEN_ERRMSG    81  /* max length of a FITSIO error message */
+#define FLEN_STATUS    31  /* max length of a FITSIO status text string */
+ 
+#define TBIT          1  /* codes for FITS table data types */
+#define TBYTE        11
+#define TSBYTE       12
+#define TLOGICAL     14
+#define TSTRING      16
+#define TUSHORT      20
+#define TSHORT       21
+#define TUINT        30
+#define TINT         31
+#define TULONG       40
+#define TLONG        41
+#define TINT32BIT    41  /* used when returning datatype of a column */
+#define TFLOAT       42
+#define TLONGLONG    81
+#define TDOUBLE      82
+#define TCOMPLEX     83
+#define TDBLCOMPLEX 163
+
+#define TYP_STRUC_KEY 10
+#define TYP_CMPRS_KEY 20
+#define TYP_SCAL_KEY  30
+#define TYP_NULL_KEY  40
+#define TYP_DIM_KEY   50
+#define TYP_RANG_KEY  60
+#define TYP_UNIT_KEY  70
+#define TYP_DISP_KEY  80
+#define TYP_HDUID_KEY 90
+#define TYP_CKSUM_KEY 100
+#define TYP_WCS_KEY   110
+#define TYP_REFSYS_KEY 120
+#define TYP_COMM_KEY  130
+#define TYP_CONT_KEY  140
+#define TYP_USER_KEY  150
+
+
+#define INT32BIT int  /* 32-bit integer datatype.  Currently this       */
+                      /* datatype is an 'int' on all useful platforms   */
+                      /* however, it is possible that that are cases    */
+                      /* where 'int' is a 2-byte integer, in which case */
+                      /* INT32BIT would need to be defined as 'long'.   */
+
+#define BYTE_IMG      8  /* BITPIX code values for FITS image types */
+#define SHORT_IMG    16
+#define LONG_IMG     32
+#define LONGLONG_IMG 64
+#define FLOAT_IMG   -32
+#define DOUBLE_IMG  -64
+                         /* The following 2 codes are not true FITS         */
+                         /* datatypes; these codes are only used internally */
+                         /* within cfitsio to make it easier for users      */
+                         /* to deal with unsigned integers.                 */
+#define SBYTE_IMG    10
+#define USHORT_IMG   20
+#define ULONG_IMG    40
+
+#define IMAGE_HDU  0  /* Primary Array or IMAGE HDU */
+#define ASCII_TBL  1  /* ASCII table HDU  */
+#define BINARY_TBL 2  /* Binary table HDU */
+#define ANY_HDU   -1  /* matches any HDU type */
+
+#define READONLY  0    /* options when opening a file */
+#define READWRITE 1
+
+/* adopt a hopefully obscure number to use as a null value flag */
+/* could be problems if the FITS files contain data with these values */
+#define FLOATNULLVALUE -9.11912E-36F
+#define DOUBLENULLVALUE -9.1191291391491E-36
+ 
+/* compression algorithm type codes */
+#define SUBTRACTIVE_DITHER_1 1
+#define MAX_COMPRESS_DIM     6
+#define RICE_1      11
+#define GZIP_1      21
+#define GZIP_2      22
+#define PLIO_1      31
+#define HCOMPRESS_1 41
+#define BZIP2_1     51  /* not publicly supported; only for test purposes */
+#define NOCOMPRESS  0
+
+#ifndef TRUE
+#define TRUE 1
+#endif
+
+#ifndef FALSE
+#define FALSE 0
+#endif
+
+#define CASESEN   1   /* do case-sensitive string match */
+#define CASEINSEN 0   /* do case-insensitive string match */
+ 
+#define GT_ID_ALL_URI  0   /* hierarchical grouping parameters */
+#define GT_ID_REF      1
+#define GT_ID_POS      2
+#define GT_ID_ALL      3
+#define GT_ID_REF_URI 11
+#define GT_ID_POS_URI 12
+
+#define OPT_RM_GPT      0
+#define OPT_RM_ENTRY    1
+#define OPT_RM_MBR      2
+#define OPT_RM_ALL      3
+
+#define OPT_GCP_GPT     0
+#define OPT_GCP_MBR     1
+#define OPT_GCP_ALL     2
+
+#define OPT_MCP_ADD     0
+#define OPT_MCP_NADD    1
+#define OPT_MCP_REPL    2
+#define OPT_MCP_MOV     3
+
+#define OPT_MRG_COPY    0
+#define OPT_MRG_MOV     1
+
+#define OPT_CMT_MBR      1
+#define OPT_CMT_MBR_DEL 11
+
+typedef struct        /* structure used to store table column information */
+{
+    char ttype[70];   /* column name = FITS TTYPEn keyword; */
+    LONGLONG tbcol;       /* offset in row to first byte of each column */
+    int  tdatatype;   /* datatype code of each column */
+    LONGLONG trepeat;    /* repeat count of column; number of elements */
+    double tscale;    /* FITS TSCALn linear scaling factor */
+    double tzero;     /* FITS TZEROn linear scaling zero point */
+    LONGLONG tnull;   /* FITS null value for int image or binary table cols */
+    char strnull[20]; /* FITS null value string for ASCII table columns */
+    char tform[10];   /* FITS tform keyword value  */
+    long  twidth;     /* width of each ASCII table column */
+}tcolumn;
+
+#define VALIDSTRUC 555  /* magic value used to identify if structure is valid */
+
+typedef struct      /* structure used to store basic FITS file information */
+{
+    int filehandle;   /* handle returned by the file open function */
+    int driver;       /* defines which set of I/O drivers should be used */
+    int open_count;   /* number of opened 'fitsfiles' using this structure */
+    char *filename;   /* file name */
+    int validcode;    /* magic value used to verify that structure is valid */
+    int only_one;     /* flag meaning only copy the specified extension */
+    LONGLONG filesize; /* current size of the physical disk file in bytes */
+    LONGLONG logfilesize; /* logical size of file, including unflushed buffers */
+    int lasthdu;      /* is this the last HDU in the file? 0 = no, else yes */
+    LONGLONG bytepos; /* current logical I/O pointer position in file */
+    LONGLONG io_pos;  /* current I/O pointer position in the physical file */
+    int curbuf;       /* number of I/O buffer currently in use */ 
+    int curhdu;       /* current HDU number; 0 = primary array */
+    int hdutype;      /* 0 = primary array, 1 = ASCII table, 2 = binary table */
+    int writemode;    /* 0 = readonly, 1 = readwrite */
+    int maxhdu;       /* highest numbered HDU known to exist in the file */
+    int MAXHDU;       /* dynamically allocated dimension of headstart array */
+    LONGLONG *headstart; /* byte offset in file to start of each HDU */
+    LONGLONG headend;   /* byte offest in file to end of the current HDU header */
+    LONGLONG ENDpos;    /* byte offest to where the END keyword was last written */
+    LONGLONG nextkey;   /* byte offset in file to beginning of next keyword */
+    LONGLONG datastart; /* byte offset in file to start of the current data unit */
+    int imgdim;         /* dimension of image; cached for fast access */
+    LONGLONG imgnaxis[99]; /* length of each axis; cached for fast access */
+    int tfield;          /* number of fields in the table (primary array has 2 */
+    LONGLONG origrows;   /* original number of rows (value of NAXIS2 keyword)  */
+    LONGLONG numrows;    /* number of rows in the table (dynamically updated) */
+    LONGLONG rowlength;  /* length of a table row or image size (bytes) */
+    tcolumn *tableptr;   /* pointer to the table structure */
+    LONGLONG heapstart;  /* heap start byte relative to start of data unit */
+    LONGLONG heapsize;   /* size of the heap, in bytes */
+
+         /* the following elements are related to compressed images */
+    int request_compress_type;  /* requested image compression algorithm */
+    long request_tilesize[MAX_COMPRESS_DIM]; /* requested tiling size */
+
+    float request_hcomp_scale;    /* requested HCOMPRESS scale factor */
+    int request_hcomp_smooth;     /* requested HCOMPRESS smooth parameter */
+    int request_quantize_dither ; /* requested dithering mode when quantizing */
+                                  /* floating point images to integer */
+    int request_dither_offset;    /* starting offset into the array of random dithering */
+    int request_lossy_int_compress;  /* lossy compress integer image as if float image? */
+
+    int compressimg; /* 1 if HDU contains a compressed image, else 0 */
+    int quantize_dither;   /* floating point pixel quantization algorithm */
+    char zcmptype[12];      /* compression type string */
+    int compress_type;      /* type of compression algorithm */
+    int zbitpix;            /* FITS data type of image (BITPIX) */
+    int zndim;              /* dimension of image */
+    long znaxis[MAX_COMPRESS_DIM];  /* length of each axis */
+    long tilesize[MAX_COMPRESS_DIM]; /* size of compression tiles */
+    long maxtilelen;        /* max number of pixels in each image tile */
+    long maxelem;	    /* maximum byte length of tile compressed arrays */
+
+    int cn_compressed;	    /* column number for COMPRESSED_DATA column */
+    int cn_uncompressed;    /* column number for UNCOMPRESSED_DATA column */
+    int cn_gzip_data;       /* column number for GZIP2 lossless compressed data */
+    int cn_zscale;	    /* column number for ZSCALE column */
+    int cn_zzero;	    /* column number for ZZERO column */
+    int cn_zblank;          /* column number for the ZBLANK column */
+
+    double zscale;          /* scaling value, if same for all tiles */
+    double zzero;           /* zero pt, if same for all tiles */
+    double cn_bscale;       /* value of the BSCALE keyword in header */
+    double cn_bzero;        /* value of the BZERO keyword (may be reset) */
+    double cn_actual_bzero; /* actual value of the BZERO keyword  */
+    int zblank;             /* value for null pixels, if not a column */
+
+    int rice_blocksize;     /* first compression parameter: pixels/block */
+    int rice_bytepix;       /* 2nd compression parameter: bytes/pixel */
+    float quantize_level;   /* floating point quantization level */
+    int dither_offset;      /* starting offset into the array of random dithering */
+    float hcomp_scale;      /* 1st hcompress compression parameter */
+    int hcomp_smooth;       /* 2nd hcompress compression parameter */
+
+    int  tilerow;           /* row number of the uncompressed tiledata */
+    long tiledatasize;       /* length of the tile data in bytes */
+    int tiletype;           /* datatype of the tile (TINT, TSHORT, etc) */
+    void *tiledata;         /* uncompressed tile of data, for row tilerow */
+    char *tilenullarray;    /* optional array of null value flags */
+    int tileanynull;        /* anynulls in this tile? */
+
+    char *iobuffer;         /* pointer to FITS file I/O buffers */
+    long bufrecnum[NIOBUF]; /* file record number of each of the buffers */
+    int dirty[NIOBUF];     /* has the corresponding buffer been modified? */
+    int ageindex[NIOBUF];  /* relative age of each buffer */  
+} FITSfile;
+
+typedef struct         /* structure used to store basic HDU information */
+{
+    int HDUposition;  /* HDU position in file; 0 = first HDU */
+    FITSfile *Fptr;   /* pointer to FITS file structure */
+}fitsfile;
+
+typedef struct  /* structure for the iterator function column information */
+{  
+     /* elements required as input to fits_iterate_data: */
+
+    fitsfile *fptr;     /* pointer to the HDU containing the column */
+    int      colnum;    /* column number in the table (use name if < 1) */
+    char     colname[70]; /* name (= TTYPEn value) of the column (optional) */
+    int      datatype;  /* output datatype (converted if necessary  */
+    int      iotype;    /* = InputCol, InputOutputCol, or OutputCol */
+
+    /* output elements that may be useful for the work function: */
+
+    void     *array;    /* pointer to the array (and the null value) */
+    long     repeat;    /* binary table vector repeat value */
+    long     tlmin;     /* legal minimum data value */
+    long     tlmax;     /* legal maximum data value */
+    char     tunit[70]; /* physical unit string */
+    char     tdisp[70]; /* suggested display format */
+
+} iteratorCol;
+
+#define InputCol         0  /* flag for input only iterator column       */
+#define InputOutputCol   1  /* flag for input and output iterator column */
+#define OutputCol        2  /* flag for output only iterator column      */
+
+/*=============================================================================
+*
+*       The following wtbarr typedef is used in the fits_read_wcstab() routine,
+*       which is intended for use with the WCSLIB library written by Mark
+*       Calabretta, http://www.atnf.csiro.au/~mcalabre/index.html
+*
+*       In order to maintain WCSLIB and CFITSIO as independent libraries it
+*       was not permissible for any CFITSIO library code to include WCSLIB
+*       header files, or vice versa.  However, the CFITSIO function
+*       fits_read_wcstab() accepts an array of structs defined by wcs.h within
+*       WCSLIB.  The problem then was to define this struct within fitsio.h
+*       without including wcs.h, especially noting that wcs.h will often (but
+*       not always) be included together with fitsio.h in an applications
+*       program that uses fits_read_wcstab().
+*
+*       Of the various possibilities, the solution adopted was for WCSLIB to
+*       define "struct wtbarr" while fitsio.h defines "typedef wtbarr", a
+*       untagged struct with identical members.  This allows both wcs.h and
+*       fitsio.h to define a wtbarr data type without conflict by virtue of
+*       the fact that structure tags and typedef names share different
+*       namespaces in C. Therefore, declarations within WCSLIB look like
+*
+*          struct wtbarr *w;
+*
+*       while within CFITSIO they are simply
+*
+*          wtbarr *w;
+*
+*       but as suggested by the commonality of the names, these are really the
+*       same aggregate data type.  However, in passing a (struct wtbarr *) to
+*       fits_read_wcstab() a cast to (wtbarr *) is formally required.
+*===========================================================================*/
+
+#ifndef WCSLIB_GETWCSTAB
+#define WCSLIB_GETWCSTAB
+
+typedef struct {
+   int  i;                      /* Image axis number.                       */
+   int  m;                      /* Array axis number for index vectors.     */
+   int  kind;                   /* Array type, 'c' (coord) or 'i' (index).  */
+   char extnam[72];             /* EXTNAME of binary table extension.       */
+   int  extver;                 /* EXTVER  of binary table extension.       */
+   int  extlev;                 /* EXTLEV  of binary table extension.       */
+   char ttype[72];              /* TTYPEn of column containing the array.   */
+   long row;                    /* Table row number.                        */
+   int  ndim;                   /* Expected array dimensionality.           */
+   int  *dimlen;                /* Where to write the array axis lengths.   */
+   double **arrayp;             /* Where to write the address of the array  */
+                                /* allocated to store the array.            */
+} wtbarr;
+
+int fits_read_wcstab(fitsfile *fptr, int nwtb, wtbarr *wtb, int *status);
+
+#endif /* WCSLIB_GETWCSTAB */
+
+/* error status codes */
+
+#define CREATE_DISK_FILE -106 /* create disk file, without extended filename syntax */
+#define OPEN_DISK_FILE   -105 /* open disk file, without extended filename syntax */
+#define SKIP_TABLE       -104 /* move to 1st image when opening file */
+#define SKIP_IMAGE       -103 /* move to 1st table when opening file */
+#define SKIP_NULL_PRIMARY -102 /* skip null primary array when opening file */
+#define USE_MEM_BUFF     -101  /* use memory buffer when opening file */
+#define OVERFLOW_ERR      -11  /* overflow during datatype conversion */
+#define PREPEND_PRIMARY    -9  /* used in ffiimg to insert new primary array */
+#define SAME_FILE         101  /* input and output files are the same */
+#define TOO_MANY_FILES    103  /* tried to open too many FITS files */
+#define FILE_NOT_OPENED   104  /* could not open the named file */
+#define FILE_NOT_CREATED  105  /* could not create the named file */
+#define WRITE_ERROR       106  /* error writing to FITS file */
+#define END_OF_FILE       107  /* tried to move past end of file */
+#define READ_ERROR        108  /* error reading from FITS file */
+#define FILE_NOT_CLOSED   110  /* could not close the file */
+#define ARRAY_TOO_BIG     111  /* array dimensions exceed internal limit */
+#define READONLY_FILE     112  /* Cannot write to readonly file */
+#define MEMORY_ALLOCATION 113  /* Could not allocate memory */
+#define BAD_FILEPTR       114  /* invalid fitsfile pointer */
+#define NULL_INPUT_PTR    115  /* NULL input pointer to routine */
+#define SEEK_ERROR        116  /* error seeking position in file */
+
+#define BAD_URL_PREFIX    121  /* invalid URL prefix on file name */
+#define TOO_MANY_DRIVERS  122  /* tried to register too many IO drivers */
+#define DRIVER_INIT_FAILED 123  /* driver initialization failed */
+#define NO_MATCHING_DRIVER 124  /* matching driver is not registered */
+#define URL_PARSE_ERROR    125  /* failed to parse input file URL */
+#define RANGE_PARSE_ERROR  126  /* failed to parse input file URL */
+
+#define	SHARED_ERRBASE	(150)
+#define	SHARED_BADARG	(SHARED_ERRBASE + 1)
+#define	SHARED_NULPTR	(SHARED_ERRBASE + 2)
+#define	SHARED_TABFULL	(SHARED_ERRBASE + 3)
+#define	SHARED_NOTINIT	(SHARED_ERRBASE + 4)
+#define	SHARED_IPCERR	(SHARED_ERRBASE + 5)
+#define	SHARED_NOMEM	(SHARED_ERRBASE + 6)
+#define	SHARED_AGAIN	(SHARED_ERRBASE + 7)
+#define	SHARED_NOFILE	(SHARED_ERRBASE + 8)
+#define	SHARED_NORESIZE	(SHARED_ERRBASE + 9)
+
+#define HEADER_NOT_EMPTY  201  /* header already contains keywords */
+#define KEY_NO_EXIST      202  /* keyword not found in header */
+#define KEY_OUT_BOUNDS    203  /* keyword record number is out of bounds */
+#define VALUE_UNDEFINED   204  /* keyword value field is blank */
+#define NO_QUOTE          205  /* string is missing the closing quote */
+#define BAD_INDEX_KEY     206  /* illegal indexed keyword name */
+#define BAD_KEYCHAR       207  /* illegal character in keyword name or card */
+#define BAD_ORDER         208  /* required keywords out of order */
+#define NOT_POS_INT       209  /* keyword value is not a positive integer */
+#define NO_END            210  /* couldn't find END keyword */
+#define BAD_BITPIX        211  /* illegal BITPIX keyword value*/
+#define BAD_NAXIS         212  /* illegal NAXIS keyword value */
+#define BAD_NAXES         213  /* illegal NAXISn keyword value */
+#define BAD_PCOUNT        214  /* illegal PCOUNT keyword value */
+#define BAD_GCOUNT        215  /* illegal GCOUNT keyword value */
+#define BAD_TFIELDS       216  /* illegal TFIELDS keyword value */
+#define NEG_WIDTH         217  /* negative table row size */
+#define NEG_ROWS          218  /* negative number of rows in table */
+#define COL_NOT_FOUND     219  /* column with this name not found in table */
+#define BAD_SIMPLE        220  /* illegal value of SIMPLE keyword  */
+#define NO_SIMPLE         221  /* Primary array doesn't start with SIMPLE */
+#define NO_BITPIX         222  /* Second keyword not BITPIX */
+#define NO_NAXIS          223  /* Third keyword not NAXIS */
+#define NO_NAXES          224  /* Couldn't find all the NAXISn keywords */
+#define NO_XTENSION       225  /* HDU doesn't start with XTENSION keyword */
+#define NOT_ATABLE        226  /* the CHDU is not an ASCII table extension */
+#define NOT_BTABLE        227  /* the CHDU is not a binary table extension */
+#define NO_PCOUNT         228  /* couldn't find PCOUNT keyword */
+#define NO_GCOUNT         229  /* couldn't find GCOUNT keyword */
+#define NO_TFIELDS        230  /* couldn't find TFIELDS keyword */
+#define NO_TBCOL          231  /* couldn't find TBCOLn keyword */
+#define NO_TFORM          232  /* couldn't find TFORMn keyword */
+#define NOT_IMAGE         233  /* the CHDU is not an IMAGE extension */
+#define BAD_TBCOL         234  /* TBCOLn keyword value < 0 or > rowlength */
+#define NOT_TABLE         235  /* the CHDU is not a table */
+#define COL_TOO_WIDE      236  /* column is too wide to fit in table */
+#define COL_NOT_UNIQUE    237  /* more than 1 column name matches template */
+#define BAD_ROW_WIDTH     241  /* sum of column widths not = NAXIS1 */
+#define UNKNOWN_EXT       251  /* unrecognizable FITS extension type */
+#define UNKNOWN_REC       252  /* unrecognizable FITS record */
+#define END_JUNK          253  /* END keyword is not blank */
+#define BAD_HEADER_FILL   254  /* Header fill area not blank */
+#define BAD_DATA_FILL     255  /* Data fill area not blank or zero */
+#define BAD_TFORM         261  /* illegal TFORM format code */
+#define BAD_TFORM_DTYPE   262  /* unrecognizable TFORM datatype code */
+#define BAD_TDIM          263  /* illegal TDIMn keyword value */
+#define BAD_HEAP_PTR      264  /* invalid BINTABLE heap address */
+ 
+#define BAD_HDU_NUM       301  /* HDU number < 1 or > MAXHDU */
+#define BAD_COL_NUM       302  /* column number < 1 or > tfields */
+#define NEG_FILE_POS      304  /* tried to move before beginning of file  */
+#define NEG_BYTES         306  /* tried to read or write negative bytes */
+#define BAD_ROW_NUM       307  /* illegal starting row number in table */
+#define BAD_ELEM_NUM      308  /* illegal starting element number in vector */
+#define NOT_ASCII_COL     309  /* this is not an ASCII string column */
+#define NOT_LOGICAL_COL   310  /* this is not a logical datatype column */
+#define BAD_ATABLE_FORMAT 311  /* ASCII table column has wrong format */
+#define BAD_BTABLE_FORMAT 312  /* Binary table column has wrong format */
+#define NO_NULL           314  /* null value has not been defined */
+#define NOT_VARI_LEN      317  /* this is not a variable length column */
+#define BAD_DIMEN         320  /* illegal number of dimensions in array */
+#define BAD_PIX_NUM       321  /* first pixel number greater than last pixel */
+#define ZERO_SCALE        322  /* illegal BSCALE or TSCALn keyword = 0 */
+#define NEG_AXIS          323  /* illegal axis length < 1 */
+ 
+#define NOT_GROUP_TABLE         340
+#define HDU_ALREADY_MEMBER      341
+#define MEMBER_NOT_FOUND        342
+#define GROUP_NOT_FOUND         343
+#define BAD_GROUP_ID            344
+#define TOO_MANY_HDUS_TRACKED   345
+#define HDU_ALREADY_TRACKED     346
+#define BAD_OPTION              347
+#define IDENTICAL_POINTERS      348
+#define BAD_GROUP_ATTACH        349
+#define BAD_GROUP_DETACH        350
+
+#define BAD_I2C           401  /* bad int to formatted string conversion */
+#define BAD_F2C           402  /* bad float to formatted string conversion */
+#define BAD_INTKEY        403  /* can't interprete keyword value as integer */
+#define BAD_LOGICALKEY    404  /* can't interprete keyword value as logical */
+#define BAD_FLOATKEY      405  /* can't interprete keyword value as float */
+#define BAD_DOUBLEKEY     406  /* can't interprete keyword value as double */
+#define BAD_C2I           407  /* bad formatted string to int conversion */
+#define BAD_C2F           408  /* bad formatted string to float conversion */
+#define BAD_C2D           409  /* bad formatted string to double conversion */
+#define BAD_DATATYPE      410  /* bad keyword datatype code */
+#define BAD_DECIM         411  /* bad number of decimal places specified */
+#define NUM_OVERFLOW      412  /* overflow during datatype conversion */
+
+# define DATA_COMPRESSION_ERR 413  /* error in imcompress routines */
+# define DATA_DECOMPRESSION_ERR 414 /* error in imcompress routines */
+# define NO_COMPRESSED_TILE  415 /* compressed tile doesn't exist */
+
+#define BAD_DATE          420  /* error in date or time conversion */
+
+#define PARSE_SYNTAX_ERR  431  /* syntax error in parser expression */
+#define PARSE_BAD_TYPE    432  /* expression did not evaluate to desired type */
+#define PARSE_LRG_VECTOR  433  /* vector result too large to return in array */
+#define PARSE_NO_OUTPUT   434  /* data parser failed not sent an out column */
+#define PARSE_BAD_COL     435  /* bad data encounter while parsing column */
+#define PARSE_BAD_OUTPUT  436  /* Output file not of proper type          */
+
+#define ANGLE_TOO_BIG     501  /* celestial angle too large for projection */
+#define BAD_WCS_VAL       502  /* bad celestial coordinate or pixel value */
+#define WCS_ERROR         503  /* error in celestial coordinate calculation */
+#define BAD_WCS_PROJ      504  /* unsupported type of celestial projection */
+#define NO_WCS_KEY        505  /* celestial coordinate keywords not found */
+#define APPROX_WCS_KEY    506  /* approximate WCS keywords were calculated */
+
+#define NO_CLOSE_ERROR    999  /* special value used internally to switch off */
+                               /* the error message from ffclos and ffchdu */
+
+/*------- following error codes are used in the grparser.c file -----------*/
+#define	NGP_ERRBASE		(360)			/* base chosen so not to interfere with CFITSIO */
+#define	NGP_OK			(0)
+#define	NGP_NO_MEMORY		(NGP_ERRBASE + 0)	/* malloc failed */
+#define	NGP_READ_ERR		(NGP_ERRBASE + 1)	/* read error from file */
+#define	NGP_NUL_PTR		(NGP_ERRBASE + 2)	/* null pointer passed as argument */
+#define	NGP_EMPTY_CURLINE	(NGP_ERRBASE + 3)	/* line read seems to be empty */
+#define	NGP_UNREAD_QUEUE_FULL	(NGP_ERRBASE + 4)	/* cannot unread more then 1 line (or single line twice) */
+#define	NGP_INC_NESTING		(NGP_ERRBASE + 5)	/* too deep include file nesting (inf. loop ?) */
+#define	NGP_ERR_FOPEN		(NGP_ERRBASE + 6)	/* fopen() failed, cannot open file */
+#define	NGP_EOF			(NGP_ERRBASE + 7)	/* end of file encountered */
+#define	NGP_BAD_ARG		(NGP_ERRBASE + 8)	/* bad arguments passed */
+#define	NGP_TOKEN_NOT_EXPECT	(NGP_ERRBASE + 9)	/* token not expected here */
+
+/*  The following exclusion if __CINT__ is defined is needed for ROOT */
+#ifndef __CINT__
+/*  the following 3 lines are needed to support C++ compilers */
+#ifdef __cplusplus
+extern "C" {
+#endif
+#endif
+
+int CFITS2Unit( fitsfile *fptr );
+fitsfile* CUnit2FITS(int unit);
+
+/*----------------  FITS file URL parsing routines -------------*/
+int fits_get_token(char **ptr, char *delimiter, char *token, int *isanumber);
+char *fits_split_names(char *list);
+int ffiurl(  char *url,  char *urltype, char *infile,
+                    char *outfile, char *extspec, char *rowfilter,
+                    char *binspec, char *colspec, int *status);
+int ffifile (char *url,  char *urltype, char *infile,
+                    char *outfile, char *extspec, char *rowfilter,
+                    char *binspec, char *colspec, char *pixfilter, int *status);
+int ffrtnm(char *url, char *rootname, int *status);
+int ffexist(const char *infile, int *exists, int *status);
+int ffexts(char *extspec, int *extnum,  char *extname, int *extvers,
+          int *hdutype, char *colname, char *rowexpress, int *status);
+int ffextn(char *url, int *extension_num, int *status);
+int ffurlt(fitsfile *fptr, char *urlType, int *status);
+int ffbins(char *binspec, int *imagetype, int *haxis, 
+                      char colname[4][FLEN_VALUE], double *minin,
+                      double *maxin, double *binsizein,
+                      char minname[4][FLEN_VALUE], char maxname[4][FLEN_VALUE],
+                      char binname[4][FLEN_VALUE], double *weight, char *wtname,
+                      int *recip, int *status);
+int ffbinr(char **binspec, char *colname, double *minin, 
+                        double *maxin, double *binsizein, char *minname,
+                        char *maxname, char *binname, int *status);
+int fits_copy_cell2image(fitsfile *fptr, fitsfile *newptr, char *colname,
+                      long rownum, int *status);
+int fits_copy_image2cell(fitsfile *fptr, fitsfile *newptr, char *colname,
+                      long rownum, int copykeyflag, int *status);
+int fits_copy_pixlist2image(fitsfile *infptr, fitsfile *outfptr, int firstkey,       /* I - first HDU record number to start with */
+           int naxis, int *colnum, int *status);
+int ffimport_file( char *filename, char **contents, int *status );
+int ffrwrg( char *rowlist, LONGLONG maxrows, int maxranges, int *numranges,
+      long *minrow, long *maxrow, int *status);
+int ffrwrgll( char *rowlist, LONGLONG maxrows, int maxranges, int *numranges,
+      LONGLONG *minrow, LONGLONG *maxrow, int *status);
+/*----------------  FITS file I/O routines -------------*/
+int fits_init_cfitsio(void);
+int ffomem(fitsfile **fptr, const char *name, int mode, void **buffptr,
+           size_t *buffsize, size_t deltasize,
+           void *(*mem_realloc)(void *p, size_t newsize),
+           int *status);
+int ffopen(fitsfile **fptr, const char *filename, int iomode, int *status);
+int ffopentest(double version, fitsfile **fptr, const char *filename, int iomode, int *status);
+
+int ffdopn(fitsfile **fptr, const char *filename, int iomode, int *status);
+int fftopn(fitsfile **fptr, const char *filename, int iomode, int *status);
+int ffiopn(fitsfile **fptr, const char *filename, int iomode, int *status);
+int ffdkopn(fitsfile **fptr, const char *filename, int iomode, int *status);
+int ffreopen(fitsfile *openfptr, fitsfile **newfptr, int *status); 
+int ffinit(  fitsfile **fptr, const char *filename, int *status);
+int ffdkinit(fitsfile **fptr, const char *filename, int *status);
+int ffimem(fitsfile **fptr,  void **buffptr,
+           size_t *buffsize, size_t deltasize,
+           void *(*mem_realloc)(void *p, size_t newsize),
+           int *status);
+int fftplt(fitsfile **fptr, const char *filename, const char *tempname,
+           int *status);
+int ffflus(fitsfile *fptr, int *status);
+int ffflsh(fitsfile *fptr, int clearbuf, int *status);
+int ffclos(fitsfile *fptr, int *status);
+int ffdelt(fitsfile *fptr, int *status);
+int ffflnm(fitsfile *fptr, char *filename, int *status);
+int ffflmd(fitsfile *fptr, int *filemode, int *status);
+int fits_delete_iraf_file(char *filename, int *status);
+
+/*---------------- utility routines -------------*/
+
+float ffvers(float *version);
+void ffupch(char *string);
+void ffgerr(int status, char *errtext);
+void ffpmsg(const char *err_message);
+void ffpmrk(void);
+int  ffgmsg(char *err_message);
+void ffcmsg(void);
+void ffcmrk(void);
+void ffrprt(FILE *stream, int status);
+void ffcmps(char *templt, char *colname, int  casesen, int *match,
+           int *exact);
+int fftkey(const char *keyword, int *status);
+int fftrec(char *card, int *status);
+int ffnchk(fitsfile *fptr, int *status);
+int ffkeyn(const char *keyroot, int value, char *keyname, int *status);
+int ffnkey(int value, char *keyroot, char *keyname, int *status);
+int ffgkcl(char *card);
+int ffdtyp(char *cval, char *dtype, int *status);
+int ffinttyp(char *cval, int *datatype, int *negative, int *status);
+int ffpsvc(char *card, char *value, char *comm, int *status);
+int ffgknm(char *card, char *name, int *length, int *status);
+int ffgthd(char *tmplt, char *card, int *hdtype, int *status);
+int fits_translate_keyword(char *inrec, char *outrec, char *patterns[][2],
+          int npat, int n_value, int n_offset, int n_range, int *pat_num,
+          int *i, int *j,  int *m, int *n, int *status);
+int fits_translate_keywords(fitsfile *infptr, fitsfile *outfptr,
+          int firstkey, char *patterns[][2],
+          int npat, int n_value, int n_offset, int n_range, int *status);    
+int ffasfm(char *tform, int *datacode, long *width, int *decim, int *status);
+int ffbnfm(char *tform, int *datacode, long *repeat, long *width, int *status);
+int ffbnfmll(char *tform, int *datacode, LONGLONG *repeat, long *width, int *status);
+int ffgabc(int tfields, char **tform, int space, long *rowlen, long *tbcol,
+           int *status);
+int fits_get_section_range(char **ptr,long *secmin,long *secmax,long *incre,
+              int *status);
+/* ffmbyt should not normally be used in application programs, but it is
+   defined here as a publicly available routine because there are a few
+   rare cases where it is needed
+*/ 
+int ffmbyt(fitsfile *fptr, LONGLONG bytpos, int ignore_err, int *status);
+/*----------------- write single keywords --------------*/
+int ffpky(fitsfile *fptr, int datatype, const char *keyname, void *value,
+          const char *comm, int *status);
+int ffprec(fitsfile *fptr, const char *card, int *status);
+int ffpcom(fitsfile *fptr, const char *comm, int *status);
+int ffpunt(fitsfile *fptr, const char *keyname, char *unit, int *status);
+int ffphis(fitsfile *fptr, const char *history, int *status);
+int ffpdat(fitsfile *fptr, int *status);
+int ffverifydate(int year, int month, int day, int *status);
+int ffgstm(char *timestr, int *timeref, int *status);
+int ffgsdt(int *day, int *month, int *year, int *status);
+int ffdt2s(int year, int month, int day, char *datestr, int *status);
+int fftm2s(int year, int month, int day, int hour, int minute, double second,
+          int decimals, char *datestr, int *status);
+int ffs2dt(char *datestr, int *year, int *month, int *day, int *status);
+int ffs2tm(char *datestr, int *year, int *month, int *day, int *hour,
+          int *minute, double *second, int *status);
+int ffpkyu(fitsfile *fptr, const char *keyname, const char *comm, int *status);
+int ffpkys(fitsfile *fptr, const char *keyname, char *value, const char *comm,int *status);
+int ffpkls(fitsfile *fptr, const char *keyname, const char *value, const char *comm,int *status);
+int ffplsw(fitsfile *fptr, int *status);
+int ffpkyl(fitsfile *fptr, const char *keyname, int  value, const char *comm, int *status);
+int ffpkyj(fitsfile *fptr, const char *keyname, LONGLONG value, const char *comm, int *status);
+int ffpkyf(fitsfile *fptr, const char *keyname, float value, int decim, const char *comm,
+          int *status);
+int ffpkye(fitsfile *fptr, const char *keyname, float  value, int decim, const char *comm,
+          int *status);
+int ffpkyg(fitsfile *fptr, const char *keyname, double value, int decim, const char *comm,
+          int *status);
+int ffpkyd(fitsfile *fptr, const char *keyname, double value, int decim, const char *comm,
+          int *status);
+int ffpkyc(fitsfile *fptr, const char *keyname, float *value, int decim, const char *comm,
+          int *status);
+int ffpkym(fitsfile *fptr, const char *keyname, double *value, int decim, const char *comm,
+          int *status);
+int ffpkfc(fitsfile *fptr, const char *keyname, float *value, int decim, const char *comm,
+          int *status);
+int ffpkfm(fitsfile *fptr, const char *keyname, double *value, int decim, const char *comm,
+          int *status);
+int ffpkyt(fitsfile *fptr, const char *keyname, long intval, double frac, const char *comm,
+          int *status);
+int ffptdm( fitsfile *fptr, int colnum, int naxis, long naxes[], int *status);
+int ffptdmll( fitsfile *fptr, int colnum, int naxis, LONGLONG naxes[], int *status);
+
+/*----------------- write array of keywords --------------*/
+int ffpkns(fitsfile *fptr, const char *keyroot, int nstart, int nkey, char *value[],
+           char *comm[], int *status);
+int ffpknl(fitsfile *fptr, const char *keyroot, int nstart, int nkey, int *value,
+           char *comm[], int *status);
+int ffpknj(fitsfile *fptr, const char *keyroot, int nstart, int nkey, long *value,
+           char *comm[], int *status);
+int ffpknjj(fitsfile *fptr, const char *keyroot, int nstart, int nkey, LONGLONG *value,
+           char *comm[], int *status);
+int ffpknf(fitsfile *fptr, const char *keyroot, int nstart, int nkey, float *value,
+           int decim, char *comm[], int *status);
+int ffpkne(fitsfile *fptr, const char *keyroot, int nstart, int nkey, float *value,
+           int decim, char *comm[], int *status);
+int ffpkng(fitsfile *fptr, const char *keyroot, int nstart, int nkey, double *value,
+           int decim, char *comm[], int *status);
+int ffpknd(fitsfile *fptr, const char *keyroot, int nstart, int nkey, double *value,
+           int decim, char *comm[], int *status);
+int ffcpky(fitsfile *infptr,fitsfile *outfptr,int incol,int outcol,
+           char *rootname, int *status); 
+
+/*----------------- write required header keywords --------------*/
+int ffphps( fitsfile *fptr, int bitpix, int naxis, long naxes[], int *status);
+int ffphpsll( fitsfile *fptr, int bitpix, int naxis, LONGLONG naxes[], int *status);
+int ffphpr( fitsfile *fptr, int simple, int bitpix, int naxis, long naxes[],
+            LONGLONG pcount, LONGLONG gcount, int extend, int *status);
+int ffphprll( fitsfile *fptr, int simple, int bitpix, int naxis, LONGLONG naxes[],
+            LONGLONG pcount, LONGLONG gcount, int extend, int *status);
+int ffphtb(fitsfile *fptr, LONGLONG naxis1, LONGLONG naxis2, int tfields, char **ttype,
+          long *tbcol, char **tform, char **tunit, const char *extname, int *status);
+int ffphbn(fitsfile *fptr, LONGLONG naxis2, int tfields, char **ttype,
+          char **tform, char **tunit, const char *extname, LONGLONG pcount, int *status);
+int ffphext( fitsfile *fptr, const char *xtension, int bitpix, int naxis, long naxes[],
+            LONGLONG pcount, LONGLONG gcount, int *status);
+/*----------------- write template keywords --------------*/
+int ffpktp(fitsfile *fptr, const char *filename, int *status);
+
+/*------------------ get header information --------------*/
+int ffghsp(fitsfile *fptr, int *nexist, int *nmore, int *status);
+int ffghps(fitsfile *fptr, int *nexist, int *position, int *status);
+ 
+/*------------------ move position in header -------------*/
+int ffmaky(fitsfile *fptr, int nrec, int *status);
+int ffmrky(fitsfile *fptr, int nrec, int *status);
+ 
+/*------------------ read single keywords -----------------*/
+int ffgnxk(fitsfile *fptr, char **inclist, int ninc, char **exclist,
+           int nexc, char *card, int  *status);
+int ffgrec(fitsfile *fptr, int nrec,      char *card, int *status);
+int ffgcrd(fitsfile *fptr, const char *keyname, char *card, int *status);
+int ffgstr(fitsfile *fptr, const char *string, char *card, int *status);
+int ffgunt(fitsfile *fptr, const char *keyname, char *unit, int  *status);
+int ffgkyn(fitsfile *fptr, int nkey, char *keyname, char *keyval, char *comm,
+           int *status);
+int ffgkey(fitsfile *fptr, const char *keyname, char *keyval, char *comm,
+           int *status);
+ 
+int ffgky( fitsfile *fptr, int datatype, const char *keyname, void *value,
+           char *comm, int *status);
+int ffgkys(fitsfile *fptr, const char *keyname, char *value, char *comm, int *status);
+int ffgkls(fitsfile *fptr, const char *keyname, char **value, char *comm, int *status);
+int fffkls(char *value, int *status);
+int ffgkyl(fitsfile *fptr, const char *keyname, int *value, char *comm, int *status);
+int ffgkyj(fitsfile *fptr, const char *keyname, long *value, char *comm, int *status);
+int ffgkyjj(fitsfile *fptr, const char *keyname, LONGLONG *value, char *comm, int *status);
+int ffgkye(fitsfile *fptr, const char *keyname, float *value, char *comm,int *status);
+int ffgkyd(fitsfile *fptr, const char *keyname, double *value,char *comm,int *status);
+int ffgkyc(fitsfile *fptr, const char *keyname, float *value, char *comm,int *status);
+int ffgkym(fitsfile *fptr, const char *keyname, double *value,char *comm,int *status);
+int ffgkyt(fitsfile *fptr, const char *keyname, long *ivalue, double *dvalue,
+           char *comm, int *status);
+int ffgtdm(fitsfile *fptr, int colnum, int maxdim, int *naxis, long naxes[],
+           int *status);
+int ffgtdmll(fitsfile *fptr, int colnum, int maxdim, int *naxis, LONGLONG naxes[],
+           int *status);
+int ffdtdm(fitsfile *fptr, char *tdimstr, int colnum, int maxdim,
+           int *naxis, long naxes[], int *status);
+int ffdtdmll(fitsfile *fptr, char *tdimstr, int colnum, int maxdim,
+           int *naxis, LONGLONG naxes[], int *status);
+
+/*------------------ read array of keywords -----------------*/
+int ffgkns(fitsfile *fptr, const char *keyname, int nstart, int nmax, char *value[],
+           int *nfound,  int *status);
+int ffgknl(fitsfile *fptr, const char *keyname, int nstart, int nmax, int *value,
+           int *nfound, int *status);
+int ffgknj(fitsfile *fptr, const char *keyname, int nstart, int nmax, long *value,
+           int *nfound, int *status);
+int ffgknjj(fitsfile *fptr, const char *keyname, int nstart, int nmax, LONGLONG *value,
+           int *nfound, int *status);
+int ffgkne(fitsfile *fptr, const char *keyname, int nstart, int nmax, float *value,
+           int *nfound, int *status);
+int ffgknd(fitsfile *fptr, const char *keyname, int nstart, int nmax, double *value,
+           int *nfound, int *status);
+int ffh2st(fitsfile *fptr, char **header, int  *status);
+int ffhdr2str( fitsfile *fptr,  int exclude_comm, char **exclist,
+   int nexc, char **header, int *nkeys, int  *status);
+int ffcnvthdr2str( fitsfile *fptr,  int exclude_comm, char **exclist,
+   int nexc, char **header, int *nkeys, int  *status);
+
+/*----------------- read required header keywords --------------*/
+int ffghpr(fitsfile *fptr, int maxdim, int *simple, int *bitpix, int *naxis,
+          long naxes[], long *pcount, long *gcount, int *extend, int *status);
+ 
+int ffghprll(fitsfile *fptr, int maxdim, int *simple, int *bitpix, int *naxis,
+          LONGLONG naxes[], long *pcount, long *gcount, int *extend, int *status);
+
+int ffghtb(fitsfile *fptr,int maxfield, long *naxis1, long *naxis2,
+           int *tfields, char **ttype, long *tbcol, char **tform, char **tunit,
+           char *extname,  int *status);
+
+int ffghtbll(fitsfile *fptr,int maxfield, LONGLONG *naxis1, LONGLONG *naxis2,
+           int *tfields, char **ttype, LONGLONG *tbcol, char **tform, char **tunit,
+           char *extname,  int *status);
+ 
+ 
+int ffghbn(fitsfile *fptr, int maxfield, long *naxis2, int *tfields,
+           char **ttype, char **tform, char **tunit, char *extname,
+           long *pcount, int *status);
+
+int ffghbnll(fitsfile *fptr, int maxfield, LONGLONG *naxis2, int *tfields,
+           char **ttype, char **tform, char **tunit, char *extname,
+           LONGLONG *pcount, int *status);
+
+/*--------------------- update keywords ---------------*/
+int ffuky(fitsfile *fptr, int datatype, const char *keyname, void *value,
+          char *comm, int *status);
+int ffucrd(fitsfile *fptr, const char *keyname, char *card, int *status);
+int ffukyu(fitsfile *fptr, const char *keyname, char *comm, int *status);
+int ffukys(fitsfile *fptr, const char *keyname, char *value, char *comm, int *status);
+int ffukls(fitsfile *fptr, const char *keyname, char *value, char *comm, int *status);
+int ffukyl(fitsfile *fptr, const char *keyname, int value, char *comm, int *status);
+int ffukyj(fitsfile *fptr, const char *keyname, LONGLONG value, char *comm, int *status);
+int ffukyf(fitsfile *fptr, const char *keyname, float value, int decim, char *comm,
+          int *status);
+int ffukye(fitsfile *fptr, const char *keyname, float value, int decim, char *comm,
+          int *status);
+int ffukyg(fitsfile *fptr, const char *keyname, double value, int decim, char *comm,
+          int *status);
+int ffukyd(fitsfile *fptr, const char *keyname, double value, int decim, char *comm,
+          int *status);
+int ffukyc(fitsfile *fptr, const char *keyname, float *value, int decim, char *comm,
+          int *status);
+int ffukym(fitsfile *fptr, const char *keyname, double *value, int decim, char *comm,
+          int *status);
+int ffukfc(fitsfile *fptr, const char *keyname, float *value, int decim, char *comm,
+          int *status);
+int ffukfm(fitsfile *fptr, const char *keyname, double *value, int decim, char *comm,
+          int *status);
+
+/*--------------------- modify keywords ---------------*/
+int ffmrec(fitsfile *fptr, int nkey, char *card, int *status);
+int ffmcrd(fitsfile *fptr, const char *keyname, char *card, int *status);
+int ffmnam(fitsfile *fptr, const char *oldname, const char *newname, int *status);
+int ffmcom(fitsfile *fptr, const char *keyname, char *comm, int *status);
+int ffmkyu(fitsfile *fptr, const char *keyname, char *comm, int *status);
+int ffmkys(fitsfile *fptr, const char *keyname, char *value, char *comm,int *status);
+int ffmkls(fitsfile *fptr, const char *keyname, char *value, char *comm,int *status);
+int ffmkyl(fitsfile *fptr, const char *keyname, int value, char *comm, int *status);
+int ffmkyj(fitsfile *fptr, const char *keyname, LONGLONG value, char *comm, int *status);
+int ffmkyf(fitsfile *fptr, const char *keyname, float value, int decim, char *comm,
+          int *status);
+int ffmkye(fitsfile *fptr, const char *keyname, float value, int decim, char *comm,
+          int *status);
+int ffmkyg(fitsfile *fptr, const char *keyname, double value, int decim, char *comm,
+          int *status);
+int ffmkyd(fitsfile *fptr, const char *keyname, double value, int decim, char *comm,
+          int *status);
+int ffmkyc(fitsfile *fptr, const char *keyname, float *value, int decim, char *comm,
+          int *status);
+int ffmkym(fitsfile *fptr, const char *keyname, double *value, int decim, char *comm,
+          int *status);
+int ffmkfc(fitsfile *fptr, const char *keyname, float *value, int decim, char *comm,
+          int *status);
+int ffmkfm(fitsfile *fptr, const char *keyname, double *value, int decim, char *comm,
+          int *status);
+ 
+/*--------------------- insert keywords ---------------*/
+int ffirec(fitsfile *fptr, int nkey, char *card, int *status);
+int ffikey(fitsfile *fptr, char *card, int *status);
+int ffikyu(fitsfile *fptr, const char *keyname, char *comm, int *status);
+int ffikys(fitsfile *fptr, const char *keyname, char *value, char *comm,int *status);
+int ffikls(fitsfile *fptr, const char *keyname, char *value, char *comm,int *status);
+int ffikyl(fitsfile *fptr, const char *keyname, int value, char *comm, int *status);
+int ffikyj(fitsfile *fptr, const char *keyname, LONGLONG value, char *comm, int *status);
+int ffikyf(fitsfile *fptr, const char *keyname, float value, int decim, char *comm,
+          int *status);
+int ffikye(fitsfile *fptr, const char *keyname, float value, int decim, char *comm,
+          int *status);
+int ffikyg(fitsfile *fptr, const char *keyname, double value, int decim, char *comm,
+          int *status);
+int ffikyd(fitsfile *fptr, const char *keyname, double value, int decim, char *comm,
+          int *status);
+int ffikyc(fitsfile *fptr, const char *keyname, float *value, int decim, char *comm,
+          int *status);
+int ffikym(fitsfile *fptr, const char *keyname, double *value, int decim, char *comm,
+          int *status);
+int ffikfc(fitsfile *fptr, const char *keyname, float *value, int decim, char *comm,
+          int *status);
+int ffikfm(fitsfile *fptr, const char *keyname, double *value, int decim, char *comm,
+          int *status);
+
+/*--------------------- delete keywords ---------------*/
+int ffdkey(fitsfile *fptr, const char *keyname, int *status);
+int ffdstr(fitsfile *fptr, const char *string, int *status);
+int ffdrec(fitsfile *fptr, int keypos, int *status);
+ 
+/*--------------------- get HDU information -------------*/
+int ffghdn(fitsfile *fptr, int *chdunum);
+int ffghdt(fitsfile *fptr, int *exttype, int *status);
+int ffghad(fitsfile *fptr, long *headstart, long *datastart, long *dataend,
+           int *status);
+int ffghadll(fitsfile *fptr, LONGLONG *headstart, LONGLONG *datastart,
+           LONGLONG *dataend, int *status);
+int ffghof(fitsfile *fptr, OFF_T *headstart, OFF_T *datastart, OFF_T *dataend,
+           int *status);
+int ffgipr(fitsfile *fptr, int maxaxis, int *imgtype, int *naxis,
+           long *naxes, int *status);
+int ffgiprll(fitsfile *fptr, int maxaxis, int *imgtype, int *naxis,
+           LONGLONG *naxes, int *status);
+int ffgidt(fitsfile *fptr, int *imgtype, int *status);
+int ffgiet(fitsfile *fptr, int *imgtype, int *status);
+int ffgidm(fitsfile *fptr, int *naxis,  int *status);
+int ffgisz(fitsfile *fptr, int nlen, long *naxes, int *status);
+int ffgiszll(fitsfile *fptr, int nlen, LONGLONG *naxes, int *status);
+
+/*--------------------- HDU operations -------------*/
+int ffmahd(fitsfile *fptr, int hdunum, int *exttype, int *status);
+int ffmrhd(fitsfile *fptr, int hdumov, int *exttype, int *status);
+int ffmnhd(fitsfile *fptr, int exttype, char *hduname, int hduvers,
+           int *status);
+int ffthdu(fitsfile *fptr, int *nhdu, int *status);
+int ffcrhd(fitsfile *fptr, int *status);
+int ffcrim(fitsfile *fptr, int bitpix, int naxis, long *naxes, int *status);
+int ffcrimll(fitsfile *fptr, int bitpix, int naxis, LONGLONG *naxes, int *status);
+int ffcrtb(fitsfile *fptr, int tbltype, LONGLONG naxis2, int tfields, char **ttype,
+           char **tform, char **tunit, const char *extname, int *status);
+int ffiimg(fitsfile *fptr, int bitpix, int naxis, long *naxes, int *status);
+int ffiimgll(fitsfile *fptr, int bitpix, int naxis, LONGLONG *naxes, int *status);
+int ffitab(fitsfile *fptr, LONGLONG naxis1, LONGLONG naxis2, int tfields, char **ttype,
+           long *tbcol, char **tform, char **tunit, const char *extname, int *status);
+int ffibin(fitsfile *fptr, LONGLONG naxis2, int tfields, char **ttype, char **tform,
+           char **tunit, const char *extname, LONGLONG pcount, int *status);
+int ffrsim(fitsfile *fptr, int bitpix, int naxis, long *naxes, int *status);
+int ffrsimll(fitsfile *fptr, int bitpix, int naxis, LONGLONG *naxes, int *status);
+int ffdhdu(fitsfile *fptr, int *hdutype, int *status);
+int ffcopy(fitsfile *infptr, fitsfile *outfptr, int morekeys, int *status);
+int ffcpfl(fitsfile *infptr, fitsfile *outfptr, int prev, int cur, int follow,
+            int *status);
+int ffcphd(fitsfile *infptr, fitsfile *outfptr, int *status);
+int ffcpdt(fitsfile *infptr, fitsfile *outfptr, int *status);
+int ffchfl(fitsfile *fptr, int *status);
+int ffcdfl(fitsfile *fptr, int *status);
+int ffwrhdu(fitsfile *fptr, FILE *outstream, int *status);
+
+int ffrdef(fitsfile *fptr, int *status);
+int ffhdef(fitsfile *fptr, int morekeys, int *status);
+int ffpthp(fitsfile *fptr, long theap, int *status);
+ 
+int ffcsum(fitsfile *fptr, long nrec, unsigned long *sum, int *status);
+void ffesum(unsigned long sum, int complm, char *ascii);
+unsigned long ffdsum(char *ascii, int complm, unsigned long *sum);
+int ffpcks(fitsfile *fptr, int *status);
+int ffupck(fitsfile *fptr, int *status);
+int ffvcks(fitsfile *fptr, int *datastatus, int *hdustatus, int *status);
+int ffgcks(fitsfile *fptr, unsigned long *datasum, unsigned long *hdusum,
+    int *status);
+ 
+/*--------------------- define scaling or null values -------------*/
+int ffpscl(fitsfile *fptr, double scale, double zero, int *status);
+int ffpnul(fitsfile *fptr, LONGLONG nulvalue, int *status);
+int fftscl(fitsfile *fptr, int colnum, double scale, double zero, int *status);
+int fftnul(fitsfile *fptr, int colnum, LONGLONG nulvalue, int *status);
+int ffsnul(fitsfile *fptr, int colnum, char *nulstring, int *status);
+ 
+/*--------------------- get column information -------------*/
+int ffgcno(fitsfile *fptr, int casesen, char *templt, int  *colnum,
+           int *status);
+int ffgcnn(fitsfile *fptr, int casesen, char *templt, char *colname,
+           int *colnum, int *status);
+ 
+int ffgtcl(fitsfile *fptr, int colnum, int *typecode, long *repeat,
+           long *width, int *status);
+int ffgtclll(fitsfile *fptr, int colnum, int *typecode, LONGLONG *repeat,
+           LONGLONG *width, int *status);
+int ffeqty(fitsfile *fptr, int colnum, int *typecode, long *repeat,
+           long *width, int *status);
+int ffeqtyll(fitsfile *fptr, int colnum, int *typecode, LONGLONG *repeat,
+           LONGLONG *width, int *status);
+int ffgncl(fitsfile *fptr, int  *ncols, int *status);
+int ffgnrw(fitsfile *fptr, long *nrows, int *status);
+int ffgnrwll(fitsfile *fptr, LONGLONG *nrows, int *status);
+int ffgacl(fitsfile *fptr, int colnum, char *ttype, long *tbcol,
+           char *tunit, char *tform, double *tscal, double *tzero,
+           char *tnull, char *tdisp, int *status);
+int ffgbcl(fitsfile *fptr, int colnum, char *ttype, char *tunit,
+           char *dtype, long *repeat, double *tscal, double *tzero,
+           long *tnull, char *tdisp, int  *status);
+int ffgbclll(fitsfile *fptr, int colnum, char *ttype, char *tunit,
+           char *dtype, LONGLONG *repeat, double *tscal, double *tzero,
+           LONGLONG *tnull, char *tdisp, int  *status);
+int ffgrsz(fitsfile *fptr, long *nrows, int *status);
+int ffgcdw(fitsfile *fptr, int colnum, int *width, int *status);
+
+/*--------------------- read primary array or image elements -------------*/
+int ffgpxv(fitsfile *fptr, int  datatype, long *firstpix, LONGLONG nelem,
+          void *nulval, void *array, int *anynul, int *status);
+int ffgpxvll(fitsfile *fptr, int  datatype, LONGLONG *firstpix, LONGLONG nelem,
+          void *nulval, void *array, int *anynul, int *status);
+int ffgpxf(fitsfile *fptr, int  datatype, long *firstpix, LONGLONG nelem,
+           void *array, char *nullarray, int *anynul, int *status);
+int ffgpxfll(fitsfile *fptr, int  datatype, LONGLONG *firstpix, LONGLONG nelem,
+           void *array, char *nullarray, int *anynul, int *status);
+int ffgsv(fitsfile *fptr, int datatype, long *blc, long *trc, long *inc,
+          void *nulval, void *array, int *anynul, int  *status);
+
+int ffgpv(fitsfile *fptr, int  datatype, LONGLONG firstelem, LONGLONG nelem,
+          void *nulval, void *array, int *anynul, int  *status);
+int ffgpf(fitsfile *fptr, int  datatype, LONGLONG firstelem, LONGLONG nelem,
+          void *array, char *nullarray, int  *anynul, int  *status);
+int ffgpvb(fitsfile *fptr, long group, LONGLONG firstelem, LONGLONG nelem, unsigned
+           char nulval, unsigned char *array, int *anynul, int *status);
+int ffgpvsb(fitsfile *fptr, long group, LONGLONG firstelem, LONGLONG nelem, signed
+           char nulval, signed char *array, int *anynul, int *status);
+int ffgpvui(fitsfile *fptr, long group, LONGLONG firstelem, LONGLONG nelem,
+           unsigned short nulval, unsigned short *array, int *anynul, 
+           int *status);
+int ffgpvi(fitsfile *fptr, long group, LONGLONG firstelem, LONGLONG nelem,
+           short nulval, short *array, int *anynul, int *status);
+int ffgpvuj(fitsfile *fptr, long group, LONGLONG firstelem, LONGLONG nelem,
+           unsigned long nulval, unsigned long *array, int *anynul, 
+           int *status);
+int ffgpvj(fitsfile *fptr, long group, LONGLONG firstelem, LONGLONG nelem,
+           long nulval, long *array, int *anynul, int *status);
+int ffgpvjj(fitsfile *fptr, long group, LONGLONG firstelem, LONGLONG nelem,
+           LONGLONG nulval, LONGLONG *array, int *anynul, int *status);
+int ffgpvuk(fitsfile *fptr, long group, LONGLONG firstelem, LONGLONG nelem,
+           unsigned int nulval, unsigned int *array, int *anynul, int *status);
+int ffgpvk(fitsfile *fptr, long group, LONGLONG firstelem, LONGLONG nelem,
+           int nulval, int *array, int *anynul, int *status);
+int ffgpve(fitsfile *fptr, long group, LONGLONG firstelem, LONGLONG nelem,
+           float nulval, float *array, int *anynul, int *status);
+int ffgpvd(fitsfile *fptr, long group, LONGLONG firstelem, LONGLONG nelem,
+           double nulval, double *array, int *anynul, int *status);
+ 
+int ffgpfb(fitsfile *fptr, long group, LONGLONG firstelem, LONGLONG nelem,
+           unsigned char *array, char *nularray, int *anynul, int *status);
+int ffgpfsb(fitsfile *fptr, long group, LONGLONG firstelem, LONGLONG nelem,
+           signed char *array, char *nularray, int *anynul, int *status);
+int ffgpfui(fitsfile *fptr, long group, LONGLONG firstelem, LONGLONG nelem,
+           unsigned short *array, char *nularray, int *anynul, int *status);
+int ffgpfi(fitsfile *fptr, long group, LONGLONG firstelem, LONGLONG nelem,
+           short *array, char *nularray, int *anynul, int *status);
+int ffgpfuj(fitsfile *fptr, long group, LONGLONG firstelem, LONGLONG nelem,
+           unsigned long *array, char *nularray, int *anynul, int *status);
+int ffgpfj(fitsfile *fptr, long group, LONGLONG firstelem, LONGLONG nelem,
+           long *array, char *nularray, int *anynul, int *status);
+int ffgpfjj(fitsfile *fptr, long group, LONGLONG firstelem, LONGLONG nelem,
+           LONGLONG *array, char *nularray, int *anynul, int *status);
+int ffgpfuk(fitsfile *fptr, long group, LONGLONG firstelem, LONGLONG nelem,
+           unsigned int *array, char *nularray, int *anynul, int *status);
+int ffgpfk(fitsfile *fptr, long group, LONGLONG firstelem, LONGLONG nelem,
+           int *array, char *nularray, int *anynul, int *status);
+int ffgpfe(fitsfile *fptr, long group, LONGLONG firstelem, LONGLONG nelem,
+           float *array, char *nularray, int *anynul, int *status);
+int ffgpfd(fitsfile *fptr, long group, LONGLONG firstelem, LONGLONG nelem,
+           double *array, char *nularray, int *anynul, int *status);
+ 
+int ffg2db(fitsfile *fptr, long group, unsigned char nulval, LONGLONG ncols,
+           LONGLONG naxis1, LONGLONG naxis2, unsigned char *array,
+           int *anynul, int *status);
+int ffg2dsb(fitsfile *fptr, long group, signed char nulval, LONGLONG ncols,
+           LONGLONG naxis1, LONGLONG naxis2, signed char *array,
+           int *anynul, int *status);
+int ffg2dui(fitsfile *fptr, long group, unsigned short nulval, LONGLONG ncols,
+           LONGLONG naxis1, LONGLONG naxis2, unsigned short *array,
+           int *anynul, int *status);
+int ffg2di(fitsfile *fptr, long group, short nulval, LONGLONG ncols,
+           LONGLONG naxis1, LONGLONG naxis2, short *array,
+           int *anynul, int *status);
+int ffg2duj(fitsfile *fptr, long group, unsigned long nulval, LONGLONG ncols,
+           LONGLONG naxis1, LONGLONG naxis2, unsigned long *array,
+           int *anynul, int *status);
+int ffg2dj(fitsfile *fptr, long group, long nulval, LONGLONG ncols,
+           LONGLONG naxis1, LONGLONG naxis2, long *array,
+           int *anynul, int *status);
+int ffg2djj(fitsfile *fptr, long group, LONGLONG nulval, LONGLONG ncols,
+           LONGLONG naxis1, LONGLONG naxis2, LONGLONG *array,
+           int *anynul, int *status);
+int ffg2duk(fitsfile *fptr, long group, unsigned int nulval, LONGLONG ncols,
+           LONGLONG naxis1, LONGLONG naxis2, unsigned int *array,
+           int *anynul, int *status);
+int ffg2dk(fitsfile *fptr, long group, int nulval, LONGLONG ncols,
+           LONGLONG naxis1, LONGLONG naxis2, int *array,
+           int *anynul, int *status);
+int ffg2de(fitsfile *fptr, long group, float nulval, LONGLONG ncols,
+           LONGLONG naxis1, LONGLONG naxis2, float *array,
+           int *anynul, int *status);
+int ffg2dd(fitsfile *fptr, long group, double nulval, LONGLONG ncols,
+           LONGLONG naxis1, LONGLONG naxis2, double *array,
+           int *anynul, int *status);
+ 
+int ffg3db(fitsfile *fptr, long group, unsigned char nulval, LONGLONG ncols,
+           LONGLONG nrows, LONGLONG naxis1, LONGLONG naxis2, LONGLONG naxis3,
+           unsigned char *array, int *anynul, int *status);
+int ffg3dsb(fitsfile *fptr, long group, signed char nulval, LONGLONG ncols,
+           LONGLONG nrows, LONGLONG naxis1, LONGLONG naxis2, LONGLONG naxis3,
+           signed char *array, int *anynul, int *status);
+int ffg3dui(fitsfile *fptr, long group, unsigned short nulval, LONGLONG ncols,
+           LONGLONG nrows, LONGLONG naxis1, LONGLONG naxis2, LONGLONG naxis3,
+           unsigned short *array, int *anynul, int *status);
+int ffg3di(fitsfile *fptr, long group, short nulval, LONGLONG ncols,
+           LONGLONG nrows, LONGLONG naxis1, LONGLONG naxis2, LONGLONG naxis3,
+           short *array, int *anynul, int *status);
+int ffg3duj(fitsfile *fptr, long group, unsigned long nulval, LONGLONG ncols,
+           LONGLONG nrows, LONGLONG naxis1, LONGLONG naxis2, LONGLONG naxis3,
+           unsigned long *array, int *anynul, int *status);
+int ffg3dj(fitsfile *fptr, long group, long nulval, LONGLONG ncols,
+           LONGLONG nrows, LONGLONG naxis1, LONGLONG naxis2, LONGLONG naxis3,
+           long *array, int *anynul, int *status);
+int ffg3djj(fitsfile *fptr, long group, LONGLONG nulval, LONGLONG ncols,
+           LONGLONG nrows, LONGLONG naxis1, LONGLONG naxis2, LONGLONG naxis3,
+           LONGLONG *array, int *anynul, int *status);
+int ffg3duk(fitsfile *fptr, long group, unsigned int nulval, LONGLONG ncols,
+           LONGLONG nrows, LONGLONG naxis1, LONGLONG naxis2, LONGLONG naxis3,
+           unsigned int *array, int *anynul, int *status);
+int ffg3dk(fitsfile *fptr, long group, int nulval, LONGLONG ncols,
+           LONGLONG nrows, LONGLONG naxis1, LONGLONG naxis2, LONGLONG naxis3,
+           int *array, int *anynul, int *status);
+int ffg3de(fitsfile *fptr, long group, float nulval, LONGLONG ncols,
+           LONGLONG nrows, LONGLONG naxis1, LONGLONG naxis2, LONGLONG naxis3,
+           float *array, int *anynul, int *status);
+int ffg3dd(fitsfile *fptr, long group, double nulval, LONGLONG ncols,
+           LONGLONG nrows, LONGLONG naxis1, LONGLONG naxis2, LONGLONG naxis3,
+           double *array, int *anynul, int *status);
+ 
+int ffgsvb(fitsfile *fptr, int colnum, int naxis, long *naxes, long *blc,
+  long *trc, long *inc, unsigned char nulval, unsigned char *array,
+  int *anynul, int *status);
+int ffgsvsb(fitsfile *fptr, int colnum, int naxis, long *naxes, long *blc,
+  long *trc, long *inc, signed char nulval, signed char *array,
+  int *anynul, int *status);
+int ffgsvui(fitsfile *fptr, int colnum, int naxis, long *naxes, long *blc,
+  long *trc, long *inc, unsigned short nulval, unsigned short *array, 
+  int *anynul, int *status);
+int ffgsvi(fitsfile *fptr, int colnum, int naxis, long *naxes, long *blc,
+  long *trc, long *inc, short nulval, short *array, int *anynul, int *status);
+int ffgsvuj(fitsfile *fptr, int colnum, int naxis, long *naxes, long *blc,
+  long *trc, long *inc, unsigned long nulval, unsigned long *array, 
+  int *anynul, int *status);
+int ffgsvj(fitsfile *fptr, int colnum, int naxis, long *naxes, long *blc,
+  long *trc, long *inc, long nulval, long *array, int *anynul, int *status);
+int ffgsvjj(fitsfile *fptr, int colnum, int naxis, long *naxes, long *blc,
+  long *trc, long *inc, LONGLONG nulval, LONGLONG *array, int *anynul,
+  int *status);
+int ffgsvuk(fitsfile *fptr, int colnum, int naxis, long *naxes, long *blc,
+  long *trc, long *inc, unsigned int nulval, unsigned int *array,
+  int *anynul, int *status);
+int ffgsvk(fitsfile *fptr, int colnum, int naxis, long *naxes, long *blc,
+  long *trc, long *inc, int nulval, int *array, int *anynul, int *status);
+int ffgsve(fitsfile *fptr, int colnum, int naxis, long *naxes, long *blc,
+  long *trc, long *inc, float nulval, float *array, int *anynul, int *status);
+int ffgsvd(fitsfile *fptr, int colnum, int naxis, long *naxes, long *blc,
+  long *trc, long *inc, double nulval, double *array, int *anynul,
+  int *status);
+ 
+int ffgsfb(fitsfile *fptr, int colnum, int naxis, long *naxes, long *blc,
+  long *trc, long *inc, unsigned char *array, char *flagval,
+  int *anynul, int *status);
+int ffgsfsb(fitsfile *fptr, int colnum, int naxis, long *naxes, long *blc,
+  long *trc, long *inc, signed char *array, char *flagval,
+  int *anynul, int *status);
+int ffgsfui(fitsfile *fptr, int colnum, int naxis, long *naxes, long *blc,
+  long *trc, long *inc, unsigned short *array, char *flagval, int *anynul, 
+  int *status);
+int ffgsfi(fitsfile *fptr, int colnum, int naxis, long *naxes, long *blc,
+  long *trc, long *inc, short *array, char *flagval, int *anynul, int *status);
+int ffgsfuj(fitsfile *fptr, int colnum, int naxis, long *naxes, long *blc,
+  long  *trc, long *inc, unsigned long *array, char *flagval, int *anynul,
+  int *status);
+int ffgsfj(fitsfile *fptr, int colnum, int naxis, long *naxes, long *blc,
+  long  *trc, long *inc, long *array, char *flagval, int *anynul, int *status);
+int ffgsfjj(fitsfile *fptr, int colnum, int naxis, long *naxes, long *blc,
+  long  *trc, long *inc, LONGLONG *array, char *flagval, int *anynul,
+  int *status);
+int ffgsfuk(fitsfile *fptr, int colnum, int naxis, long *naxes, long *blc,
+  long  *trc, long *inc, unsigned int *array, char *flagval, int *anynul,
+  int *status);
+int ffgsfk(fitsfile *fptr, int colnum, int naxis, long *naxes, long *blc,
+  long  *trc, long *inc, int *array, char *flagval, int *anynul, int *status);
+int ffgsfe(fitsfile *fptr, int colnum, int naxis, long *naxes, long *blc,
+  long *trc, long *inc, float *array, char *flagval, int *anynul, int *status);
+int ffgsfd(fitsfile *fptr, int colnum, int naxis, long *naxes, long *blc,
+  long *trc, long *inc, double *array, char *flagval, int *anynul,
+  int *status);
+ 
+int ffggpb(fitsfile *fptr, long group, long firstelem, long nelem,
+           unsigned char *array, int *status);
+int ffggpsb(fitsfile *fptr, long group, long firstelem, long nelem,
+           signed char *array, int *status);
+int ffggpui(fitsfile *fptr, long group, long firstelem, long nelem,
+           unsigned short *array, int *status);
+int ffggpi(fitsfile *fptr, long group, long firstelem, long nelem,
+           short *array, int *status);
+int ffggpuj(fitsfile *fptr, long group, long firstelem, long nelem,
+           unsigned long *array, int *status);
+int ffggpj(fitsfile *fptr, long group, long firstelem, long nelem,
+           long *array, int *status);
+int ffggpjj(fitsfile *fptr, long group, long firstelem, long nelem,
+           LONGLONG *array, int *status);
+int ffggpuk(fitsfile *fptr, long group, long firstelem, long nelem,
+           unsigned int *array, int *status);
+int ffggpk(fitsfile *fptr, long group, long firstelem, long nelem,
+           int *array, int *status);
+int ffggpe(fitsfile *fptr, long group, long firstelem, long nelem,
+           float *array, int *status);
+int ffggpd(fitsfile *fptr, long group, long firstelem, long nelem,
+           double *array, int *status);
+ 
+/*--------------------- read column elements -------------*/
+int ffgcv( fitsfile *fptr, int datatype, int colnum, LONGLONG firstrow,
+           LONGLONG firstelem, LONGLONG nelem, void *nulval, void *array, int *anynul,
+           int  *status);
+int ffgcf( fitsfile *fptr, int datatype, int colnum, LONGLONG firstrow,
+           LONGLONG firstelem, LONGLONG nelem, void *array, char *nullarray,
+           int *anynul, int *status);
+int ffgcvs(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, char *nulval, char **array, int *anynul, int *status);
+int ffgcl (fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, char *array, int  *status);
+int ffgcvl (fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, char nulval, char *array, int *anynul, int  *status);
+int ffgcvb(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, unsigned char nulval, unsigned char *array,
+           int *anynul, int *status);
+int ffgcvsb(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, signed char nulval, signed char *array,
+           int *anynul, int *status);
+int ffgcvui(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, unsigned short nulval, unsigned short *array, 
+           int *anynul, int *status);
+int ffgcvi(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, short nulval, short *array, int *anynul, int *status);
+int ffgcvuj(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, unsigned long nulval, unsigned long *array, int *anynul,
+           int *status);
+int ffgcvj(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, long nulval, long *array, int *anynul, int *status);
+int ffgcvjj(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, LONGLONG nulval, LONGLONG *array, int *anynul,
+           int *status);
+int ffgcvuk(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, unsigned int nulval, unsigned int *array, int *anynul,
+           int *status);
+int ffgcvk(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, int nulval, int *array, int *anynul, int *status);
+int ffgcve(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, float nulval, float *array, int *anynul, int *status);
+int ffgcvd(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+         LONGLONG nelem, double nulval, double *array, int *anynul, int *status);
+int ffgcvc(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, float nulval, float *array, int *anynul, int *status);
+int ffgcvm(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+         LONGLONG nelem, double nulval, double *array, int *anynul, int *status);
+
+int ffgcx(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstbit,
+            LONGLONG nbits, char *larray, int *status);
+int ffgcxui(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG nrows,
+            long firstbit, int nbits, unsigned short *array, int *status);
+int ffgcxuk(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG nrows,
+            long firstbit, int nbits, unsigned int *array, int *status);
+
+int ffgcfs(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem, 
+      LONGLONG nelem, char **array, char *nularray, int *anynul, int *status);
+int ffgcfl(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+      LONGLONG nelem, char *array, char *nularray, int *anynul, int *status);
+int ffgcfb(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem, 
+      LONGLONG nelem, unsigned char *array, char *nularray, int *anynul, int *status);
+int ffgcfsb(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+      LONGLONG nelem, signed char *array, char *nularray, int *anynul, int *status);
+int ffgcfui(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+      LONGLONG nelem, unsigned short *array, char *nularray, int *anynul, 
+      int *status);
+int ffgcfi(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+      LONGLONG nelem, short *array, char *nularray, int *anynul, int *status);
+int ffgcfuj(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+      LONGLONG nelem, unsigned long *array, char *nularray, int *anynul,
+      int *status);
+int ffgcfj(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+      LONGLONG nelem, long *array, char *nularray, int *anynul, int *status);
+int ffgcfjj(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+      LONGLONG nelem, LONGLONG *array, char *nularray, int *anynul, int *status);
+int ffgcfuk(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+      LONGLONG nelem, unsigned int *array, char *nularray, int *anynul,
+      int *status);
+int ffgcfk(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+      LONGLONG nelem, int *array, char *nularray, int *anynul, int *status);
+int ffgcfe(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+      LONGLONG nelem, float *array, char *nularray, int *anynul, int *status);
+int ffgcfd(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+      LONGLONG nelem, double *array, char *nularray, int *anynul, int *status);
+int ffgcfc(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+      LONGLONG nelem, float *array, char *nularray, int *anynul, int *status);
+int ffgcfm(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+      LONGLONG nelem, double *array, char *nularray, int *anynul, int *status);
+ 
+int ffgdes(fitsfile *fptr, int colnum, LONGLONG rownum, long *length,
+           long *heapaddr, int *status);
+int ffgdesll(fitsfile *fptr, int colnum, LONGLONG rownum, LONGLONG *length,
+           LONGLONG *heapaddr, int *status);
+int ffgdess(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG nrows, long *length,
+           long *heapaddr, int *status);
+int ffgdessll(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG nrows, LONGLONG *length,
+           LONGLONG *heapaddr, int *status);
+int ffpdes(fitsfile *fptr, int colnum, LONGLONG rownum, LONGLONG length,
+           LONGLONG heapaddr, int *status);
+int fftheap(fitsfile *fptr, LONGLONG *heapsize, LONGLONG *unused, LONGLONG *overlap,
+            int *valid, int *status);
+int ffcmph(fitsfile *fptr, int *status);
+
+int ffgtbb(fitsfile *fptr, LONGLONG firstrow, LONGLONG firstchar, LONGLONG nchars,
+           unsigned char *values, int *status);
+
+int ffgextn(fitsfile *fptr, LONGLONG offset, LONGLONG nelem, void *array, int *status);
+int ffpextn(fitsfile *fptr, LONGLONG offset, LONGLONG nelem, void *array, int *status);
+
+/*------------ write primary array or image elements -------------*/
+int ffppx(fitsfile *fptr, int datatype, long *firstpix, LONGLONG nelem,
+          void *array, int *status);
+int ffppxll(fitsfile *fptr, int datatype, LONGLONG *firstpix, LONGLONG nelem,
+          void *array, int *status);
+int ffppxn(fitsfile *fptr, int datatype, long *firstpix, LONGLONG nelem,
+          void *array, void *nulval, int *status);
+int ffppxnll(fitsfile *fptr, int datatype, LONGLONG *firstpix, LONGLONG nelem,
+          void *array, void *nulval, int *status);
+int ffppr(fitsfile *fptr, int datatype, LONGLONG  firstelem,
+           LONGLONG nelem, void *array, int *status);
+int ffpprb(fitsfile *fptr, long group, LONGLONG firstelem,
+           LONGLONG nelem, unsigned char *array, int *status);
+int ffpprsb(fitsfile *fptr, long group, LONGLONG firstelem,
+           LONGLONG nelem, signed char *array, int *status);
+int ffpprui(fitsfile *fptr, long group, LONGLONG firstelem,
+           LONGLONG nelem, unsigned short *array, int *status);
+int ffppri(fitsfile *fptr, long group, LONGLONG firstelem,
+           LONGLONG nelem, short *array, int *status);
+int ffppruj(fitsfile *fptr, long group, LONGLONG firstelem,
+           LONGLONG nelem, unsigned long *array, int *status);
+int ffpprj(fitsfile *fptr, long group, LONGLONG firstelem,
+           LONGLONG nelem, long *array, int *status);
+int ffppruk(fitsfile *fptr, long group, LONGLONG firstelem,
+           LONGLONG nelem, unsigned int *array, int *status);
+int ffpprk(fitsfile *fptr, long group, LONGLONG firstelem,
+           LONGLONG nelem, int *array, int *status);
+int ffppre(fitsfile *fptr, long group, LONGLONG firstelem,
+           LONGLONG nelem, float *array, int *status);
+int ffpprd(fitsfile *fptr, long group, LONGLONG firstelem,
+           LONGLONG nelem, double *array, int *status);
+int ffpprjj(fitsfile *fptr, long group, LONGLONG firstelem,
+           LONGLONG nelem, LONGLONG *array, int *status);
+
+int ffppru(fitsfile *fptr, long group, LONGLONG firstelem, LONGLONG nelem,
+           int *status);
+int ffpprn(fitsfile *fptr, LONGLONG firstelem, LONGLONG nelem, int *status);
+ 
+int ffppn(fitsfile *fptr, int datatype, LONGLONG  firstelem, LONGLONG nelem,
+          void  *array, void *nulval, int  *status);
+int ffppnb(fitsfile *fptr, long group, LONGLONG firstelem, LONGLONG nelem,
+           unsigned char *array, unsigned char nulval, int *status);
+int ffppnsb(fitsfile *fptr, long group, LONGLONG firstelem, LONGLONG nelem,
+           signed char *array, signed char nulval, int *status);
+int ffppnui(fitsfile *fptr, long group, LONGLONG firstelem,
+           LONGLONG nelem, unsigned short *array, unsigned short nulval,
+           int *status);
+int ffppni(fitsfile *fptr, long group, LONGLONG firstelem,
+           LONGLONG nelem, short *array, short nulval, int *status);
+int ffppnj(fitsfile *fptr, long group, LONGLONG firstelem,
+           LONGLONG nelem, long *array, long nulval, int *status);
+int ffppnuj(fitsfile *fptr, long group, LONGLONG firstelem, LONGLONG nelem,
+           unsigned long *array, unsigned long nulval, int *status);
+int ffppnuk(fitsfile *fptr, long group, LONGLONG firstelem, LONGLONG nelem,
+           unsigned int *array, unsigned int nulval, int *status);
+int ffppnk(fitsfile *fptr, long group, LONGLONG firstelem,
+           LONGLONG nelem, int *array, int nulval, int *status);
+int ffppne(fitsfile *fptr, long group, LONGLONG firstelem,
+           LONGLONG nelem, float *array, float nulval, int *status);
+int ffppnd(fitsfile *fptr, long group, LONGLONG firstelem,
+           LONGLONG nelem, double *array, double nulval, int *status);
+int ffppnjj(fitsfile *fptr, long group, LONGLONG firstelem,
+           LONGLONG nelem, LONGLONG *array, LONGLONG nulval, int *status);
+
+int ffp2db(fitsfile *fptr, long group, LONGLONG ncols, LONGLONG naxis1,
+           LONGLONG naxis2, unsigned char *array, int *status);
+int ffp2dsb(fitsfile *fptr, long group, LONGLONG ncols, LONGLONG naxis1,
+           LONGLONG naxis2, signed char *array, int *status);
+int ffp2dui(fitsfile *fptr, long group, LONGLONG ncols, LONGLONG naxis1,
+           LONGLONG naxis2, unsigned short *array, int *status);
+int ffp2di(fitsfile *fptr, long group, LONGLONG ncols, LONGLONG naxis1,
+           LONGLONG naxis2, short *array, int *status);
+int ffp2duj(fitsfile *fptr, long group, LONGLONG ncols, LONGLONG naxis1,
+           LONGLONG naxis2, unsigned long *array, int *status);
+int ffp2dj(fitsfile *fptr, long group, LONGLONG ncols, LONGLONG naxis1,
+           LONGLONG naxis2, long *array, int *status);
+int ffp2duk(fitsfile *fptr, long group, LONGLONG ncols, LONGLONG naxis1,
+           LONGLONG naxis2, unsigned int *array, int *status);
+int ffp2dk(fitsfile *fptr, long group, LONGLONG ncols, LONGLONG naxis1,
+           LONGLONG naxis2, int *array, int *status);
+int ffp2de(fitsfile *fptr, long group, LONGLONG ncols, LONGLONG naxis1,
+           LONGLONG naxis2, float *array, int *status);
+int ffp2dd(fitsfile *fptr, long group, LONGLONG ncols, LONGLONG naxis1,
+           LONGLONG naxis2, double *array, int *status);
+int ffp2djj(fitsfile *fptr, long group, LONGLONG ncols, LONGLONG naxis1,
+           LONGLONG naxis2, LONGLONG *array, int *status);
+
+int ffp3db(fitsfile *fptr, long group, LONGLONG ncols, LONGLONG nrows, LONGLONG naxis1,
+           LONGLONG naxis2, LONGLONG naxis3, unsigned char *array, int *status);
+int ffp3dsb(fitsfile *fptr, long group, LONGLONG ncols, LONGLONG nrows, LONGLONG naxis1,
+           LONGLONG naxis2, LONGLONG naxis3, signed char *array, int *status);
+int ffp3dui(fitsfile *fptr, long group, LONGLONG ncols, LONGLONG nrows, LONGLONG naxis1,
+           LONGLONG naxis2, LONGLONG naxis3, unsigned short *array, int *status);
+int ffp3di(fitsfile *fptr, long group, LONGLONG ncols, LONGLONG nrows, LONGLONG naxis1,
+           LONGLONG naxis2, LONGLONG naxis3, short *array, int *status);
+int ffp3duj(fitsfile *fptr, long group, LONGLONG ncols, LONGLONG nrows, LONGLONG naxis1,
+           LONGLONG naxis2, LONGLONG naxis3, unsigned long *array, int *status);
+int ffp3dj(fitsfile *fptr, long group, LONGLONG ncols, LONGLONG nrows, LONGLONG naxis1,
+           LONGLONG naxis2, LONGLONG naxis3, long *array, int *status);
+int ffp3duk(fitsfile *fptr, long group, LONGLONG ncols, LONGLONG nrows, LONGLONG naxis1,
+           LONGLONG naxis2, LONGLONG naxis3, unsigned int *array, int *status);
+int ffp3dk(fitsfile *fptr, long group, LONGLONG ncols, LONGLONG nrows, LONGLONG naxis1,
+           LONGLONG naxis2, LONGLONG naxis3, int *array, int *status);
+int ffp3de(fitsfile *fptr, long group, LONGLONG ncols, LONGLONG nrows, LONGLONG naxis1,
+           LONGLONG naxis2, LONGLONG naxis3, float *array, int *status);
+int ffp3dd(fitsfile *fptr, long group, LONGLONG ncols, LONGLONG nrows, LONGLONG naxis1,
+           LONGLONG naxis2, LONGLONG naxis3, double *array, int *status);
+int ffp3djj(fitsfile *fptr, long group, LONGLONG ncols, LONGLONG nrows, LONGLONG naxis1,
+           LONGLONG naxis2, LONGLONG naxis3, LONGLONG *array, int *status);
+
+int ffpss(fitsfile *fptr, int datatype,
+           long *fpixel, long *lpixel, void *array, int *status);
+int ffpssb(fitsfile *fptr, long group, long naxis, long *naxes,
+           long *fpixel, long *lpixel, unsigned char *array, int *status);
+int ffpsssb(fitsfile *fptr, long group, long naxis, long *naxes,
+           long *fpixel, long *lpixel, signed char *array, int *status);
+int ffpssui(fitsfile *fptr, long group, long naxis, long *naxes,
+           long *fpixel, long *lpixel, unsigned short *array, int *status);
+int ffpssi(fitsfile *fptr, long group, long naxis, long *naxes,
+           long *fpixel, long *lpixel, short *array, int *status);
+int ffpssuj(fitsfile *fptr, long group, long naxis, long *naxes,
+           long *fpixel, long *lpixel, unsigned long *array, int *status);
+int ffpssj(fitsfile *fptr, long group, long naxis, long *naxes,
+           long *fpixel, long *lpixel, long *array, int *status);
+int ffpssuk(fitsfile *fptr, long group, long naxis, long *naxes,
+           long *fpixel, long *lpixel, unsigned int *array, int *status);
+int ffpssk(fitsfile *fptr, long group, long naxis, long *naxes,
+           long *fpixel, long *lpixel, int *array, int *status);
+int ffpsse(fitsfile *fptr, long group, long naxis, long *naxes,
+           long *fpixel, long *lpixel, float *array, int *status);
+int ffpssd(fitsfile *fptr, long group, long naxis, long *naxes,
+           long *fpixel, long *lpixel, double *array, int *status);
+int ffpssjj(fitsfile *fptr, long group, long naxis, long *naxes,
+           long *fpixel, long *lpixel, LONGLONG *array, int *status);
+
+int ffpgpb(fitsfile *fptr, long group, long firstelem,
+           long nelem, unsigned char *array, int *status);
+int ffpgpsb(fitsfile *fptr, long group, long firstelem,
+           long nelem, signed char *array, int *status);
+int ffpgpui(fitsfile *fptr, long group, long firstelem,
+           long nelem, unsigned short *array, int *status);
+int ffpgpi(fitsfile *fptr, long group, long firstelem,
+           long nelem, short *array, int *status);
+int ffpgpuj(fitsfile *fptr, long group, long firstelem,
+           long nelem, unsigned long *array, int *status);
+int ffpgpj(fitsfile *fptr, long group, long firstelem,
+           long nelem, long *array, int *status);
+int ffpgpuk(fitsfile *fptr, long group, long firstelem,
+           long nelem, unsigned int *array, int *status);
+int ffpgpk(fitsfile *fptr, long group, long firstelem,
+           long nelem, int *array, int *status);
+int ffpgpe(fitsfile *fptr, long group, long firstelem,
+           long nelem, float *array, int *status);
+int ffpgpd(fitsfile *fptr, long group, long firstelem,
+           long nelem, double *array, int *status);
+int ffpgpjj(fitsfile *fptr, long group, long firstelem,
+           long nelem, LONGLONG *array, int *status);
+
+/*--------------------- iterator functions -------------*/
+int fits_iter_set_by_name(iteratorCol *col, fitsfile *fptr, char *colname,
+          int datatype,  int iotype);
+int fits_iter_set_by_num(iteratorCol *col, fitsfile *fptr, int colnum,
+          int datatype,  int iotype);
+int fits_iter_set_file(iteratorCol *col, fitsfile *fptr);
+int fits_iter_set_colname(iteratorCol *col, char *colname);
+int fits_iter_set_colnum(iteratorCol *col, int colnum);
+int fits_iter_set_datatype(iteratorCol *col, int datatype);
+int fits_iter_set_iotype(iteratorCol *col, int iotype);
+
+fitsfile * fits_iter_get_file(iteratorCol *col);
+char * fits_iter_get_colname(iteratorCol *col);
+int fits_iter_get_colnum(iteratorCol *col);
+int fits_iter_get_datatype(iteratorCol *col);
+int fits_iter_get_iotype(iteratorCol *col);
+void * fits_iter_get_array(iteratorCol *col);
+long fits_iter_get_tlmin(iteratorCol *col);
+long fits_iter_get_tlmax(iteratorCol *col);
+long fits_iter_get_repeat(iteratorCol *col);
+char * fits_iter_get_tunit(iteratorCol *col);
+char * fits_iter_get_tdisp(iteratorCol *col);
+
+int ffiter(int ncols,  iteratorCol *data, long offset, long nPerLoop,
+           int (*workFn)( long totaln, long offset, long firstn,
+             long nvalues, int narrays, iteratorCol *data, void *userPointer),
+           void *userPointer, int *status);
+
+/*--------------------- write column elements -------------*/
+int ffpcl(fitsfile *fptr, int datatype, int colnum, LONGLONG firstrow,
+          LONGLONG firstelem, LONGLONG nelem, void *array, int *status);
+int ffpcls(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, char **array, int *status);
+int ffpcll(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, char *array, int *status);
+int ffpclb(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, unsigned char *array, int *status);
+int ffpclsb(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, signed char *array, int *status);
+int ffpclui(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, unsigned short *array, int *status);
+int ffpcli(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, short *array, int *status);
+int ffpcluj(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, unsigned long *array, int *status);
+int ffpclj(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, long *array, int *status);
+int ffpcluk(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, unsigned int *array, int *status);
+int ffpclk(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, int *array, int *status);
+int ffpcle(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, float *array, int *status);
+int ffpcld(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, double *array, int *status);
+int ffpclc(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, float *array, int *status);
+int ffpclm(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, double *array, int *status);
+int ffpclu(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, int *status);
+int ffprwu(fitsfile *fptr, LONGLONG firstrow, LONGLONG nrows, int *status);
+int ffpcljj(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, LONGLONG *array, int *status);
+int ffpclx(fitsfile *fptr, int colnum, LONGLONG frow, long fbit, long nbit,
+            char *larray, int *status);
+
+int ffpcn(fitsfile *fptr, int datatype, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+          LONGLONG nelem, void *array, void *nulval, int *status);
+int ffpcns( fitsfile *fptr, int  colnum, LONGLONG  firstrow, LONGLONG  firstelem,
+            LONGLONG  nelem, char **array, char  *nulvalue, int  *status);
+int ffpcnl( fitsfile *fptr, int  colnum, LONGLONG  firstrow, LONGLONG  firstelem,
+            LONGLONG  nelem, char *array, char  nulvalue,  int  *status);
+int ffpcnb(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, unsigned char *array, unsigned char nulvalue,
+           int *status);
+int ffpcnsb(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, signed char *array, signed char nulvalue,
+           int *status);
+int ffpcnui(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, unsigned short *array, unsigned short nulvalue,
+           int *status);
+int ffpcni(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, short *array, short nulvalue, int *status);
+int ffpcnuj(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, unsigned long *array, unsigned long nulvalue,
+           int *status);
+int ffpcnj(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, long *array, long nulvalue, int *status);
+int ffpcnuk(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, unsigned int *array, unsigned int nulvalue,
+           int *status);
+int ffpcnk(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, int *array, int nulvalue, int *status);
+int ffpcne(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, float *array, float nulvalue, int *status);
+int ffpcnd(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, double *array, double nulvalue, int *status);
+int ffpcnjj(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, LONGLONG *array, LONGLONG nulvalue, int *status);
+int ffptbb(fitsfile *fptr, LONGLONG firstrow, LONGLONG firstchar, LONGLONG nchars,
+           unsigned char *values, int *status);
+ 
+int ffirow(fitsfile *fptr, LONGLONG firstrow, LONGLONG nrows, int *status);
+int ffdrow(fitsfile *fptr, LONGLONG firstrow, LONGLONG nrows, int *status);
+int ffdrrg(fitsfile *fptr, char *ranges, int *status);
+int ffdrws(fitsfile *fptr, long *rownum,  long nrows, int *status);
+int ffdrwsll(fitsfile *fptr, LONGLONG *rownum,  LONGLONG nrows, int *status);
+int fficol(fitsfile *fptr, int numcol, char *ttype, char *tform, int *status);
+int fficls(fitsfile *fptr, int firstcol, int ncols, char **ttype,
+           char **tform, int *status);
+int ffmvec(fitsfile *fptr, int colnum, LONGLONG newveclen, int *status);
+int ffdcol(fitsfile *fptr, int numcol, int *status);
+int ffcpcl(fitsfile *infptr, fitsfile *outfptr, int incol, int outcol, 
+           int create_col, int *status);
+int ffcprw(fitsfile *infptr, fitsfile *outfptr, LONGLONG firstrow, 
+           LONGLONG nrows, int *status);
+
+/*--------------------- WCS Utilities ------------------*/
+int ffgics(fitsfile *fptr, double *xrval, double *yrval, double *xrpix,
+           double *yrpix, double *xinc, double *yinc, double *rot,
+           char *type, int *status);
+int ffgicsa(fitsfile *fptr, char version, double *xrval, double *yrval, double *xrpix,
+           double *yrpix, double *xinc, double *yinc, double *rot,
+           char *type, int *status);
+int ffgtcs(fitsfile *fptr, int xcol, int ycol, double *xrval,
+           double *yrval, double *xrpix, double *yrpix, double *xinc,
+           double *yinc, double *rot, char *type, int *status);
+int ffwldp(double xpix, double ypix, double xref, double yref,
+           double xrefpix, double yrefpix, double xinc, double yinc,
+           double rot, char *type, double *xpos, double *ypos, int *status);
+int ffxypx(double xpos, double ypos, double xref, double yref, 
+           double xrefpix, double yrefpix, double xinc, double yinc,
+           double rot, char *type, double *xpix, double *ypix, int *status);
+
+/*   WCS support routines (provide interface to Doug Mink's WCS library */
+int ffgiwcs(fitsfile *fptr,  char **header, int *status); 
+int ffgtwcs(fitsfile *fptr, int xcol, int ycol, char **header, int *status);
+
+/*--------------------- lexical parsing routines ------------------*/
+int fftexp( fitsfile *fptr, char *expr, int maxdim,
+	    int *datatype, long *nelem, int *naxis,
+	    long *naxes, int *status );
+
+int fffrow( fitsfile *infptr, char *expr,
+	    long firstrow, long nrows,
+            long *n_good_rows, char *row_status, int *status);
+
+int ffffrw( fitsfile *fptr, char *expr, long *rownum, int *status);
+
+int fffrwc( fitsfile *fptr, char *expr, char *timeCol,    
+            char *parCol, char *valCol, long ntimes,      
+            double *times, char *time_status, int  *status );
+
+int ffsrow( fitsfile *infptr, fitsfile *outfptr, char *expr, 
+            int *status);
+
+int ffcrow( fitsfile *fptr, int datatype, char *expr,
+	    long firstrow, long nelements, void *nulval,
+	    void *array, int *anynul, int *status );
+
+int ffcalc_rng( fitsfile *infptr, char *expr, fitsfile *outfptr,
+               char *parName, char *parInfo, int nRngs,
+                 long *start, long *end, int *status );
+
+int ffcalc( fitsfile *infptr, char *expr, fitsfile *outfptr,
+            char *parName, char *parInfo, int *status );
+
+  /* ffhist is not really intended as a user-callable routine */
+  /* but it may be useful for some specialized applications   */
+  /* ffhist2 is a newer version which is strongly recommended instead of ffhist */
+
+int ffhist(fitsfile **fptr, char *outfile, int imagetype, int naxis,
+           char colname[4][FLEN_VALUE],
+           double *minin, double *maxin, double *binsizein,
+           char minname[4][FLEN_VALUE], char maxname[4][FLEN_VALUE],
+           char binname[4][FLEN_VALUE], 
+           double weightin, char wtcol[FLEN_VALUE],
+           int recip, char *rowselect, int *status);
+int ffhist2(fitsfile **fptr, char *outfile, int imagetype, int naxis,
+           char colname[4][FLEN_VALUE],
+           double *minin, double *maxin, double *binsizein,
+           char minname[4][FLEN_VALUE], char maxname[4][FLEN_VALUE],
+           char binname[4][FLEN_VALUE], 
+           double weightin, char wtcol[FLEN_VALUE],
+           int recip, char *rowselect, int *status);
+
+int fits_select_image_section(fitsfile **fptr, char *outfile,
+           char *imagesection, int *status);
+int fits_copy_image_section(fitsfile *infptr, fitsfile *outfile,
+           char *imagesection, int *status);
+
+int fits_calc_binning(fitsfile *fptr, int naxis, char colname[4][FLEN_VALUE], 
+    double *minin, double *maxin,  double *binsizein,
+    char minname[4][FLEN_VALUE],  char maxname[4][FLEN_VALUE], 
+    char binname[4][FLEN_VALUE],  int *colnum,  long *haxes,  float *amin, 
+    float *amax, float *binsize,  int *status);
+
+int fits_write_keys_histo(fitsfile *fptr,  fitsfile *histptr, 
+      int naxis, int *colnum, int *status);  
+int fits_rebin_wcs( fitsfile *fptr, int naxis, float *amin,  float *binsize, 
+      int *status);      
+int fits_make_hist(fitsfile *fptr, fitsfile *histptr, int bitpix,int naxis,
+     long *naxes,  int *colnum,  float *amin,  float *amax, float *binsize,
+     float weight, int wtcolnum, int recip, char *selectrow, int *status);
+
+typedef struct
+{
+	/* input(s) */
+	int count;
+	char ** path;
+	char ** tag;
+	fitsfile ** ifptr;
+
+	char * expression;
+
+	/* output control */
+	int bitpix;
+	long blank;
+	fitsfile * ofptr;
+	char keyword[FLEN_KEYWORD];
+	char comment[FLEN_COMMENT];
+} PixelFilter;
+
+
+int fits_pixel_filter (PixelFilter * filter, int * status);
+
+
+/*--------------------- grouping routines ------------------*/
+
+int ffgtcr(fitsfile *fptr, char *grpname, int grouptype, int *status);
+int ffgtis(fitsfile *fptr, char *grpname, int grouptype, int *status);
+int ffgtch(fitsfile *gfptr, int grouptype, int *status);
+int ffgtrm(fitsfile *gfptr, int rmopt, int *status);
+int ffgtcp(fitsfile *infptr, fitsfile *outfptr, int cpopt, int *status);
+int ffgtmg(fitsfile *infptr, fitsfile *outfptr, int mgopt, int *status);
+int ffgtcm(fitsfile *gfptr, int cmopt, int *status);
+int ffgtvf(fitsfile *gfptr, long *firstfailed, int *status);
+int ffgtop(fitsfile *mfptr,int group,fitsfile **gfptr,int *status);
+int ffgtam(fitsfile *gfptr, fitsfile *mfptr, int hdupos, int *status);
+int ffgtnm(fitsfile *gfptr, long *nmembers, int *status);
+int ffgmng(fitsfile *mfptr, long *nmembers, int *status);
+int ffgmop(fitsfile *gfptr, long member, fitsfile **mfptr, int *status);
+int ffgmcp(fitsfile *gfptr, fitsfile *mfptr, long member, int cpopt, 
+	   int *status);
+int ffgmtf(fitsfile *infptr, fitsfile *outfptr,	long member, int tfopt,	       
+	   int *status);
+int ffgmrm(fitsfile *fptr, long member, int rmopt, int *status);
+
+/*--------------------- group template parser routines ------------------*/
+
+int	fits_execute_template(fitsfile *ff, char *ngp_template, int *status);
+
+int fits_img_stats_short(short *array,long nx, long ny, int nullcheck,   
+    short nullvalue,long *ngoodpix, short *minvalue, short *maxvalue, double *mean,  
+    double *sigma, double *noise1, double *noise2, double *noise3, double *noise5, int *status);
+int fits_img_stats_int(int *array,long nx, long ny, int nullcheck,   
+    int nullvalue,long *ngoodpix, int *minvalue, int *maxvalue, double *mean,  
+    double *sigma, double *noise1, double *noise2, double *noise3, double *noise5, int *status);
+int fits_img_stats_float(float *array, long nx, long ny, int nullcheck,   
+    float nullvalue,long *ngoodpix, float *minvalue, float *maxvalue, double *mean,  
+    double *sigma, double *noise1, double *noise2, double *noise3, double *noise5, int *status);
+
+/*--------------------- image compression routines ------------------*/
+
+int fits_set_compression_type(fitsfile *fptr, int ctype, int *status);
+int fits_set_tile_dim(fitsfile *fptr, int ndim, long *dims, int *status);
+int fits_set_noise_bits(fitsfile *fptr, int noisebits, int *status);
+int fits_set_quantize_level(fitsfile *fptr, float qlevel, int *status);
+int fits_set_hcomp_scale(fitsfile *fptr, float scale, int *status);
+int fits_set_hcomp_smooth(fitsfile *fptr, int smooth, int *status);
+int fits_set_quantize_dither(fitsfile *fptr, int dither, int *status);
+int fits_set_dither_offset(fitsfile *fptr, int offset, int *status);
+int fits_set_lossy_int(fitsfile *fptr, int lossy_int, int *status);
+
+int fits_get_compression_type(fitsfile *fptr, int *ctype, int *status);
+int fits_get_tile_dim(fitsfile *fptr, int ndim, long *dims, int *status);
+int fits_get_quantize_level(fitsfile *fptr, float *qlevel, int *status);
+int fits_get_noise_bits(fitsfile *fptr, int *noisebits, int *status);
+int fits_get_hcomp_scale(fitsfile *fptr, float *scale, int *status);
+int fits_get_hcomp_smooth(fitsfile *fptr, int *smooth, int *status);
+int fits_get_dither_offset(fitsfile *fptr, int *offset, int *status);
+
+int fits_img_compress(fitsfile *infptr, fitsfile *outfptr, int *status);
+int fits_compress_img(fitsfile *infptr, fitsfile *outfptr, int compress_type,
+         long *tilesize, int parm1, int parm2, int *status);
+int fits_is_compressed_image(fitsfile *fptr, int *status);
+int fits_is_reentrant(void);
+int fits_decompress_img (fitsfile *infptr, fitsfile *outfptr, int *status);
+int fits_img_decompress_header(fitsfile *infptr, fitsfile *outfptr, int *status);
+int fits_img_decompress (fitsfile *infptr, fitsfile *outfptr, int *status);
+
+/* H-compress routines */
+int fits_hcompress(int *a, int nx, int ny, int scale, char *output, 
+    long *nbytes, int *status);
+int fits_hcompress64(LONGLONG *a, int nx, int ny, int scale, char *output, 
+    long *nbytes, int *status);
+int fits_hdecompress(unsigned char *input, int smooth, int *a, int *nx, 
+       int *ny, int *scale, int *status);
+int fits_hdecompress64(unsigned char *input, int smooth, LONGLONG *a, int *nx, 
+       int *ny, int *scale, int *status);
+
+int fits_transpose_table(fitsfile *infptr, fitsfile *outfptr, int *status);
+int fits_compress_table_fast(fitsfile *infptr, fitsfile *outfptr, int *status);
+int fits_compress_table_best(fitsfile *infptr, fitsfile *outfptr, int *status);
+int fits_compress_table_rice(fitsfile *infptr, fitsfile *outfptr, int *status);
+int fits_uncompress_table(fitsfile *infptr, fitsfile *outfptr, int *status);
+int fits_gzip_datablocks(fitsfile *fptr, size_t *size, int *status);
+
+/*  The following exclusion if __CINT__ is defined is needed for ROOT */
+#ifndef __CINT__
+#ifdef __cplusplus
+}
+#endif
+#endif
+
+#endif
+
diff --git a/src/plugins/cfitsio/fitsio2.h b/src/plugins/cfitsio/fitsio2.h
new file mode 100644
index 0000000..b6fd66c
--- /dev/null
+++ b/src/plugins/cfitsio/fitsio2.h
@@ -0,0 +1,1205 @@
+#ifndef _FITSIO2_H
+#define _FITSIO2_H
+ 
+#include "fitsio.h"
+
+/* 
+    Threading support using POSIX threads programming interface
+    (supplied by Bruce O'Neel) 
+
+    All threaded programs MUST have the 
+
+    -D_REENTRANT
+
+    on the compile line and must link with -lpthread.  This means that
+    when one builds cfitsio for threads you must have -D_REENTRANT on the
+    gcc or cc command line.
+*/
+
+#ifdef _REENTRANT
+#include <pthread.h>
+#include <assert.h>
+extern pthread_mutex_t Fitsio_Lock;
+extern int Fitsio_Pthread_Status;
+
+#define FFLOCK1(lockname)   (assert(!(Fitsio_Pthread_Status = pthread_mutex_lock(&lockname))))
+#define FFUNLOCK1(lockname) (assert(!(Fitsio_Pthread_Status = pthread_mutex_unlock(&lockname))))
+#define FFLOCK   FFLOCK1(Fitsio_Lock)
+#define FFUNLOCK FFUNLOCK1(Fitsio_Lock)
+
+#else
+#define FFLOCK
+#define FFUNLOCK
+#endif
+
+/*
+  If REPLACE_LINKS is defined, then whenever CFITSIO fails to open
+  a file with write access because it is a soft link to a file that
+  only has read access, then CFITSIO will attempt to replace
+  the link with a local copy of the file, with write access.  This
+  feature was originally added to support the ftools in the Hera
+  environment, where many of the user's data file are soft links.
+*/
+#if defined(BUILD_HERA)
+#define REPLACE_LINKS 1
+#endif
+
+#define USE_LARGE_VALUE -99  /* flag used when writing images */
+
+#define DBUFFSIZE 28800 /* size of data buffer in bytes */
+
+#define NMAXFILES  300   /* maximum number of FITS files that can be opened */
+        /* CFITSIO will allocate (NMAXFILES * 80) bytes of memory */
+
+#define MINDIRECT 8640   /* minimum size for direct reads and writes */
+                         /* MINDIRECT must have a value >= 8640 */
+
+/*   it is useful to identify certain specific types of machines   */
+#define NATIVE             0 /* machine that uses non-byteswapped IEEE formats */
+#define OTHERTYPE          1  /* any other type of machine */
+#define VAXVMS             3  /* uses an odd floating point format */
+#define ALPHAVMS           4  /* uses an odd floating point format */
+#define IBMPC              5  /* used in drvrfile.c to work around a bug on PCs */
+#define CRAY               6  /* requires a special NaN test algorithm */
+
+#define GFLOAT             1  /* used for VMS */
+#define IEEEFLOAT          2  /* used for VMS */
+
+/* ======================================================================= */
+/* The following logic is used to determine the type machine,              */
+/*  whether the bytes are swapped, and the number of bits in a long value  */
+/* ======================================================================= */
+
+/*   The following platforms have sizeof(long) == 8               */
+/*   This block of code should match a similar block in fitsio.h  */
+/*   and the block of code at the beginning of f77_wrap.h         */
+
+#if defined(__alpha) && ( defined(__unix__) || defined(__NetBSD__) )
+                                  /* old Dec Alpha platforms running OSF */
+#define BYTESWAPPED TRUE
+#define LONGSIZE 64
+
+#elif defined(__sparcv9) || (defined(__sparc__) && defined(__arch64__))
+                               /*  SUN Solaris7 in 64-bit mode */
+#define BYTESWAPPED FALSE
+#define MACHINE NATIVE
+#define LONGSIZE 64   
+
+                            /* IBM System z mainframe support */ 
+#elif defined(__s390x__)
+#define BYTESWAPPED FALSE
+#define LONGSIZE 64
+
+#elif defined(__s390__)
+#define BYTESWAPPED FALSE
+#define LONGSIZE 32
+
+#elif defined(__ia64__)  || defined(__x86_64__)
+                  /*  Intel itanium 64-bit PC, or AMD opteron 64-bit PC */
+#define BYTESWAPPED TRUE
+#define LONGSIZE 64   
+
+#elif defined(_SX)             /* Nec SuperUx */
+
+#define BYTESWAPPED FALSE
+#define MACHINE NATIVE
+#define LONGSIZE 64
+
+#elif defined(__powerpc64__) || defined(__64BIT__) /* IBM 64-bit AIX powerpc*/
+                              /* could also test for __ppc64__ or __PPC64 */
+#define BYTESWAPPED FALSE
+#define MACHINE NATIVE
+#define LONGSIZE 64   
+
+#elif defined(_MIPS_SZLONG)
+
+#  if defined(MIPSEL)
+#    define BYTESWAPPED TRUE
+#  else
+#    define BYTESWAPPED FALSE
+#    define MACHINE NATIVE
+#  endif
+
+#  if _MIPS_SZLONG == 32
+#    define LONGSIZE 32
+#  elif _MIPS_SZLONG == 64
+#    define LONGSIZE 64
+#  else
+#    error "can't handle long size given by _MIPS_SZLONG"
+#  endif
+
+/* ============================================================== */
+/*  the following are all 32-bit byteswapped platforms            */
+
+#elif defined(vax) && defined(VMS)
+ 
+#define MACHINE VAXVMS
+#define BYTESWAPPED TRUE
+ 
+#elif defined(__alpha) && defined(__VMS)
+
+#if (__D_FLOAT == TRUE)
+
+/* this float option is the same as for VAX/VMS machines. */
+#define MACHINE VAXVMS
+#define BYTESWAPPED TRUE
+ 
+#elif  (__G_FLOAT == TRUE)
+ 
+/*  G_FLOAT is the default for ALPHA VMS systems */
+#define MACHINE ALPHAVMS
+#define BYTESWAPPED TRUE
+#define FLOATTYPE GFLOAT
+ 
+#elif  (__IEEE_FLOAT == TRUE)
+ 
+#define MACHINE ALPHAVMS
+#define BYTESWAPPED TRUE
+#define FLOATTYPE IEEEFLOAT
+
+#endif  /* end of alpha VMS case */
+
+#elif defined(ultrix) && defined(unix)
+ /* old Dec ultrix machines */
+#define BYTESWAPPED TRUE
+ 
+#elif defined(__i386) || defined(__i386__) || defined(__i486__) || defined(__i586__) \
+  || defined(_MSC_VER) || defined(__BORLANDC__) || defined(__TURBOC__) \
+  || defined(_NI_mswin_) || defined(__EMX__)
+
+/*  generic 32-bit IBM PC */
+#define MACHINE IBMPC
+#define BYTESWAPPED TRUE
+
+#elif defined(__arm__)
+
+/* This assumes all ARM are little endian.  In the future, it might be  */
+/* necessary to use  "if defined(__ARMEL__)"  to distinguish little from big. */
+/* (__ARMEL__ would be defined on little-endian, but not on big-endian). */
+
+#define BYTESWAPPED TRUE
+ 
+#elif defined(__tile__)
+
+/*  64-core 8x8-architecture Tile64 platform */
+
+#define BYTESWAPPED TRUE
+
+#elif defined(__sh__)
+
+/* SuperH CPU can be used in both little and big endian modes */
+
+#if defined(__LITTLE_ENDIAN__)
+#define BYTESWAPPED TRUE
+#else
+#define BYTESWAPPED FALSE
+#endif
+ 
+#else
+
+/*  assume all other machine uses the same IEEE formats as used in FITS files */
+/*  e.g., Macs fall into this category  */
+
+#define MACHINE NATIVE
+#define BYTESWAPPED FALSE
+ 
+#endif
+
+#ifndef MACHINE
+#define MACHINE  OTHERTYPE
+#endif
+
+/*  assume longs are 4 bytes long, unless previously set otherwise */
+#ifndef LONGSIZE
+#define LONGSIZE 32
+#endif
+
+/*       end of block that determine long size and byte swapping        */ 
+/* ==================================================================== */
+ 
+#define IGNORE_EOF 1
+#define REPORT_EOF 0
+#define DATA_UNDEFINED -1
+#define NULL_UNDEFINED 1234554321
+#define ASCII_NULL_UNDEFINED 1   /* indicate no defined null value */
+ 
+#define maxvalue(A,B) ((A) > (B) ? (A) : (B))
+#define minvalue(A,B) ((A) < (B) ? (A) : (B))
+
+/* faster string comparison macros */
+#define FSTRCMP(a,b)     ((a)[0]<(b)[0]? -1:(a)[0]>(b)[0]?1:strcmp((a),(b)))
+#define FSTRNCMP(a,b,n)  ((a)[0]<(b)[0]?-1:(a)[0]>(b)[0]?1:strncmp((a),(b),(n)))
+
+#if defined(__VMS) || defined(VMS)
+ 
+#define FNANMASK   0xFFFF /* mask all bits  */
+#define DNANMASK   0xFFFF /* mask all bits  */
+ 
+#else
+ 
+#define FNANMASK   0x7F80 /* mask bits 1 - 8; all set on NaNs */
+                                     /* all 0 on underflow  or 0. */
+ 
+#define DNANMASK   0x7FF0 /* mask bits 1 - 11; all set on NaNs */
+                                     /* all 0 on underflow  or 0. */
+ 
+#endif
+ 
+#if MACHINE == CRAY
+    /*
+      Cray machines:   the large negative integer corresponds
+      to the 3 most sig digits set to 1.   If these
+      3 bits are set in a floating point number (64 bits), then it represents
+      a reserved value (i.e., a NaN)
+    */
+#define fnan(L) ( (L) >= 0xE000000000000000 ? 1 : 0) )
+ 
+#else
+    /* these functions work for both big and little endian machines */
+    /* that use the IEEE floating point format for internal numbers */
+ 
+   /* These functions tests whether the float value is a reserved IEEE     */
+   /* value such as a Not-a-Number (NaN), or underflow, overflow, or       */
+   /* infinity.   The functions returns 1 if the value is a NaN, overflow  */
+   /* or infinity; it returns 2 if the value is an denormalized underflow  */
+   /* value; otherwise it returns 0. fnan tests floats, dnan tests doubles */
+ 
+#define fnan(L) \
+      ( (L & FNANMASK) == FNANMASK ?  1 : (L & FNANMASK) == 0 ? 2 : 0)
+ 
+#define dnan(L) \
+      ( (L & DNANMASK) == DNANMASK ?  1 : (L & DNANMASK) == 0 ? 2 : 0)
+ 
+#endif
+
+#define DSCHAR_MAX  127.49 /* max double value that fits in an signed char */
+#define DSCHAR_MIN -128.49 /* min double value that fits in an signed char */
+#define DUCHAR_MAX  255.49 /* max double value that fits in an unsigned char */
+#define DUCHAR_MIN -0.49   /* min double value that fits in an unsigned char */
+#define DUSHRT_MAX  65535.49 /* max double value that fits in a unsigned short*/
+#define DUSHRT_MIN -0.49   /* min double value that fits in an unsigned short */
+#define DSHRT_MAX  32767.49 /* max double value that fits in a short */
+#define DSHRT_MIN -32768.49 /* min double value that fits in a short */
+
+#if LONGSIZE == 32
+#  define DLONG_MAX  2147483647.49 /* max double value that fits in a long */
+#  define DLONG_MIN -2147483648.49 /* min double value that fits in a long */
+#  define DULONG_MAX 4294967295.49 /* max double that fits in a unsigned long */
+#else
+#  define DLONG_MAX   9.2233720368547752E18 /* max double value  long */
+#  define DLONG_MIN  -9.2233720368547752E18 /* min double value  long */
+#  define DULONG_MAX 1.84467440737095504E19 /* max double value  ulong */
+#endif
+
+#define DULONG_MIN -0.49   /* min double value that fits in an unsigned long */
+#define DLONGLONG_MAX  9.2233720368547755807E18 /* max double value  longlong */
+#define DLONGLONG_MIN -9.2233720368547755808E18 /* min double value  longlong */
+#define DUINT_MAX 4294967295.49 /* max dbl that fits in a unsigned 4-byte int */
+#define DUINT_MIN -0.49   /* min dbl that fits in an unsigned 4-byte int */
+#define DINT_MAX  2147483647.49 /* max double value that fits in a 4-byte int */
+#define DINT_MIN -2147483648.49 /* min double value that fits in a 4-byte int */
+
+#ifndef UINT32_MAX
+#define UINT32_MAX 4294967295U /* max unsigned 32-bit integer */
+#endif
+#ifndef INT32_MAX
+#define INT32_MAX  2147483647 /* max 32-bit integer */
+#endif
+#ifndef INT32_MIN
+#define INT32_MIN (-INT32_MAX -1) /* min 32-bit integer */
+#endif
+
+
+#define COMPRESS_NULL_VALUE -2147483647
+#define N_RANDOM 10000  /* DO NOT CHANGE THIS;  used when quantizing real numbers */
+
+int ffmkky(const char *keyname, char *keyval, const char *comm, char *card, int *status);
+int ffgnky(fitsfile *fptr, char *card, int *status);
+void ffcfmt(char *tform, char *cform);
+void ffcdsp(char *tform, char *cform);
+void ffswap2(short *values, long nvalues);
+void ffswap4(INT32BIT *values, long nvalues);
+void ffswap8(double *values, long nvalues);
+int ffi2c(LONGLONG ival, char *cval, int *status);
+int ffl2c(int lval, char *cval, int *status);
+int ffs2c(char *instr, char *outstr, int *status);
+int ffr2f(float fval, int decim, char *cval, int *status);
+int ffr2e(float fval, int decim, char *cval, int *status);
+int ffd2f(double dval, int decim, char *cval, int *status);
+int ffd2e(double dval, int decim, char *cval, int *status);
+int ffc2ii(char *cval, long *ival, int *status);
+int ffc2jj(char *cval, LONGLONG *ival, int *status);
+int ffc2ll(char *cval, int *lval, int *status);
+int ffc2rr(char *cval, float *fval, int *status);
+int ffc2dd(char *cval, double *dval, int *status);
+int ffc2x(char *cval, char *dtype, long *ival, int *lval, char *sval,
+          double *dval, int *status);
+int ffc2s(char *instr, char *outstr, int *status);
+int ffc2i(char *cval, long *ival, int *status);
+int ffc2j(char *cval, LONGLONG *ival, int *status);
+int ffc2r(char *cval, float *fval, int *status);
+int ffc2d(char *cval, double *dval, int *status);
+int ffc2l(char *cval, int *lval, int *status);
+void ffxmsg(int action, char *err_message);
+int ffgcnt(fitsfile *fptr, char *value, int *status);
+int ffgtkn(fitsfile *fptr, int numkey, char *keyname, long *value, int *status);
+int ffgtknjj(fitsfile *fptr, int numkey, char *keyname, LONGLONG *value, int *status);
+int fftkyn(fitsfile *fptr, int numkey, char *keyname, char *value, int *status);
+int ffgphd(fitsfile *fptr, int maxdim, int *simple, int *bitpix, int *naxis,
+        LONGLONG naxes[], long *pcount, long *gcount, int *extend, double *bscale,
+          double *bzero, LONGLONG *blank, int *nspace, int *status);
+int ffgttb(fitsfile *fptr, LONGLONG *rowlen, LONGLONG *nrows, LONGLONG *pcount,
+          long *tfield, int *status);
+ 
+int ffmkey(fitsfile *fptr, char *card, int *status);
+ 
+/*  ffmbyt has been moved to fitsio.h */
+int ffgbyt(fitsfile *fptr, LONGLONG nbytes, void *buffer, int *status);
+int ffpbyt(fitsfile *fptr, LONGLONG nbytes, void *buffer, int *status);
+int ffgbytoff(fitsfile *fptr, long gsize, long ngroups, long offset, 
+           void *buffer, int *status);
+int ffpbytoff(fitsfile *fptr, long gsize, long ngroups, long offset,
+           void *buffer, int *status);
+int ffldrc(fitsfile *fptr, long record, int err_mode, int *status);
+int ffwhbf(fitsfile *fptr, int *nbuff);
+int ffbfeof(fitsfile *fptr, int *status);
+int ffbfwt(FITSfile *Fptr, int nbuff, int *status);
+int ffpxsz(int datatype);
+
+int ffourl(char *url, char *urltype, char *outfile, char *tmplfile,
+            char *compspec, int *status);
+int ffparsecompspec(fitsfile *fptr, char *compspec, int *status);
+int ffoptplt(fitsfile *fptr, const char *tempname, int *status);
+int fits_is_this_a_copy(char *urltype);
+int fits_store_Fptr(FITSfile *Fptr, int *status);
+int fits_clear_Fptr(FITSfile *Fptr, int *status);
+int fits_already_open(fitsfile **fptr, char *url, 
+    char *urltype, char *infile, char *extspec, char *rowfilter,
+    char *binspec, char *colspec, int  mode,int  *isopen, int  *status);
+int ffedit_columns(fitsfile **fptr, char *outfile, char *expr, int *status);
+int fits_get_col_minmax(fitsfile *fptr, int colnum, float *datamin, 
+                     float *datamax, int *status);
+int ffwritehisto(long totaln, long offset, long firstn, long nvalues,
+             int narrays, iteratorCol *imagepars, void *userPointer);
+int ffcalchist(long totalrows, long offset, long firstrow, long nrows,
+             int ncols, iteratorCol *colpars, void *userPointer);
+int ffrhdu(fitsfile *fptr, int *hdutype, int *status);
+int ffpinit(fitsfile *fptr, int *status);
+int ffainit(fitsfile *fptr, int *status);
+int ffbinit(fitsfile *fptr, int *status);
+int ffchdu(fitsfile *fptr, int *status);
+int ffwend(fitsfile *fptr, int *status);
+int ffpdfl(fitsfile *fptr, int *status);
+int ffuptf(fitsfile *fptr, int *status);
+
+int ffdblk(fitsfile *fptr, long nblocks, int *status);
+int ffgext(fitsfile *fptr, int moveto, int *exttype, int *status);
+int ffgtbc(fitsfile *fptr, LONGLONG *totalwidth, int *status);
+int ffgtbp(fitsfile *fptr, char *name, char *value, int *status);
+int ffiblk(fitsfile *fptr, long nblock, int headdata, int *status);
+int ffshft(fitsfile *fptr, LONGLONG firstbyte, LONGLONG nbytes, LONGLONG nshift,
+    int *status);
+ 
+ int ffgcprll(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, int writemode, double *scale, double *zero, char *tform,
+           long *twidth, int *tcode, int *maxelem, LONGLONG *startpos,
+           LONGLONG *elemnum, long *incre, LONGLONG *repeat, LONGLONG *rowlen,
+           int *hdutype, LONGLONG *tnull, char *snull, int *status);
+	   
+int ffflushx(FITSfile *fptr);
+int ffseek(FITSfile *fptr, LONGLONG position);
+int ffread(FITSfile *fptr, long nbytes, void *buffer,
+            int *status);
+int ffwrite(FITSfile *fptr, long nbytes, void *buffer,
+            int *status);
+int fftrun(fitsfile *fptr, LONGLONG filesize, int *status);
+
+int ffpcluc(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, int *status);
+	   
+int ffgcll(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, int nultyp, char nulval, char *array, char *nularray,
+           int *anynul, int *status);
+int ffgcls(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, int nultyp, char *nulval,
+           char **array, char *nularray, int *anynul, int  *status);
+int ffgcls2(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, int nultyp, char *nulval,
+           char **array, char *nularray, int *anynul, int  *status);
+int ffgclb(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, long  elemincre, int nultyp, unsigned char nulval,
+           unsigned char *array, char *nularray, int *anynul, int  *status);
+int ffgclsb(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, long  elemincre, int nultyp, signed char nulval,
+           signed char *array, char *nularray, int *anynul, int  *status);
+int ffgclui(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, long  elemincre, int nultyp, unsigned short nulval,
+           unsigned short *array, char *nularray, int *anynul, int  *status);
+int ffgcli(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, long  elemincre, int nultyp, short nulval,
+           short *array, char *nularray, int *anynul, int  *status);
+int ffgcluj(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, long elemincre, int nultyp, unsigned long nulval,
+           unsigned long *array, char *nularray, int *anynul, int  *status);
+int ffgcljj(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, long elemincre, int nultyp, LONGLONG nulval, 
+           LONGLONG *array, char *nularray, int *anynul, int  *status);
+int ffgclj(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, long elemincre, int nultyp, long nulval, long *array,
+           char *nularray, int *anynul, int  *status);
+int ffgcluk(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, long elemincre, int nultyp, unsigned int nulval,
+           unsigned int *array, char *nularray, int *anynul, int  *status);
+int ffgclk(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, long elemincre, int nultyp, int nulval, int *array,
+           char *nularray, int *anynul, int  *status);
+int ffgcle(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, long elemincre, int nultyp,  float nulval, float *array,
+           char *nularray, int *anynul, int  *status);
+int ffgcld(fitsfile *fptr, int colnum, LONGLONG firstrow, LONGLONG firstelem,
+           LONGLONG nelem, long elemincre, int nultyp, double nulval,
+           double *array, char *nularray, int *anynul, int  *status);
+ 
+int ffpi1b(fitsfile *fptr, long nelem, long incre, unsigned char *buffer,
+           int *status);
+int ffpi2b(fitsfile *fptr, long nelem, long incre, short *buffer, int *status);
+int ffpi4b(fitsfile *fptr, long nelem, long incre, INT32BIT *buffer,
+           int *status);
+int ffpi8b(fitsfile *fptr, long nelem, long incre, long *buffer, int *status);
+int ffpr4b(fitsfile *fptr, long nelem, long incre, float *buffer, int *status);
+int ffpr8b(fitsfile *fptr, long nelem, long incre, double *buffer, int *status);
+ 
+int ffgi1b(fitsfile *fptr, LONGLONG pos, long nelem, long incre,
+          unsigned char *buffer, int *status);
+int ffgi2b(fitsfile *fptr, LONGLONG pos, long nelem, long incre, short *buffer,
+          int *status);
+int ffgi4b(fitsfile *fptr, LONGLONG pos, long nelem, long incre, INT32BIT *buffer,
+          int *status);
+int ffgi8b(fitsfile *fptr, LONGLONG pos, long nelem, long incre, long *buffer,
+          int *status);
+int ffgr4b(fitsfile *fptr, LONGLONG pos, long nelem, long incre, float *buffer,
+          int *status);
+int ffgr8b(fitsfile *fptr, LONGLONG pos, long nelem, long incre, double *buffer,
+          int *status);
+ 
+int ffcins(fitsfile *fptr, LONGLONG naxis1, LONGLONG naxis2, LONGLONG nbytes,
+           LONGLONG bytepos, int *status);
+int ffcdel(fitsfile *fptr, LONGLONG naxis1, LONGLONG naxis2, LONGLONG nbytes,
+           LONGLONG bytepos, int *status);
+int ffkshf(fitsfile *fptr, int firstcol, int tfields, int nshift, int *status);
+ 
+int fffi1i1(unsigned char *input, long ntodo, double scale, double zero,
+            int nullcheck, unsigned char tnull, unsigned char nullval, char
+             *nullarray, int *anynull, unsigned char *output, int *status);
+int fffi2i1(short *input, long ntodo, double scale, double zero,
+            int nullcheck, short tnull, unsigned char nullval, char *nullarray,
+            int *anynull, unsigned char *output, int *status);
+int fffi4i1(INT32BIT *input, long ntodo, double scale, double zero,
+            int nullcheck, INT32BIT tnull, unsigned char nullval, char *nullarray,
+            int *anynull, unsigned char *output, int *status);
+int fffi8i1(LONGLONG *input, long ntodo, double scale, double zero,
+            int nullcheck, LONGLONG tnull, unsigned char nullval, char *nullarray,
+            int *anynull, unsigned char *output, int *status);
+int fffr4i1(float *input, long ntodo, double scale, double zero,
+            int nullcheck, unsigned char nullval, char *nullarray,
+            int *anynull, unsigned char *output, int *status);
+int fffr8i1(double *input, long ntodo, double scale, double zero,
+            int nullcheck, unsigned char nullval, char *nullarray,
+            int *anynull, unsigned char *output, int *status);
+int fffstri1(char *input, long ntodo, double scale, double zero,
+            long twidth, double power, int nullcheck, char *snull,
+            unsigned char nullval, char *nullarray, int *anynull,
+            unsigned char *output, int *status);
+ 
+int fffi1s1(unsigned char *input, long ntodo, double scale, double zero,
+            int nullcheck, unsigned char tnull, signed char nullval, char
+             *nullarray, int *anynull, signed char *output, int *status);
+int fffi2s1(short *input, long ntodo, double scale, double zero,
+            int nullcheck, short tnull, signed char nullval, char *nullarray,
+            int *anynull, signed char *output, int *status);
+int fffi4s1(INT32BIT *input, long ntodo, double scale, double zero,
+            int nullcheck, INT32BIT tnull, signed char nullval, char *nullarray,
+            int *anynull, signed char *output, int *status);
+int fffi8s1(LONGLONG *input, long ntodo, double scale, double zero,
+            int nullcheck, LONGLONG tnull, signed char nullval, char *nullarray,
+            int *anynull, signed char *output, int *status);
+int fffr4s1(float *input, long ntodo, double scale, double zero,
+            int nullcheck, signed char nullval, char *nullarray,
+            int *anynull, signed char *output, int *status);
+int fffr8s1(double *input, long ntodo, double scale, double zero,
+            int nullcheck, signed char nullval, char *nullarray,
+            int *anynull, signed char *output, int *status);
+int fffstrs1(char *input, long ntodo, double scale, double zero,
+            long twidth, double power, int nullcheck, char *snull,
+            signed char nullval, char *nullarray, int *anynull,
+            signed char *output, int *status);
+
+int fffi1u2(unsigned char *input, long ntodo, double scale, double zero,
+            int nullcheck, unsigned char tnull, unsigned short nullval, 
+            char *nullarray,
+            int *anynull, unsigned short *output, int *status);
+int fffi2u2(short *input, long ntodo, double scale, double zero,
+            int nullcheck, short tnull, unsigned short nullval, char *nullarray,
+            int *anynull, unsigned short *output, int *status);
+int fffi4u2(INT32BIT *input, long ntodo, double scale, double zero,
+            int nullcheck, INT32BIT tnull, unsigned short nullval, char *nullarray,
+            int *anynull, unsigned short *output, int *status);
+int fffi8u2(LONGLONG *input, long ntodo, double scale, double zero,
+            int nullcheck, LONGLONG tnull, unsigned short nullval, char *nullarray,
+            int *anynull, unsigned short *output, int *status);
+int fffr4u2(float *input, long ntodo, double scale, double zero,
+            int nullcheck, unsigned short nullval, char *nullarray,
+            int *anynull, unsigned short *output, int *status);
+int fffr8u2(double *input, long ntodo, double scale, double zero,
+            int nullcheck, unsigned short nullval, char *nullarray,
+            int *anynull, unsigned short *output, int *status);
+int fffstru2(char *input, long ntodo, double scale, double zero,
+            long twidth, double power, int nullcheck, char *snull,
+            unsigned short nullval, char *nullarray, int  *anynull, 
+            unsigned short *output, int *status);
+
+int fffi1i2(unsigned char *input, long ntodo, double scale, double zero,
+            int nullcheck, unsigned char tnull, short nullval, char *nullarray,
+            int *anynull, short *output, int *status);
+int fffi2i2(short *input, long ntodo, double scale, double zero,
+            int nullcheck, short tnull, short nullval, char *nullarray,
+            int *anynull, short *output, int *status);
+int fffi4i2(INT32BIT *input, long ntodo, double scale, double zero,
+            int nullcheck, INT32BIT tnull, short nullval, char *nullarray,
+            int *anynull, short *output, int *status);
+int fffi8i2(LONGLONG *input, long ntodo, double scale, double zero,
+            int nullcheck, LONGLONG tnull, short nullval, char *nullarray,
+            int *anynull, short *output, int *status);
+int fffr4i2(float *input, long ntodo, double scale, double zero,
+            int nullcheck, short nullval, char *nullarray,
+            int *anynull, short *output, int *status);
+int fffr8i2(double *input, long ntodo, double scale, double zero,
+            int nullcheck, short nullval, char *nullarray,
+            int *anynull, short *output, int *status);
+int fffstri2(char *input, long ntodo, double scale, double zero,
+            long twidth, double power, int nullcheck, char *snull,
+            short nullval, char *nullarray, int  *anynull, short *output,
+            int *status);
+
+int fffi1u4(unsigned char *input, long ntodo, double scale, double zero,
+            int nullcheck, unsigned char tnull, unsigned long nullval,
+            char *nullarray,
+            int *anynull, unsigned long *output, int *status);
+int fffi2u4(short *input, long ntodo, double scale, double zero,
+            int nullcheck, short tnull, unsigned long nullval, char *nullarray,
+            int *anynull, unsigned long *output, int *status);
+int fffi4u4(INT32BIT *input, long ntodo, double scale, double zero,
+            int nullcheck, INT32BIT tnull, unsigned long nullval, char *nullarray,
+            int *anynull, unsigned long *output, int *status);
+int fffi8u4(LONGLONG *input, long ntodo, double scale, double zero,
+            int nullcheck, LONGLONG tnull, unsigned long nullval, char *nullarray,
+            int *anynull, unsigned long *output, int *status);
+int fffr4u4(float *input, long ntodo, double scale, double zero,
+            int nullcheck, unsigned long nullval, char *nullarray,
+            int *anynull, unsigned long *output, int *status);
+int fffr8u4(double *input, long ntodo, double scale, double zero,
+            int nullcheck, unsigned long nullval, char *nullarray,
+            int *anynull, unsigned long *output, int *status);
+int fffstru4(char *input, long ntodo, double scale, double zero,
+            long twidth, double power, int nullcheck, char *snull,
+            unsigned long nullval, char *nullarray, int *anynull,
+            unsigned long *output, int *status);
+ 
+int fffi1i4(unsigned char *input, long ntodo, double scale, double zero,
+            int nullcheck, unsigned char tnull, long nullval, char *nullarray,
+            int *anynull, long *output, int *status);
+int fffi2i4(short *input, long ntodo, double scale, double zero,
+            int nullcheck, short tnull, long nullval, char *nullarray,
+            int *anynull, long *output, int *status);
+int fffi4i4(INT32BIT *input, long ntodo, double scale, double zero,
+            int nullcheck, INT32BIT tnull, long nullval, char *nullarray,
+            int *anynull, long *output, int *status);
+int fffi8i4(LONGLONG *input, long ntodo, double scale, double zero,
+            int nullcheck, LONGLONG tnull, long nullval, char *nullarray,
+            int *anynull, long *output, int *status);
+int fffr4i4(float *input, long ntodo, double scale, double zero,
+            int nullcheck, long nullval, char *nullarray,
+            int *anynull, long *output, int *status);
+int fffr8i4(double *input, long ntodo, double scale, double zero,
+            int nullcheck, long nullval, char *nullarray,
+            int *anynull, long *output, int *status);
+int fffstri4(char *input, long ntodo, double scale, double zero,
+            long twidth, double power, int nullcheck, char *snull,
+            long nullval, char *nullarray, int *anynull, long *output,
+            int *status);
+ 
+int fffi1int(unsigned char *input, long ntodo, double scale, double zero,
+            int nullcheck, unsigned char tnull, int nullval, char *nullarray,
+            int *anynull, int *output, int *status);
+int fffi2int(short *input, long ntodo, double scale, double zero,
+            int nullcheck, short tnull, int nullval, char *nullarray,
+            int *anynull, int *output, int *status);
+int fffi4int(INT32BIT *input, long ntodo, double scale, double zero,
+            int nullcheck, INT32BIT tnull, int nullval, char *nullarray,
+            int *anynull, int *output, int *status);
+int fffi8int(LONGLONG *input, long ntodo, double scale, double zero,
+            int nullcheck, LONGLONG tnull, int nullval, char *nullarray,
+            int *anynull, int *output, int *status);
+int fffr4int(float *input, long ntodo, double scale, double zero,
+            int nullcheck, int nullval, char *nullarray,
+            int *anynull, int *output, int *status);
+int fffr8int(double *input, long ntodo, double scale, double zero,
+            int nullcheck, int nullval, char *nullarray,
+            int *anynull, int *output, int *status);
+int fffstrint(char *input, long ntodo, double scale, double zero,
+            long twidth, double power, int nullcheck, char *snull,
+            int nullval, char *nullarray, int *anynull, int *output,
+            int *status);
+ 
+int fffi1uint(unsigned char *input, long ntodo, double scale, double zero,
+            int nullcheck, unsigned char tnull, unsigned int nullval,
+            char *nullarray, int *anynull, unsigned int *output, int *status);
+int fffi2uint(short *input, long ntodo, double scale, double zero,
+            int nullcheck, short tnull, unsigned int nullval, char *nullarray,
+            int *anynull, unsigned int *output, int *status);
+int fffi4uint(INT32BIT *input, long ntodo, double scale, double zero,
+            int nullcheck, INT32BIT tnull, unsigned int nullval, char *nullarray,
+            int *anynull, unsigned int *output, int *status);
+int fffi8uint(LONGLONG *input, long ntodo, double scale, double zero,
+            int nullcheck, LONGLONG tnull, unsigned int nullval, char *nullarray,
+            int *anynull, unsigned int *output, int *status);
+int fffr4uint(float *input, long ntodo, double scale, double zero,
+            int nullcheck, unsigned int nullval, char *nullarray,
+            int *anynull, unsigned int *output, int *status);
+int fffr8uint(double *input, long ntodo, double scale, double zero,
+            int nullcheck, unsigned int nullval, char *nullarray,
+            int *anynull, unsigned int *output, int *status);
+int fffstruint(char *input, long ntodo, double scale, double zero,
+            long twidth, double power, int nullcheck, char *snull,
+            unsigned int nullval, char *nullarray, int *anynull,
+            unsigned int *output, int *status);
+ 
+int fffi1i8(unsigned char *input, long ntodo, double scale, double zero,
+            int nullcheck, unsigned char tnull, LONGLONG nullval, 
+            char *nullarray, int *anynull, LONGLONG *output, int *status);
+int fffi2i8(short *input, long ntodo, double scale, double zero,
+            int nullcheck, short tnull, LONGLONG nullval, char *nullarray,
+            int *anynull, LONGLONG *output, int *status);
+int fffi4i8(INT32BIT *input, long ntodo, double scale, double zero,
+            int nullcheck, INT32BIT tnull, LONGLONG nullval, char *nullarray,
+            int *anynull, LONGLONG *output, int *status);
+int fffi8i8(LONGLONG *input, long ntodo, double scale, double zero,
+            int nullcheck, LONGLONG tnull, LONGLONG nullval, char *nullarray,
+            int *anynull, LONGLONG *output, int *status);
+int fffr4i8(float *input, long ntodo, double scale, double zero,
+            int nullcheck, LONGLONG nullval, char *nullarray,
+            int *anynull, LONGLONG *output, int *status);
+int fffr8i8(double *input, long ntodo, double scale, double zero,
+            int nullcheck, LONGLONG nullval, char *nullarray,
+            int *anynull, LONGLONG *output, int *status);
+int fffstri8(char *input, long ntodo, double scale, double zero,
+            long twidth, double power, int nullcheck, char *snull,
+            LONGLONG nullval, char *nullarray, int *anynull, LONGLONG *output,
+            int *status);
+
+int fffi1r4(unsigned char *input, long ntodo, double scale, double zero,
+            int nullcheck, unsigned char tnull, float nullval, char *nullarray,
+            int *anynull, float *output, int *status);
+int fffi2r4(short *input, long ntodo, double scale, double zero,
+            int nullcheck, short tnull, float nullval, char *nullarray,
+            int *anynull, float *output, int *status);
+int fffi4r4(INT32BIT *input, long ntodo, double scale, double zero,
+            int nullcheck, INT32BIT tnull, float nullval, char *nullarray,
+            int *anynull, float *output, int *status);
+int fffi8r4(LONGLONG *input, long ntodo, double scale, double zero,
+            int nullcheck, LONGLONG tnull, float nullval, char *nullarray,
+            int *anynull, float *output, int *status);
+int fffr4r4(float *input, long ntodo, double scale, double zero,
+            int nullcheck, float nullval, char *nullarray,
+            int *anynull, float *output, int *status);
+int fffr8r4(double *input, long ntodo, double scale, double zero,
+            int nullcheck, float nullval, char *nullarray,
+            int *anynull, float *output, int *status);
+int fffstrr4(char *input, long ntodo, double scale, double zero,
+            long twidth, double power, int nullcheck, char *snull,
+            float nullval, char *nullarray, int *anynull, float *output,
+            int *status);
+ 
+int fffi1r8(unsigned char *input, long ntodo, double scale, double zero,
+            int nullcheck, unsigned char tnull, double nullval, char *nullarray,
+            int *anynull, double *output, int *status);
+int fffi2r8(short *input, long ntodo, double scale, double zero,
+            int nullcheck, short tnull, double nullval, char *nullarray,
+            int *anynull, double *output, int *status);
+int fffi4r8(INT32BIT *input, long ntodo, double scale, double zero,
+            int nullcheck, INT32BIT tnull, double nullval, char *nullarray,
+            int *anynull, double *output, int *status);
+int fffi8r8(LONGLONG *input, long ntodo, double scale, double zero,
+            int nullcheck, LONGLONG tnull, double nullval, char *nullarray,
+            int *anynull, double *output, int *status);
+int fffr4r8(float *input, long ntodo, double scale, double zero,
+            int nullcheck, double nullval, char *nullarray,
+            int *anynull, double *output, int *status);
+int fffr8r8(double *input, long ntodo, double scale, double zero,
+            int nullcheck, double nullval, char *nullarray,
+            int *anynull, double *output, int *status);
+int fffstrr8(char *input, long ntodo, double scale, double zero,
+            long twidth, double power, int nullcheck, char *snull,
+            double nullval, char *nullarray, int *anynull, double *output,
+            int *status);
+ 
+int ffi1fi1(unsigned char *array, long ntodo, double scale, double zero,
+            unsigned char *buffer, int *status);
+int ffs1fi1(signed char *array, long ntodo, double scale, double zero,
+            unsigned char *buffer, int *status);
+int ffu2fi1(unsigned short *array, long ntodo, double scale, double zero,
+            unsigned char *buffer, int *status);
+int ffi2fi1(short *array, long ntodo, double scale, double zero,
+            unsigned char *buffer, int *status);
+int ffu4fi1(unsigned long *array, long ntodo, double scale, double zero,
+            unsigned char *buffer, int *status);
+int ffi4fi1(long *array, long ntodo, double scale, double zero,
+            unsigned char *buffer, int *status);
+int ffi8fi1(LONGLONG *array, long ntodo, double scale, double zero,
+            unsigned char *buffer, int *status);
+int ffuintfi1(unsigned int *array, long ntodo, double scale, double zero,
+            unsigned char *buffer, int *status);
+int ffintfi1(int *array, long ntodo, double scale, double zero,
+            unsigned char *buffer, int *status);
+int ffr4fi1(float *array, long ntodo, double scale, double zero,
+            unsigned char *buffer, int *status);
+int ffr8fi1(double *array, long ntodo, double scale, double zero,
+            unsigned char *buffer, int *status);
+ 
+int ffi1fi2(unsigned char *array, long ntodo, double scale, double zero,
+            short *buffer, int *status);
+int ffs1fi2(signed char *array, long ntodo, double scale, double zero,
+            short *buffer, int *status);
+int ffu2fi2(unsigned short *array, long ntodo, double scale, double zero,
+            short *buffer, int *status);
+int ffi2fi2(short *array, long ntodo, double scale, double zero,
+            short *buffer, int *status);
+int ffu4fi2(unsigned long *array, long ntodo, double scale, double zero,
+            short *buffer, int *status);
+int ffi4fi2(long *array, long ntodo, double scale, double zero,
+            short *buffer, int *status);
+int ffi8fi2(LONGLONG *array, long ntodo, double scale, double zero,
+            short *buffer, int *status);
+int ffuintfi2(unsigned int *array, long ntodo, double scale, double zero,
+            short *buffer, int *status);
+int ffintfi2(int *array, long ntodo, double scale, double zero,
+            short *buffer, int *status);
+int ffr4fi2(float *array, long ntodo, double scale, double zero,
+            short *buffer, int *status);
+int ffr8fi2(double *array, long ntodo, double scale, double zero,
+            short *buffer, int *status);
+ 
+int ffi1fi4(unsigned char *array, long ntodo, double scale, double zero,
+            INT32BIT *buffer, int *status);
+int ffs1fi4(signed char *array, long ntodo, double scale, double zero,
+            INT32BIT *buffer, int *status);
+int ffu2fi4(unsigned short *array, long ntodo, double scale, double zero,
+            INT32BIT *buffer, int *status);
+int ffi2fi4(short *array, long ntodo, double scale, double zero,
+            INT32BIT *buffer, int *status);
+int ffu4fi4(unsigned long *array, long ntodo, double scale, double zero,
+            INT32BIT *buffer, int *status);
+int ffi4fi4(long *array, long ntodo, double scale, double zero,
+            INT32BIT *buffer, int *status);
+int ffi8fi4(LONGLONG *array, long ntodo, double scale, double zero,
+            INT32BIT *buffer, int *status);
+int ffuintfi4(unsigned int *array, long ntodo, double scale, double zero,
+            INT32BIT *buffer, int *status);
+int ffintfi4(int *array, long ntodo, double scale, double zero,
+            INT32BIT *buffer, int *status);
+int ffr4fi4(float *array, long ntodo, double scale, double zero,
+            INT32BIT *buffer, int *status);
+int ffr8fi4(double *array, long ntodo, double scale, double zero,
+            INT32BIT *buffer, int *status);
+
+int fflongfi8(long *array, long ntodo, double scale, double zero,
+            LONGLONG *buffer, int *status);
+int ffi8fi8(LONGLONG *array, long ntodo, double scale, double zero,
+            LONGLONG *buffer, int *status);
+int ffi2fi8(short *array, long ntodo, double scale, double zero,
+            LONGLONG *buffer, int *status);
+int ffi1fi8(unsigned char *array, long ntodo, double scale, double zero,
+            LONGLONG *buffer, int *status);
+int ffs1fi8(signed char *array, long ntodo, double scale, double zero,
+            LONGLONG *buffer, int *status);
+int ffr4fi8(float *array, long ntodo, double scale, double zero,
+            LONGLONG *buffer, int *status);
+int ffr8fi8(double *array, long ntodo, double scale, double zero,
+            LONGLONG *buffer, int *status);
+int ffintfi8(int *array, long ntodo, double scale, double zero,
+            LONGLONG *buffer, int *status);
+int ffu2fi8(unsigned short *array, long ntodo, double scale, double zero,
+            LONGLONG *buffer, int *status);
+int ffu4fi8(unsigned long *array, long ntodo, double scale, double zero,
+            LONGLONG *buffer, int *status);
+int ffuintfi8(unsigned int *array, long ntodo, double scale, double zero,
+            LONGLONG *buffer, int *status);
+
+int ffi1fr4(unsigned char *array, long ntodo, double scale, double zero,
+            float *buffer, int *status);
+int ffs1fr4(signed char *array, long ntodo, double scale, double zero,
+            float *buffer, int *status);
+int ffu2fr4(unsigned short *array, long ntodo, double scale, double zero,
+            float *buffer, int *status);
+int ffi2fr4(short *array, long ntodo, double scale, double zero,
+            float *buffer, int *status);
+int ffu4fr4(unsigned long *array, long ntodo, double scale, double zero,
+            float *buffer, int *status);
+int ffi4fr4(long *array, long ntodo, double scale, double zero,
+            float *buffer, int *status);
+int ffi8fr4(LONGLONG *array, long ntodo, double scale, double zero,
+            float *buffer, int *status);
+int ffuintfr4(unsigned int *array, long ntodo, double scale, double zero,
+            float *buffer, int *status);
+int ffintfr4(int *array, long ntodo, double scale, double zero,
+            float *buffer, int *status);
+int ffr4fr4(float *array, long ntodo, double scale, double zero,
+            float *buffer, int *status);
+int ffr8fr4(double *array, long ntodo, double scale, double zero,
+            float *buffer, int *status);
+ 
+int ffi1fr8(unsigned char *array, long ntodo, double scale, double zero,
+            double *buffer, int *status);
+int ffs1fr8(signed char *array, long ntodo, double scale, double zero,
+            double *buffer, int *status);
+int ffu2fr8(unsigned short *array, long ntodo, double scale, double zero,
+            double *buffer, int *status);
+int ffi2fr8(short *array, long ntodo, double scale, double zero,
+            double *buffer, int *status);
+int ffu4fr8(unsigned long *array, long ntodo, double scale, double zero,
+            double *buffer, int *status);
+int ffi4fr8(long *array, long ntodo, double scale, double zero,
+            double *buffer, int *status);
+int ffi8fr8(LONGLONG *array, long ntodo, double scale, double zero,
+            double *buffer, int *status);
+int ffuintfr8(unsigned int *array, long ntodo, double scale, double zero,
+            double *buffer, int *status);
+int ffintfr8(int *array, long ntodo, double scale, double zero,
+            double *buffer, int *status);
+int ffr4fr8(float *array, long ntodo, double scale, double zero,
+            double *buffer, int *status);
+int ffr8fr8(double *array, long ntodo, double scale, double zero,
+            double *buffer, int *status);
+
+int ffi1fstr(unsigned char *input, long ntodo, double scale, double zero,
+            char *cform, long twidth, char *output, int *status);
+int ffs1fstr(signed char *input, long ntodo, double scale, double zero,
+            char *cform, long twidth, char *output, int *status);
+int ffu2fstr(unsigned short *input, long ntodo, double scale, double zero,
+            char *cform, long twidth, char *output, int *status);
+int ffi2fstr(short *input, long ntodo, double scale, double zero,
+            char *cform, long twidth, char *output, int *status);
+int ffu4fstr(unsigned long *input, long ntodo, double scale, double zero,
+            char *cform, long twidth, char *output, int *status);
+int ffi4fstr(long *input, long ntodo, double scale, double zero,
+            char *cform, long twidth, char *output, int *status);
+int ffi8fstr(LONGLONG *input, long ntodo, double scale, double zero,
+            char *cform, long twidth, char *output, int *status);
+int ffintfstr(int *input, long ntodo, double scale, double zero,
+            char *cform, long twidth, char *output, int *status);
+int ffuintfstr(unsigned int *input, long ntodo, double scale, double zero,
+            char *cform, long twidth, char *output, int *status);
+int ffr4fstr(float *input, long ntodo, double scale, double zero,
+            char *cform, long twidth, char *output, int *status);
+int ffr8fstr(double *input, long ntodo, double scale, double zero,
+            char *cform, long twidth, char *output, int *status);
+
+/*  the following 4 routines are VMS macros used on VAX or Alpha VMS */
+void ieevpd(double *inarray, double *outarray, long *nvals);
+void ieevud(double *inarray, double *outarray, long *nvals);
+void ieevpr(float *inarray, float *outarray, long *nvals);
+void ieevur(float *inarray, float *outarray, long *nvals);
+
+/*  routines related to the lexical parser  */
+int  ffselect_table(fitsfile **fptr, char *outfile, char *expr,  int *status);
+int  ffiprs( fitsfile *fptr, int compressed, char *expr, int maxdim,
+	     int *datatype, long *nelem, int *naxis, long *naxes,
+	     int *status );
+void ffcprs( void );
+int  ffcvtn( int inputType, void *input, char *undef, long ntodo,
+	     int outputType, void *nulval, void *output,
+	     int *anynull, int *status );
+int  parse_data( long totalrows, long offset, long firstrow,
+                 long nrows, int nCols, iteratorCol *colData,
+                 void *userPtr );
+int  uncompress_hkdata( fitsfile *fptr, long ntimes, 
+                        double *times, int *status );
+int  ffffrw_work( long totalrows, long offset, long firstrow,
+                  long nrows, int nCols, iteratorCol *colData,
+                  void *userPtr );
+
+int fits_translate_pixkeyword(char *inrec, char *outrec,char *patterns[][2],
+    int npat, int naxis, int *colnum, int *pat_num, int *i,
+      int *j, int *n, int *m, int *l, int *status);
+
+/*  image compression routines */
+int fits_write_compressed_img(fitsfile *fptr, 
+            int  datatype, long  *fpixel, long *lpixel,   
+            int nullcheck, void *array,  void *nulval,
+            int  *status);
+int fits_write_compressed_pixels(fitsfile *fptr, 
+            int  datatype, LONGLONG  fpixel, LONGLONG npixels,   
+            int nullcheck,  void *array, void *nulval,
+            int  *status);
+int fits_write_compressed_img_plane(fitsfile *fptr, int  datatype, 
+      int  bytesperpixel,  long   nplane, long *firstcoord, long *lastcoord, 
+      long *naxes,  int  nullcheck, 
+      void *array,  void *nullval, long *nread, int  *status);
+
+int imcomp_init_table(fitsfile *outfptr,
+        int bitpix, int naxis,long *naxes, int writebitpix, int *status);
+int imcomp_calc_max_elem (int comptype, int nx, int zbitpix, int blocksize);
+int imcomp_copy_imheader(fitsfile *infptr, fitsfile *outfptr,
+                int *status);
+int imcomp_copy_img2comp(fitsfile *infptr, fitsfile *outfptr, int *status);
+int imcomp_copy_comp2img(fitsfile *infptr, fitsfile *outfptr, 
+                          int norec, int *status);
+int imcomp_copy_prime2img(fitsfile *infptr, fitsfile *outfptr, int *status);
+int imcomp_compress_image (fitsfile *infptr, fitsfile *outfptr,
+                 int *status);
+int imcomp_compress_tile (fitsfile *outfptr, long row, 
+    int datatype,  void *tiledata, long tilelen, long nx, long ny,
+    int nullcheck, void *nullval, int *status);
+int imcomp_nullscale(int *idata, long tilelen, int nullflagval, int nullval,
+     double scale, double zero, int * status);
+int imcomp_nullvalues(int *idata, long tilelen, int nullflagval, int nullval,
+     int * status);
+int imcomp_scalevalues(int *idata, long tilelen, double scale, double zero,
+     int * status);
+int imcomp_nullscalefloats(float *fdata, long tilelen, int *idata, 
+    double scale, double zero, int nullcheck, float nullflagval, int nullval,
+    int *status);
+int imcomp_nullfloats(float *fdata, long tilelen, int *idata, int nullcheck,
+    float nullflagval, int nullval, int *status);
+int imcomp_nullscaledoubles(double *fdata, long tilelen, int *idata, 
+    double scale, double zero, int nullcheck, double nullflagval, int nullval,
+    int *status);
+int imcomp_nulldoubles(double *fdata, long tilelen, int *idata, int nullcheck,
+    double nullflagval, int nullval, int *status);
+    
+ 
+/*  image decompression routines */
+int fits_read_compressed_img(fitsfile *fptr, 
+            int  datatype, LONGLONG  *fpixel,LONGLONG  *lpixel,long *inc,   
+            int nullcheck, void *nulval,  void *array, char *nullarray,
+            int  *anynul, int  *status);
+int fits_read_compressed_pixels(fitsfile *fptr, 
+            int  datatype, LONGLONG  fpixel, LONGLONG npixels,   
+            int nullcheck, void *nulval,  void *array, char *nullarray,
+            int  *anynul, int  *status);
+int fits_read_compressed_img_plane(fitsfile *fptr, int  datatype, 
+      int  bytesperpixel,  long   nplane, LONGLONG *firstcoord, LONGLONG *lastcoord, 
+      long *inc,  long *naxes,  int  nullcheck,  void *nullval, 
+      void *array, char *nullarray, int  *anynul, long *nread, int  *status);
+
+int imcomp_get_compressed_image_par(fitsfile *infptr, int *status);
+int imcomp_decompress_tile (fitsfile *infptr,
+          int nrow, int tilesize, int datatype, int nullcheck,
+          void *nulval, void *buffer, char *bnullarray, int *anynul,
+          int *status);
+int imcomp_copy_overlap (char *tile, int pixlen, int ndim,
+         long *tfpixel, long *tlpixel, char *bnullarray, char *image,
+         long *fpixel, long *lpixel, long *inc, int nullcheck, char *nullarray,
+         int *status);
+int imcomp_merge_overlap (char *tile, int pixlen, int ndim,
+         long *tfpixel, long *tlpixel, char *bnullarray, char *image,
+         long *fpixel, long *lpixel, int nullcheck, int *status);
+int imcomp_decompress_img(fitsfile *infptr, fitsfile *outfptr, int datatype,
+         int  *status);
+int fits_quantize_float (long row, float fdata[], long nx, long ny, int nullcheck,
+         float in_null_value,
+           float quantize_level, int idata[], double *bscale, double *bzero,
+           int *iminval, int *imaxval);
+int fits_quantize_double (long row, double fdata[], long nx, long ny, int nullcheck,
+         double in_null_value,
+           float quantize_level, int idata[], double *bscale, double *bzero,
+           int *iminval, int *imaxval);
+int fits_rcomp(int a[], int nx, unsigned char *c, int clen,int nblock);
+int fits_rcomp_short(short a[], int nx, unsigned char *c, int clen,int nblock);
+int fits_rcomp_byte(signed char a[], int nx, unsigned char *c, int clen,int nblock);
+int fits_rdecomp (unsigned char *c, int clen, unsigned int array[], int nx,
+             int nblock);
+int fits_rdecomp_short (unsigned char *c, int clen, unsigned short array[], int nx,
+             int nblock);
+int fits_rdecomp_byte (unsigned char *c, int clen, unsigned char array[], int nx,
+             int nblock);
+int pl_p2li (int *pxsrc, int xs, short *lldst, int npix);
+int pl_l2pi (short *ll_src, int xs, int *px_dst, int npix);
+int fits_init_randoms(void);
+
+int fitsio_init_lock(void);
+
+/* general driver routines */
+
+int urltype2driver(char *urltype, int *driver);
+
+int fits_register_driver( char *prefix,
+	int (*init)(void),
+	int (*fitsshutdown)(void),
+	int (*setoptions)(int option),
+	int (*getoptions)(int *options),
+	int (*getversion)(int *version),
+	int (*checkfile) (char *urltype, char *infile, char *outfile),
+	int (*fitsopen)(char *filename, int rwmode, int *driverhandle),
+	int (*fitscreate)(char *filename, int *driverhandle),
+	int (*fitstruncate)(int driverhandle, LONGLONG filesize),
+	int (*fitsclose)(int driverhandle),
+	int (*fremove)(char *filename),
+        int (*size)(int driverhandle, LONGLONG *size),
+	int (*flush)(int driverhandle),
+	int (*seek)(int driverhandle, LONGLONG offset),
+	int (*fitsread) (int driverhandle, void *buffer, long nbytes),
+	int (*fitswrite)(int driverhandle, void *buffer, long nbytes));
+
+/* file driver I/O routines */
+
+int file_init(void);
+int file_setoptions(int options);
+int file_getoptions(int *options);
+int file_getversion(int *version);
+int file_shutdown(void);
+int file_checkfile(char *urltype, char *infile, char *outfile);
+int file_open(char *filename, int rwmode, int *driverhandle);
+int file_compress_open(char *filename, int rwmode, int *hdl);
+int file_openfile(char *filename, int rwmode, FILE **diskfile);
+int file_create(char *filename, int *driverhandle);
+int file_truncate(int driverhandle, LONGLONG filesize);
+int file_size(int driverhandle, LONGLONG *filesize);
+int file_close(int driverhandle);
+int file_remove(char *filename);
+int file_flush(int driverhandle);
+int file_seek(int driverhandle, LONGLONG offset);
+int file_read (int driverhandle, void *buffer, long nbytes);
+int file_write(int driverhandle, void *buffer, long nbytes);
+int file_is_compressed(char *filename);
+
+/* stream driver I/O routines */
+
+int stream_open(char *filename, int rwmode, int *driverhandle);
+int stream_create(char *filename, int *driverhandle);
+int stream_size(int driverhandle, LONGLONG *filesize);
+int stream_close(int driverhandle);
+int stream_flush(int driverhandle);
+int stream_seek(int driverhandle, LONGLONG offset);
+int stream_read (int driverhandle, void *buffer, long nbytes);
+int stream_write(int driverhandle, void *buffer, long nbytes);
+
+/* memory driver I/O routines */
+
+int mem_init(void);
+int mem_setoptions(int options);
+int mem_getoptions(int *options);
+int mem_getversion(int *version);
+int mem_shutdown(void);
+int mem_create(char *filename, int *handle);
+int mem_create_comp(char *filename, int *handle);
+int mem_openmem(void **buffptr, size_t *buffsize, size_t deltasize,
+                void *(*memrealloc)(void *p, size_t newsize), int *handle);
+int mem_createmem(size_t memsize, int *handle);
+int stdin_checkfile(char *urltype, char *infile, char *outfile);
+int stdin_open(char *filename, int rwmode, int *handle);
+int stdin2mem(int hd);
+int stdin2file(int hd);
+int stdout_close(int handle);
+int mem_compress_openrw(char *filename, int rwmode, int *hdl);
+int mem_compress_open(char *filename, int rwmode, int *hdl);
+int mem_compress_stdin_open(char *filename, int rwmode, int *hdl);
+int mem_iraf_open(char *filename, int rwmode, int *hdl);
+int mem_rawfile_open(char *filename, int rwmode, int *hdl);
+int mem_size(int handle, LONGLONG *filesize);
+int mem_truncate(int handle, LONGLONG filesize);
+int mem_close_free(int handle);
+int mem_close_keep(int handle);
+int mem_close_comp(int handle);
+int mem_seek(int handle, LONGLONG offset);
+int mem_read(int hdl, void *buffer, long nbytes);
+int mem_write(int hdl, void *buffer, long nbytes);
+int mem_uncompress2mem(char *filename, FILE *diskfile, int hdl);
+
+int iraf2mem(char *filename, char **buffptr, size_t *buffsize, 
+      size_t *filesize, int *status);
+
+/* root driver I/O routines */
+
+int root_init(void);
+int root_setoptions(int options);
+int root_getoptions(int *options);
+int root_getversion(int *version);
+int root_shutdown(void);
+int root_open(char *filename, int rwmode, int *driverhandle);
+int root_create(char *filename, int *driverhandle);
+int root_close(int driverhandle);
+int root_flush(int driverhandle);
+int root_seek(int driverhandle, LONGLONG offset);
+int root_read (int driverhandle, void *buffer, long nbytes);
+int root_write(int driverhandle, void *buffer, long nbytes);
+int root_size(int handle, LONGLONG *filesize);
+
+/* http driver I/O routines */
+
+int http_checkfile(char *urltype, char *infile, char *outfile);
+int http_open(char *filename, int rwmode, int *driverhandle);
+int http_file_open(char *filename, int rwmode, int *driverhandle);
+int http_compress_open(char *filename, int rwmode, int *driverhandle);
+
+/* ftp driver I/O routines */
+
+int ftp_checkfile(char *urltype, char *infile, char *outfile);
+int ftp_open(char *filename, int rwmode, int *driverhandle);
+int ftp_file_open(char *filename, int rwmode, int *driverhandle);
+int ftp_compress_open(char *filename, int rwmode, int *driverhandle);
+
+int uncompress2mem(char *filename, FILE *diskfile,
+             char **buffptr, size_t *buffsize,
+             void *(*mem_realloc)(void *p, size_t newsize),
+             size_t *filesize, int *status);
+
+int uncompress2mem_from_mem(                                                
+             char *inmemptr,     
+             size_t inmemsize, 
+             char **buffptr,  
+             size_t *buffsize,  
+             void *(*mem_realloc)(void *p, size_t newsize), 
+             size_t *filesize,  
+             int *status);
+
+int uncompress2file(char *filename, 
+             FILE *indiskfile, 
+             FILE *outdiskfile, 
+             int *status);
+
+int compress2mem_from_mem(                                                
+             char *inmemptr,     
+             size_t inmemsize, 
+             char **buffptr,  
+             size_t *buffsize,  
+             void *(*mem_realloc)(void *p, size_t newsize), 
+             size_t *filesize,  
+             int *status);
+
+int compress2file_from_mem(                                                
+             char *inmemptr,     
+             size_t inmemsize, 
+             FILE *outdiskfile, 
+             size_t *filesize,   /* O - size of file, in bytes              */
+             int *status);
+
+
+#ifdef HAVE_GSIFTP
+/* prototypes for gsiftp driver I/O routines */
+#include "drvrgsiftp.h"
+#endif
+
+#ifdef HAVE_SHMEM_SERVICES
+/* prototypes for shared memory driver I/O routines  */
+#include "drvrsmem.h"
+#endif
+
+#if defined(vms) || defined(__vms) || defined(WIN32) || defined(__WIN32__) || (defined(macintosh) && !defined(TARGET_API_MAC_CARBON))
+/* A hack for nonunix machines, which lack strcasecmp and strncasecmp */
+int strcasecmp (const char *s1, const char *s2       );
+int strncasecmp(const char *s1, const char *s2, size_t n);
+#endif
+
+/* end of the entire "ifndef _FITSIO2_H" block */
+#endif
diff --git a/src/plugins/cfitsio/fpack.h b/src/plugins/cfitsio/fpack.h
new file mode 100644
index 0000000..8a350ba
--- /dev/null
+++ b/src/plugins/cfitsio/fpack.h
@@ -0,0 +1,164 @@
+/* used by FPACK and FUNPACK
+ * R. Seaman, NOAO
+ * W. Pence, NASA/GSFC
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+/* not needed any more */
+/* #include <unistd.h> */
+/* #include <sys/stat.h> */
+/* #include <sys/types.h> */
+
+#define	FPACK_VERSION	"1.6.0 (Feb 2011)"
+/*
+VERSION	History
+1.6.0 (February 2011)
+    - Added full support for compressing and uncompressing FITS binary tables
+      using a newly proposed format convention.  This is intended only for
+      further feasibility studies, and is not recommended for use with publicly
+      distributed FITS files.
+    - Use the minimum of the MAD 2nd, 3rd, and 5th order values as a more
+      conservative extimate of the noise when quantizing floating point images.
+    - Enhanced the tile compression routines so that a tile that contains all
+      NaN pixel values will be compressed.
+    - When uncompressing an image that was originally in a FITS primary array,
+      funpack will also append any new keywords that were written into the 
+      primary array of the compressed FITS file after the file was compressed.
+    - Added support for the GZIP_2 algorithm, which shuffles the bytes in the
+      pixel values prior to compressing them with gzip.      
+1.5.1 (December 2010) Added prototype, mainly hidden, support for compressing 
+      binary tables.  
+1.5.0 (August 2010) Added the -i2f option to lossy compress integer images.
+1.4.0 (Jan 2010) Reduced the default value for the q floating point image 
+      quantization parameter from 16 to 4.  This results in about 50% better
+      compression (from about 4.6x to 6.4) with no lost of significant information 
+      (with the new subtractive dithering enhancement).  Replaced the code for
+      generating temporary filenames to make the code more portable (to Windows). 
+      Replaced calls to the unix 'access' and 'stat' functions with more portable
+      code.   When unpacking a file, write it first to a temporary file, then
+      rename it when finished, so that other tasks cannot try to read the file
+      before it is complete.
+1.3.0 (Oct 2009) added randomization to the dithering pattern so that
+      the same pattern is not used for every image; also added an option
+      for losslessly compressing floating point images with GZIP for test 
+      purposes (not recommended for general use).  Also added support for
+      reading the input FITS file from the stdin file streams.
+1.2.0 (Sept 2009) added subtractive dithering feature (in CFITSIO) when
+      quantizing floating point images; When packing an IRAF .imh + .pix image,
+      the file name is changed to FILE.fits.fz, and if the original file is
+      deleted, then both the .imh and .pix files are deleted.
+1.1.4 (May 2009) added -E option to funpack to unpack a list of HDUs
+1.1.3 (March 2009)  minor modifications to the content and format of the -T report
+1.1.2 (September 2008)
+*/
+
+#define	FP_INIT_MAGIC	42
+#define	FPACK		0
+#define	FUNPACK		1
+
+/* changed from 16 in Jan. 2010 */
+#define	DEF_QLEVEL	4.  
+
+#define	DEF_HCOMP_SCALE	 0.
+#define	DEF_HCOMP_SMOOTH 0
+#define	DEF_RESCALE_NOISE 0
+
+#define	SZ_STR		513
+#define	SZ_CARD		81
+
+
+typedef struct
+{
+	int	comptype;
+	float	quantize_level;
+	int     no_dither;
+	int     dither_offset;
+	float	scale;
+	float	rescale_noise;
+	int	smooth;
+	int	int_to_float;
+	float	n3ratio;
+	float	n3min;
+	long	ntile[MAX_COMPRESS_DIM];
+
+	int	to_stdout;
+	int	listonly;
+	int	clobber;
+	int	delete_input;
+	int	do_not_prompt;
+	int	do_checksums;
+	int	do_gzip_file;
+	int     do_tables;  
+	int     do_fast;
+	int	test_all;
+	int	verbose;
+
+	char	prefix[SZ_STR];
+	char	extname[SZ_STR];
+	int	delete_suffix;
+	char	outfile[SZ_STR];
+	int	firstfile;
+
+	int	initialized;
+	int	preflight_checked;
+} fpstate;
+
+typedef struct
+{
+	int	n_nulls;
+	double	minval;
+	double 	maxval;
+	double	mean;
+	double	sigma;
+	double	noise1;
+	double	noise2;
+	double	noise3;
+	double	noise5;
+} imgstats;
+
+int fp_get_param (int argc, char *argv[], fpstate *fpptr);
+void abort_fpack(int sig);
+void fp_abort_output (fitsfile *infptr, fitsfile *outfptr, int stat); 
+int fp_usage (void);
+int fp_help (void);
+int fp_hint (void); 
+int fp_init (fpstate *fpptr);
+int fp_list (int argc, char *argv[], fpstate fpvar);
+int fp_info (char *infits);
+int fp_info_hdu (fitsfile *infptr);
+int fp_preflight (int argc, char *argv[], int unpack, fpstate *fpptr);
+int fp_loop (int argc, char *argv[], int unpack, fpstate fpvar);
+int fp_pack (char *infits, char *outfits, fpstate fpvar, int *islossless);
+int fp_unpack (char *infits, char *outfits, fpstate fpvar);
+int fp_test (char *infits, char *outfits, char *outfits2, fpstate fpvar);
+int fp_pack_hdu (fitsfile *infptr, fitsfile *outfptr, fpstate fpvar, 
+    int *islossless, int *status);
+int fp_unpack_hdu (fitsfile *infptr, fitsfile *outfptr, fpstate fpvar, int *status);
+int fits_read_image_speed (fitsfile *infptr, float *whole_elapse, 
+    float *whole_cpu, float *row_elapse, float *row_cpu, int *status);
+int fp_test_hdu (fitsfile *infptr, fitsfile *outfptr, fitsfile *outfptr2, 
+	fpstate fpvar, int *status);
+int marktime(int *status);
+int gettime(float *elapse, float *elapscpu, int *status);
+int fits_read_image_speed (fitsfile *infptr, float *whole_elapse, 
+    float *whole_cpu, float *row_elapse, float *row_cpu, int *status);
+
+int fp_i2stat(fitsfile *infptr, int naxis, long *naxes, imgstats *imagestats, int *status);
+int fp_i4stat(fitsfile *infptr, int naxis, long *naxes, imgstats *imagestats, int *status);
+int fp_r4stat(fitsfile *infptr, int naxis, long *naxes, imgstats *imagestats, int *status);
+int fp_i2rescale(fitsfile *infptr, int naxis, long *naxes, double rescale,
+    fitsfile *outfptr, int *status);
+int fp_i4rescale(fitsfile *infptr, int naxis, long *naxes, double rescale,
+    fitsfile *outfptr, int *status);
+    
+int fp_msg (char *msg);
+int fp_version (void);
+int fp_noop (void);
+
+int fu_get_param (int argc, char *argv[], fpstate *fpptr);
+int fu_usage (void);
+int fu_hint (void);
+int fu_help (void);
diff --git a/src/plugins/cfitsio/fpackutil.c b/src/plugins/cfitsio/fpackutil.c
new file mode 100644
index 0000000..8eb74d7
--- /dev/null
+++ b/src/plugins/cfitsio/fpackutil.c
@@ -0,0 +1,2381 @@
+/* FPACK utility routines
+   R. Seaman, NOAO & W. Pence, NASA/GSFC
+*/
+
+#include <time.h>
+#include <float.h>
+#include <signal.h>
+
+/* #include  "bzlib.h"  only for experimental purposes */
+
+#if defined(unix) || defined(__unix__)  || defined(__unix)
+#include <sys/time.h>
+#endif
+
+#include <math.h>
+#include "fitsio.h"
+#include "fpack.h"
+
+/* these filename buffer are used to delete temporary files */
+/* in case the program is aborted */
+char tempfilename[SZ_STR];
+char tempfilename2[SZ_STR];
+char tempfilename3[SZ_STR];
+
+/* nearest integer function */
+# define NINT(x)  ((x >= 0.) ? (int) (x + 0.5) : (int) (x - 0.5))
+# define NSHRT(x) ((x >= 0.) ? (short) (x + 0.5) : (short) (x - 0.5))
+
+/* define variables for measuring elapsed time */
+clock_t scpu, ecpu;
+long startsec;   /* start of elapsed time interval */
+int startmilli;  /* start of elapsed time interval */
+
+/*  CLOCKS_PER_SEC should be defined by most compilers */
+#if defined(CLOCKS_PER_SEC)
+#define CLOCKTICKS CLOCKS_PER_SEC
+#else
+/* on SUN OS machine, CLOCKS_PER_SEC is not defined, so set its value */
+#define CLOCKTICKS 1000000
+#endif
+
+FILE *outreport;
+
+/* dimension of central image area to be sampled for test statistics */
+int XSAMPLE = 4100;
+int YSAMPLE = 4100;
+
+int fp_msg (char *msg)
+{
+        printf ("%s", msg);
+        return(0);
+}
+/*--------------------------------------------------------------------------*/
+int fp_noop (void)
+{
+        fp_msg ("Input and output files are unchanged.\n");
+        return(0);
+}
+/*--------------------------------------------------------------------------*/
+void fp_abort_output (fitsfile *infptr, fitsfile *outfptr, int stat)
+{
+	int status = 0, hdunum;
+	char  msg[SZ_STR];
+
+	fits_file_name(infptr, tempfilename, &status);
+	fits_get_hdu_num(infptr, &hdunum);
+	
+        fits_close_file (infptr, &status);
+
+	sprintf(msg, "Error processing file: %s\n", tempfilename);
+	fp_msg (msg);
+	sprintf(msg, "  in HDU number %d\n", hdunum);
+	fp_msg (msg);
+
+	fits_report_error (stderr, stat);
+
+	if (outfptr) {
+	    fits_delete_file(outfptr, &status);
+	    fp_msg ("Input file is unchanged.\n");
+	}
+
+	exit (stat); 
+}
+/*--------------------------------------------------------------------------*/
+int fp_version (void)
+{
+        float version;
+        char cfitsioversion[40];
+
+        fp_msg (FPACK_VERSION);
+        fits_get_version(&version);
+        sprintf(cfitsioversion, " CFITSIO version %5.3f", version);
+        fp_msg(cfitsioversion);
+        fp_msg ("\n");
+        return(0);
+}
+/*--------------------------------------------------------------------------*/
+int fp_access (char *filename)
+{
+	/* test if a file exists */
+	
+	FILE *diskfile;
+	
+	diskfile = fopen(filename, "r");
+
+	if (diskfile) {
+		fclose(diskfile);
+		return(0);
+	} else {
+	        return(-1);
+	}
+}
+/*--------------------------------------------------------------------------*/
+int fp_tmpnam(char *suffix, char *rootname, char *tmpnam)
+{
+	/* create temporary file name */
+
+	int maxtry = 30, len, i1 = 0, ii;
+
+	if (strlen(suffix) + strlen(rootname) > SZ_STR-5) {
+	    fp_msg ("Error: filename is too long to create tempory file\n"); exit (-1);
+	}
+
+	strcpy (tmpnam, rootname);  /* start with rootname */
+	strcat(tmpnam, suffix);     /* append the suffix */
+
+	maxtry = SZ_STR - strlen(tmpnam) - 1;
+
+        for (ii = 0; ii < maxtry; ii++) {
+		if (fp_access(tmpnam)) break;  /* good, the file does not exist */
+		strcat(tmpnam, "x");  /* append an x to the name, and try again */
+	}
+
+	if (ii == maxtry) {
+		fp_msg ("\nCould not create temporary file name:\n");
+		fp_msg (tmpnam);
+		fp_msg ("\n");
+		exit (-1);
+	}
+
+        return(0);
+}
+/*--------------------------------------------------------------------------*/
+int fp_init (fpstate *fpptr)
+{
+	int	ii;
+
+	fpptr->comptype = RICE_1;
+	fpptr->quantize_level = DEF_QLEVEL;
+        fpptr->no_dither = 0;
+        fpptr->dither_offset = 0;
+        fpptr->int_to_float = 0;
+
+	/* thresholds when using the -i2f flag */
+        fpptr->n3ratio = 1.20;  /* minimum ratio of image noise sigma / q */
+        fpptr->n3min = 6.;     /* minimum noise sigma. */
+
+	fpptr->scale = DEF_HCOMP_SCALE;
+	fpptr->smooth = DEF_HCOMP_SMOOTH;
+	fpptr->rescale_noise = DEF_RESCALE_NOISE;
+	fpptr->ntile[0] = (long) 0;	/* 0 means extent of axis */
+
+	for (ii=1; ii < MAX_COMPRESS_DIM; ii++)
+	    fpptr->ntile[ii] = (long) 1;
+
+	fpptr->to_stdout = 0;
+	fpptr->listonly = 0;
+	fpptr->clobber = 0;
+	fpptr->delete_input = 0;
+	fpptr->do_not_prompt = 0;
+	fpptr->do_checksums = 1;
+	fpptr->do_gzip_file = 0;
+	fpptr->do_tables = 0;  /* this is for beta testing purposes only */
+	fpptr->do_fast = 0;  /* this is for beta testing purposes only */
+	fpptr->test_all = 0;
+	fpptr->verbose = 0;
+
+	fpptr->prefix[0] = 0;
+	fpptr->extname[0] = 0;
+	fpptr->delete_suffix = 0;
+	fpptr->outfile[0] = 0;
+
+	fpptr->firstfile = 1;
+
+	/* magic number for initialization check, boolean for preflight
+	 */
+	fpptr->initialized = FP_INIT_MAGIC;
+	fpptr->preflight_checked = 0;
+	return(0);
+}
+/*--------------------------------------------------------------------------*/
+int fp_list (int argc, char *argv[], fpstate fpvar)
+{
+	fitsfile *infptr;
+	char	infits[SZ_STR], msg[SZ_STR];
+	int	hdunum, iarg, stat=0;
+	LONGLONG sizell;
+
+	if (fpvar.initialized != FP_INIT_MAGIC) {
+	    fp_msg ("Error: internal initialization error\n"); exit (-1);
+	}
+
+	for (iarg=fpvar.firstfile; iarg < argc; iarg++) {
+	    strncpy (infits, argv[iarg], SZ_STR);
+
+	    if (strchr (infits, '[') || strchr (infits, ']')) {
+		fp_msg ("Error: section/extension notation not supported: ");
+		fp_msg (infits); fp_msg ("\n"); exit (-1);
+	    }
+
+	    if (fp_access (infits) != 0) {
+		        fp_msg ("Error: can't find or read input file "); fp_msg (infits);
+		        fp_msg ("\n"); fp_noop (); exit (-1);
+	    }
+
+	    fits_open_file (&infptr, infits, READONLY, &stat);
+	    if (stat) { fits_report_error (stderr, stat); exit (stat); }
+
+	    /* move to the end of file, to get the total size in bytes */
+	    fits_get_num_hdus (infptr, &hdunum, &stat);
+	    fits_movabs_hdu (infptr, hdunum, NULL, &stat);
+	    fits_get_hduaddrll(infptr, NULL, NULL, &sizell, &stat);
+
+
+	    if (stat) { 
+	        fp_abort_output(infptr, NULL, stat);
+	    }
+
+            sprintf (msg, "# %s (", infits); fp_msg (msg);
+
+#if defined(_MSC_VER)
+    /* Microsoft Visual C++ 6.0 uses '%I64d' syntax  for 8-byte integers */
+        sprintf(msg, "%I64d bytes)\n", sizell); fp_msg (msg);
+#elif (USE_LL_SUFFIX == 1)
+        sprintf(msg, "%lld bytes)\n", sizell); fp_msg (msg);
+#else
+        sprintf(msg, "%ld bytes)\n", sizell); fp_msg (msg);
+#endif
+	    fp_info_hdu (infptr);
+
+	    fits_close_file (infptr, &stat);
+	    if (stat) { fits_report_error (stderr, stat); exit (stat); }
+	}
+	return(0);
+}
+/*--------------------------------------------------------------------------*/
+int fp_info_hdu (fitsfile *infptr)
+{
+	long	naxes[9] = {1, 1, 1, 1, 1, 1, 1, 1, 1};
+	char	msg[SZ_STR], val[SZ_CARD], com[SZ_CARD];
+        int     comptype, naxis=0, hdutype, bitpix, hdupos, stat=0, ii;
+        unsigned long   datasum, hdusum;
+
+	fits_movabs_hdu (infptr, 1, NULL, &stat);
+	if (stat) { 
+	    fp_abort_output(infptr, NULL, stat);
+	}
+
+	for (hdupos=1; ! stat; hdupos++) {
+	    fits_get_hdu_type (infptr, &hdutype, &stat);
+	    if (stat) { 
+	        fp_abort_output(infptr, NULL, stat);
+	    }
+
+	    /* fits_get_hdu_type calls unknown extensions "IMAGE_HDU"
+	     * so consult XTENSION keyword itself
+	     */
+	    fits_read_keyword (infptr, "XTENSION", val, com, &stat);
+	    if (stat == KEY_NO_EXIST) {
+		/* in primary HDU which by definition is an "image" */
+		stat=0; /* clear for later error handling */
+
+	    } else if (stat) {
+	        fp_abort_output(infptr, NULL, stat);
+
+	    } else if (hdutype == IMAGE_HDU) {
+		/* that is, if XTENSION != "IMAGE" AND != "BINTABLE" */
+		if (strncmp (val+1, "IMAGE", 5) &&
+		    strncmp (val+1, "BINTABLE", 5)) {
+
+		     /* assign something other than any of these */
+		    hdutype = IMAGE_HDU + ASCII_TBL + BINARY_TBL;
+		}
+	    }
+
+            fits_get_chksum(infptr, &datasum, &hdusum, &stat);
+
+	    if (hdutype == IMAGE_HDU) {
+		sprintf (msg, "  %d IMAGE", hdupos); fp_msg (msg);
+                sprintf (msg, " SUMS=%lu/%lu", (unsigned long) (~((int) hdusum)), datasum); fp_msg (msg);
+
+		fits_get_img_param (infptr, 9, &bitpix, &naxis, naxes, &stat);
+
+                sprintf (msg, " BITPIX=%d", bitpix); fp_msg (msg);
+
+		if (naxis == 0) {
+                    sprintf (msg, " [no_pixels]"); fp_msg (msg);
+		} else if (naxis == 1) {
+		    sprintf (msg, " [%ld]", naxes[1]); fp_msg (msg);
+		} else {
+		    sprintf (msg, " [%ld", naxes[0]); fp_msg (msg);
+		    for (ii=1; ii < naxis; ii++) {
+			sprintf (msg, "x%ld", naxes[ii]); fp_msg (msg);
+		    }
+		    fp_msg ("]");
+		}
+
+                if (fits_is_compressed_image (infptr, &stat)) {
+                    fits_read_keyword (infptr, "ZCMPTYPE", val, com, &stat);
+
+                    /* allow for quote in keyword value */
+                    if (! strncmp (val+1, "RICE_1", 6))
+                        fp_msg (" tiled_rice\n");
+                    else if (! strncmp (val+1, "GZIP_1", 6))
+                        fp_msg (" tiled_gzip_1\n");
+                    else if (! strncmp (val+1, "GZIP_2", 6))
+                        fp_msg (" tiled_gzip_2\n");
+                    else if (! strncmp (val+1, "PLIO_1", 6))
+                        fp_msg (" tiled_plio\n");
+                    else if (! strncmp (val+1, "HCOMPRESS_1", 11))
+                        fp_msg (" tiled_hcompress\n");
+                    else
+                        fp_msg (" unknown\n");
+
+                } else
+                    fp_msg (" not_tiled\n");
+
+            } else if (hdutype == ASCII_TBL) {
+                sprintf (msg, "  %d ASCII_TBL", hdupos); fp_msg (msg);
+                sprintf (msg, " SUMS=%lu/%lu\n", (unsigned long) (~((int) hdusum)), datasum); fp_msg (msg);
+
+            } else if (hdutype == BINARY_TBL) {
+                sprintf (msg, "  %d BINARY_TBL", hdupos); fp_msg (msg);
+                sprintf (msg, " SUMS=%lu/%lu\n", (unsigned long) (~((int) hdusum)), datasum); fp_msg (msg);
+
+            } else {
+                sprintf (msg, "  %d OTHER", hdupos); fp_msg (msg);
+                sprintf (msg, " SUMS=%lu/%lu", (unsigned long) (~((int) hdusum), datasum)); fp_msg (msg);
+                sprintf (msg, " %s\n", val); fp_msg (msg);
+            }
+
+	    fits_movrel_hdu (infptr, 1, NULL, &stat);
+	}
+	return(0);
+}
+
+/*--------------------------------------------------------------------------*/
+int fp_preflight (int argc, char *argv[], int unpack, fpstate *fpptr)
+{
+	char	infits[SZ_STR], outfits[SZ_STR], temp[SZ_STR], *cptr;
+	int	iarg, suflen, namelen, nfiles = 0;
+
+	if (fpptr->initialized != FP_INIT_MAGIC) {
+	    fp_msg ("Error: internal initialization error\n"); exit (-1);
+	}
+
+	for (iarg=fpptr->firstfile; iarg < argc; iarg++) {
+
+            outfits[0] = '\0';
+	    
+            if (strlen(argv[iarg]) > SZ_STR - 4) {  /* allow for .fz or .gz suffix */
+		    fp_msg ("Error: input file name\n   "); fp_msg (argv[iarg]);
+		    fp_msg ("\n   is too long\n"); fp_noop (); exit (-1);
+	    }
+
+	    strncpy (infits, argv[iarg], SZ_STR);
+
+	    if (strchr (infits, '[') || strchr (infits, ']')) {
+		fp_msg ("Error: section/extension notation not supported: ");
+		fp_msg (infits); fp_msg ("\n"); fp_noop (); exit (-1);
+	    }
+	    
+            if (unpack) {
+	  	/* ********** This section applies to funpack ************ */
+
+	      /* check that input file  exists */
+	      if (infits[0] != '-') {  /* if not reading from stdin stream */
+	         if (fp_access (infits) != 0) {  /* if not, then check if */
+		    strcat(infits, ".fz");       /* a .fz version exsits */
+	            if (fp_access (infits) != 0) {
+                        namelen = strlen(infits);
+                        infits[namelen - 3] = '\0';  /* remove the .fz suffix */
+		        fp_msg ("Error: can't find or read input file "); fp_msg (infits);
+		        fp_msg ("\n"); fp_noop (); exit (-1);
+                    }
+	         } else {   /* make sure a .fz version of the same file doesn't exist */
+                    namelen = strlen(infits);
+		    strcat(infits, ".fz");   
+	            if (fp_access (infits) == 0) {
+                        infits[namelen] = '\0';  /* remove the .fz suffix */
+		        fp_msg ("Error: ambiguous input file name.  Which file should be unpacked?:\n  ");
+		        fp_msg (infits); fp_msg ("\n  "); 
+		        fp_msg (infits); fp_msg (".fz\n"); 
+		        fp_noop (); exit (-1);
+                    } else {
+                        infits[namelen] = '\0';  /* remove the .fz suffix */
+		    }
+	         }
+	      }
+
+              /* if writing to stdout, then we are all done */
+	      if (fpptr->to_stdout) {
+                      continue;
+	      }
+
+              if (fpptr->outfile[0]) {  /* user specified output file name */
+	          nfiles++;
+		  if (nfiles > 1) {
+		      fp_msg ("Error: cannot use same output file name for multiple files:\n   ");
+		      fp_msg (fpptr->outfile);
+		      fp_msg ("\n"); fp_noop (); exit (-1);
+	          }
+
+                  /* check that output file doesn't exist */
+	          if (fp_access (fpptr->outfile) == 0) {
+		            fp_msg ("Error: output file already exists:\n "); 
+			    fp_msg (fpptr->outfile);
+		            fp_msg ("\n "); fp_noop (); exit (-1);
+		  } 
+                  continue;
+	      }     
+
+	      /* construct output file name to test */
+	      if (fpptr->prefix[0]) {
+                  if (strlen(fpptr->prefix) + strlen(infits) > SZ_STR - 1) {
+		      fp_msg ("Error: output file name for\n   "); fp_msg (infits);
+		      fp_msg ("\n   is too long with the prefix\n"); fp_noop (); exit (-1);
+	          }
+	          strcat(outfits,fpptr->prefix);
+	      }
+
+	      /* construct output file name */
+	      if (infits[0] == '-') {
+	        strcpy(outfits, "output.fits");
+	      } else {
+	        strcpy(outfits, infits);
+	      }
+
+	      /* remove .gz suffix, if present (output is not gzipped) */
+              namelen = strlen(outfits);
+	      if ( !strcmp(".gz", outfits + namelen - 3) ) {
+                        outfits[namelen - 3] = '\0';
+	      }
+
+	      /* check for .fz suffix that is sometimes required */
+	      /* and remove it if present */
+	      if (infits[0] != '-') {  /* if not reading from stdin stream */
+                 namelen = strlen(outfits);
+	         if ( !strcmp(".fz", outfits + namelen - 3) ) { /* suffix is present */
+                        outfits[namelen - 3] = '\0';
+	         } else if (fpptr->delete_suffix) {  /* required suffix is missing */
+		    fp_msg ("Error: input compressed file "); fp_msg (infits);
+		    fp_msg ("\n does not have the default .fz suffix.\n"); 
+		    fp_noop (); exit (-1);
+	         }
+	      }
+
+	      /* if infits != outfits, make sure outfits doesn't already exist */
+              if (strcmp(infits, outfits)) {
+	                if (fp_access (outfits) == 0) {
+		            fp_msg ("Error: output file already exists:\n "); fp_msg (outfits);
+		            fp_msg ("\n "); fp_noop (); exit (-1);
+		        }       
+	      }
+
+	      /* if gzipping the output, make sure .gz file doesn't exist */
+	      if (fpptr->do_gzip_file) {
+	                strcat(outfits, ".gz");
+	                if (fp_access (outfits) == 0) {
+		            fp_msg ("Error: output file already exists:\n "); fp_msg (outfits);
+		            fp_msg ("\n "); fp_noop (); exit (-1);
+		        }       
+              		namelen = strlen(outfits);
+                        outfits[namelen - 3] = '\0';  /* remove the .gz suffix again */
+	      }
+	  } else {
+	  	/* ********** This section applies to fpack ************ */
+
+	      /* check that input file  exists */
+	      if (infits[0] != '-') {  /* if not reading from stdin stream */
+	        if (fp_access (infits) != 0) {  /* if not, then check if */
+		    strcat(infits, ".gz");     /* a gzipped version exsits */
+	            if (fp_access (infits) != 0) {
+                        namelen = strlen(infits);
+                        infits[namelen - 3] = '\0';  /* remove the .gz suffix */
+		        fp_msg ("Error: can't find or read input file "); fp_msg (infits);
+		        fp_msg ("\n"); fp_noop (); exit (-1);
+                    }
+	        }
+	      }
+
+              /* make sure the file to pack does not already have a .fz suffix */
+              namelen = strlen(infits);
+	      if ( !strcmp(".fz", infits + namelen - 3) ) {
+		        fp_msg ("Error: fpack input file already has '.fz' suffix\n" ); fp_msg (infits);
+		        fp_msg ("\n"); fp_noop (); exit (-1);
+	      }
+
+              /* if writing to stdout, or just testing the files, then we are all done */
+	      if (fpptr->to_stdout || fpptr->test_all) {
+                        continue;
+	      }
+
+	      /* construct output file name */
+	      if (infits[0] == '-') {
+	        strcpy(outfits, "input.fits");
+	      } else {
+	        strcpy(outfits, infits);
+	      }
+
+	      /* remove .gz suffix, if present (output is not gzipped) */
+              namelen = strlen(outfits);
+	      if ( !strcmp(".gz", outfits + namelen - 3) ) {
+                        outfits[namelen - 3] = '\0';
+	      }
+	      
+	      /* remove .imh suffix (IRAF format image), and replace with .fits */
+              namelen = strlen(outfits);
+	      if ( !strcmp(".imh", outfits + namelen - 4) ) {
+                        outfits[namelen - 4] = '\0';
+                        strcat(outfits, ".fits");
+	      }
+
+	      /* If not clobbering the input file, add .fz suffix to output name */
+	      if (! fpptr->clobber)
+		        strcat(outfits, ".fz");
+			
+	      /* if infits != outfits, make sure outfits doesn't already exist */
+              if (strcmp(infits, outfits)) {
+	                if (fp_access (outfits) == 0) {
+		            fp_msg ("Error: output file already exists:\n "); fp_msg (outfits);
+		            fp_msg ("\n "); fp_noop (); exit (-1);
+		        }       
+	      }
+	  }   /* end of fpack section */
+	}
+
+	fpptr->preflight_checked++;
+	return(0);
+}
+
+/*--------------------------------------------------------------------------*/
+/* must run fp_preflight() before fp_loop()
+ */
+int fp_loop (int argc, char *argv[], int unpack, fpstate fpvar)
+{
+	char	infits[SZ_STR], outfits[SZ_STR];
+	char	temp[SZ_STR], answer[30], *cptr;
+	int	ii, iarg, islossless, namelen, iraf_infile = 0, status = 0, ifail;
+	FILE	*diskfile;
+        
+	if (fpvar.initialized != FP_INIT_MAGIC) {
+	    fp_msg ("Error: internal initialization error\n"); exit (-1);
+	} else if (! fpvar.preflight_checked) {
+	    fp_msg ("Error: internal preflight error\n"); exit (-1);
+	}
+
+	if (fpvar.test_all && fpvar.outfile[0]) {
+	    outreport = fopen(fpvar.outfile, "w");
+		fprintf(outreport," Filename Extension BITPIX NAXIS1 NAXIS2 Size N_nulls Minval Maxval Mean Sigm Noise1 Noise2 Noise3 Noise5 T_whole T_rowbyrow ");
+		fprintf(outreport,"[Comp_ratio, Pack_cpu, Unpack_cpu, Lossless readtimes] (repeated for Rice, Hcompress, and GZIP)\n");
+	}
+
+
+	tempfilename[0] = '\0';
+	tempfilename2[0] = '\0';
+	tempfilename3[0] = '\0';
+
+/* set up signal handler to delete temporary file on abort */	    
+#ifdef SIGINT
+    if (signal(SIGINT, SIG_IGN) != SIG_IGN) {
+	(void) signal(SIGINT,  abort_fpack); 
+    }
+#endif
+
+#ifdef SIGTERM
+    if (signal(SIGTERM, SIG_IGN) != SIG_IGN) {
+	(void) signal(SIGTERM,  abort_fpack); 
+    }
+#endif
+
+#ifdef SIGHUP
+    if (signal(SIGHUP, SIG_IGN) != SIG_IGN) {
+	(void) signal(SIGHUP,  abort_fpack);
+    }
+#endif
+
+	for (iarg=fpvar.firstfile; iarg < argc; iarg++) {
+
+          temp[0] = '\0';
+	  outfits[0] = '\0';
+          islossless = 1;
+
+	  strncpy (infits, argv[iarg], SZ_STR - 1);
+
+          if (unpack) {
+	  	/* ********** This section applies to funpack ************ */
+
+	      /* find input file */
+	      if (infits[0] != '-') {  /* if not reading from stdin stream */
+	         if (fp_access (infits) != 0) {  /* if not, then */
+		    strcat(infits, ".fz");       /* a .fz version must exsit */
+	         }
+	      }
+
+	      if (fpvar.to_stdout) {
+		strcpy(outfits, "-");
+
+              } else if (fpvar.outfile[0]) {  /* user specified output file name */
+	          strcpy(outfits, fpvar.outfile);
+
+	      } else {
+	          /* construct output file name */
+	          if (fpvar.prefix[0]) {
+	              strcat(outfits,fpvar.prefix);
+	          }
+
+	          /* construct output file name */
+	          if (infits[0] == '-') {
+	            strcpy(outfits, "output.fits");
+	          } else {
+	            strcpy(outfits, infits);
+	          }
+
+	          /* remove .gz suffix, if present (output is not gzipped) */
+                  namelen = strlen(outfits);
+	          if ( !strcmp(".gz", outfits + namelen - 3) ) {
+                        outfits[namelen - 3] = '\0';
+	          }
+
+	          /* check for .fz suffix that is sometimes required */
+	          /* and remove it if present */
+                  namelen = strlen(outfits);
+	          if ( !strcmp(".fz", outfits + namelen - 3) ) { /* suffix is present */
+                        outfits[namelen - 3] = '\0';
+	          }
+	      }
+
+	  } else {
+	  	/* ********** This section applies to fpack ************ */
+
+	      if (fpvar.to_stdout) {
+		strcpy(outfits, "-");
+	      } else if (! fpvar.test_all) {
+
+	          /* construct output file name */
+	          if (infits[0] == '-') {
+	            strcpy(outfits, "input.fits");
+	          } else {
+	            strcpy(outfits, infits);
+	          }
+	      
+	          /* remove .gz suffix, if present (output is not gzipped) */
+                  namelen = strlen(outfits);
+	          if ( !strcmp(".gz", outfits + namelen - 3) ) {
+                        outfits[namelen - 3] = '\0';
+	          }
+	      
+	          /* remove .imh suffix (IRAF format image), and replace with .fits */
+                  namelen = strlen(outfits);
+	          if ( !strcmp(".imh", outfits + namelen - 4) ) {
+                        outfits[namelen - 4] = '\0';
+                        strcat(outfits, ".fits");
+                        iraf_infile = 1;  /* this is an IRAF format input file */
+			           /* change the output name to "NAME.fits.fz" */
+	          }
+
+	          /* If not clobbering the input file, add .fz suffix to output name */
+	          if (! fpvar.clobber)
+		        strcat(outfits, ".fz");
+	      }
+	  }
+
+          strncpy(temp, outfits, SZ_STR-1);
+
+	  if (infits[0] != '-') {  /* if not reading from stdin stream */
+             if (!strcmp(infits, outfits) ) {  /* are input and output names the same? */
+
+                /* clobber the input file with the output file with the same name */
+                if (! fpvar.clobber) {
+                    fp_msg ("\nError: must use -F flag to clobber input file.\n");
+		    exit (-1);
+		} 
+
+                /* create temporary file name in the output directory (same as input directory)*/
+		fp_tmpnam("Tmp1", infits, outfits);
+		
+                strcpy(tempfilename, outfits);  /* store temp file name, in case of abort */
+	      }
+	    }
+
+
+            /* *************** now do the real work ********************* */
+	    
+	    if (fpvar.verbose && ! fpvar.to_stdout)
+		printf("%s ", infits);
+		
+	    if (fpvar.test_all) {   /* compare all the algorithms */
+
+                /* create 2 temporary file names, in the CWD */
+		fp_tmpnam("Tmpfile1", "", tempfilename);
+		fp_tmpnam("Tmpfile2", "", tempfilename2);
+
+		fp_test (infits, tempfilename, tempfilename2, fpvar);
+
+		remove(tempfilename);
+                tempfilename[0] = '\0';   /* clear the temp file name */
+		remove(tempfilename2);
+                tempfilename2[0] = '\0';
+                continue;
+
+	    } else if (unpack) {
+		if (fpvar.to_stdout) {
+			/* unpack the input file to the stdout stream */
+			fp_unpack (infits, outfits, fpvar);
+		} else {
+			/* unpack to temporary file, so other tasks can't open it until it is renamed */
+
+			/* create  temporary file name, in the output directory */
+			fp_tmpnam("Tmp2", outfits, tempfilename2);
+
+			/* unpack the input file to the temporary file */
+			fp_unpack (infits, tempfilename2, fpvar);
+
+			/* rename the temporary file to it's real name */
+			ifail = rename(tempfilename2, outfits);
+			if (ifail) {
+			    fp_msg("Failed to rename temporary file name:\n  ");
+			    fp_msg(tempfilename2);
+			    fp_msg(" -> ");
+			    fp_msg(outfits);
+			    fp_msg("\n");
+			    exit (-1);
+			} else {
+			    tempfilename2[0] = '\0';  /* clear temporary file name */
+			}
+		}
+	    }  else {
+		fp_pack (infits, outfits, fpvar, &islossless);
+	    }
+
+	    if (fpvar.to_stdout) {
+		continue;
+	    }
+
+            /* ********** clobber and/or delete files, if needed ************** */
+
+            if (!strcmp(infits, temp) && fpvar.clobber ) {  
+
+		if (!islossless && ! fpvar.do_not_prompt) {
+		    fp_msg ("\nFile ");
+		    fp_msg (infits); 
+		    fp_msg ("\nwas compressed with a LOSSY method.  Overwrite the\n");
+		    fp_msg ("original file with the compressed version? (Y/N) ");
+		    fgets(answer, 29, stdin);
+		    if (answer[0] != 'Y' && answer[0] != 'y') {
+		        fp_msg ("\noriginal file NOT overwritten!\n");
+			remove(outfits);
+                        continue;
+		    }
+		}
+ 
+	        /* rename clobbers input, may be unix/shell version dependent */
+
+		if (iraf_infile) {  /* special case of deleting an IRAF format header and pixel file */
+		   if (fits_delete_iraf_file(infits, &status)) {
+		        fp_msg("\nError deleting IRAF .imh and .pix files.\n");
+			fp_msg(infits); fp_msg ("\n"); exit (-1);
+		    }
+		}
+				
+		if (rename (outfits, temp) != 0) {
+		        fp_msg ("\nError renaming tmp file to ");
+		        fp_msg (temp); fp_msg ("\n"); exit (-1);
+		}
+		tempfilename[0] = '\0';  /* clear temporary file name */
+                strcpy(outfits, temp);
+
+	    } else if (fpvar.clobber || fpvar.delete_input) {      /* delete the input file */
+	         if (!islossless && !fpvar.do_not_prompt) {  /* user did not turn off delete prompt */
+		    fp_msg ("\nFile ");
+		    fp_msg (infits); 
+		    fp_msg ("\nwas compressed with a LOSSY method.  \n");
+		    fp_msg ("Delete the original file? (Y/N) ");
+		    fgets(answer, 29, stdin);
+		    if (answer[0] != 'Y' && answer[0] != 'y') {  /* user abort */
+		        fp_msg ("\noriginal file NOT deleted!\n");
+		    } else {
+			if (iraf_infile) {  /* special case of deleting an IRAF format header and pixel file */
+		   	    if (fits_delete_iraf_file(infits, &status)) {
+		        	fp_msg("\nError deleting IRAF .imh and .pix files.\n");
+				fp_msg(infits); fp_msg ("\n"); exit (-1);
+			    }
+		        }  else if (remove(infits) != 0) {  /* normal case of deleting input FITS file */
+		            fp_msg ("\nError deleting input file ");
+		            fp_msg (infits); fp_msg ("\n"); exit (-1);
+		        }
+		    }
+		  } else {   /* user said don't prompt, so just delete the input file */
+			if (iraf_infile) {  /* special case of deleting an IRAF format header and pixel file */
+		   	    if (fits_delete_iraf_file(infits, &status)) {
+		        	fp_msg("\nError deleting IRAF .imh and .pix files.\n");
+				fp_msg(infits); fp_msg ("\n"); exit (-1);
+			    }
+		        }  else if (remove(infits) != 0) {  /* normal case of deleting input FITS file */
+		            fp_msg ("\nError deleting input file ");
+		            fp_msg (infits); fp_msg ("\n"); exit (-1);
+		        }
+		  }
+	    }
+            iraf_infile = 0; 
+
+	    if (fpvar.do_gzip_file) {       /* gzip the output file */
+		strcpy(temp, "gzip -1 ");
+		strcat(temp,outfits);
+                system(temp);
+	        strcat(outfits, ".gz");    /* only possibible with funpack */
+	    }
+
+	    if (fpvar.verbose && ! fpvar.to_stdout)
+		printf("-> %s\n", outfits);
+
+	}
+
+	if (fpvar.test_all && fpvar.outfile[0])
+	    fclose(outreport);
+	return(0);
+}
+
+/*--------------------------------------------------------------------------*/
+/* fp_pack assumes the output file does not exist (checked by preflight)
+ */
+int fp_pack (char *infits, char *outfits, fpstate fpvar, int *islossless)
+{
+	fitsfile *infptr, *outfptr;
+	int	stat=0;
+
+	fits_open_file (&infptr, infits, READONLY, &stat);
+	if (stat) { fits_report_error (stderr, stat); exit (stat); }
+
+	fits_create_file (&outfptr, outfits, &stat);
+	if (stat) { 
+	    fp_abort_output(infptr, NULL, stat);
+	}
+
+	fits_set_compression_type (outfptr, fpvar.comptype, &stat);
+	fits_set_lossy_int (outfptr, fpvar.int_to_float, &stat);
+
+	if (fpvar.no_dither)
+	    fits_set_quantize_dither(outfptr, -1, &stat);
+	    
+	fits_set_quantize_level (outfptr, fpvar.quantize_level, &stat);
+	fits_set_hcomp_scale (outfptr, fpvar.scale, &stat);
+	fits_set_hcomp_smooth (outfptr, fpvar.smooth, &stat);
+	fits_set_tile_dim (outfptr, 6, fpvar.ntile, &stat);
+	fits_set_dither_offset(outfptr, fpvar.dither_offset, &stat);
+
+	if (stat) { 
+	    fp_abort_output(infptr, outfptr, stat);
+	}
+
+	
+	while (! stat) {
+
+	    /* the lossy_int value may have changed, so reset it for each HDU */
+	    fits_set_lossy_int (outfptr, fpvar.int_to_float, &stat);
+
+	    fp_pack_hdu (infptr, outfptr, fpvar, islossless, &stat);
+
+	    if (fpvar.do_checksums) {
+	        fits_write_chksum (outfptr, &stat);
+	    }
+
+	    fits_movrel_hdu (infptr, 1, NULL, &stat);
+	}
+
+	if (stat == END_OF_FILE) stat = 0;
+
+	/* set checksum for case of newly created primary HDU
+	 */
+	if (fpvar.do_checksums) {
+	    fits_movabs_hdu (outfptr, 1, NULL, &stat);
+	    fits_write_chksum (outfptr, &stat);
+	}
+
+	if (stat) { 
+	    fp_abort_output(infptr, outfptr, stat);
+	}
+
+	fits_close_file (outfptr, &stat);
+	fits_close_file (infptr, &stat);
+
+	return(0);
+}
+
+/*--------------------------------------------------------------------------*/
+/* fp_unpack assumes the output file does not exist
+ */
+int fp_unpack (char *infits, char *outfits, fpstate fpvar)
+{
+        fitsfile *infptr, *outfptr;
+        int stat=0, hdutype, extnum, single = 0;
+	char *loc, *hduloc, hduname[SZ_STR];
+
+        fits_open_file (&infptr, infits, READONLY, &stat);
+        fits_create_file (&outfptr, outfits, &stat);
+
+        if (fpvar.extname[0]) {  /* unpack a list of HDUs? */
+
+            /* move to the first HDU in the list */
+	    hduloc = fpvar.extname;
+	    loc = strchr(hduloc, ','); /* look for 'comma' delimiter between names */
+	    
+	    if (loc)         
+	        *loc = '\0';  /* terminate the first name in the string */
+
+	    strcpy(hduname, hduloc);  /* copy the first name into temporary string */
+
+	    if (loc)        
+	        hduloc = loc + 1;  /* advance to the beginning of the next name, if any */
+            else {
+	        hduloc += strlen(hduname);  /* end of the list */
+	        single = 1;  /* only 1 HDU is being unpacked */
+            }
+
+            if (isdigit( (int) hduname[0]) ) {
+	       extnum = strtol(hduname, &loc, 10); /* read the string as an integer */
+
+               /* check for junk following the integer */
+               if (*loc == '\0' )  /* no junk, so move to this HDU number (+1) */
+               {	       
+                  fits_movabs_hdu(infptr, extnum + 1, &hdutype, &stat);  /* move to HDU number */
+                  if (hdutype != IMAGE_HDU)
+		     stat = NOT_IMAGE;
+
+               } else {  /* the string is not an integer, so must be the column name */
+	          hdutype = IMAGE_HDU;
+	          fits_movnam_hdu(infptr, hdutype, hduname, 0, &stat);
+ 	       }
+            }
+	    else
+	    {
+                /* move to the named image extension */
+  	        hdutype = IMAGE_HDU;
+	        fits_movnam_hdu(infptr, hdutype, hduname, 0, &stat);
+            }
+        }
+
+        if (stat) {
+	  fp_msg ("Unable to find and move to extension '");
+          fp_msg(hduname);
+	  fp_msg("'\n");
+	  fp_abort_output(infptr, outfptr, stat);
+        }
+
+        while (! stat) {
+	
+	    if (single)
+	        stat = -1;  /* special status flag to force output primary array */
+
+            fp_unpack_hdu (infptr, outfptr, fpvar, &stat);
+
+	    if (fpvar.do_checksums) {
+	        fits_write_chksum (outfptr, &stat);
+	    }
+
+	    /* move to the next HDU */
+            if (fpvar.extname[0]) {  /* unpack a list of HDUs? */
+
+                if (!(*hduloc)) {
+		    stat = END_OF_FILE;  /* we reached the end of the list */
+		} else {
+		    /* parse the next HDU name and move to it */
+	            loc = strchr(hduloc, ',');
+	    
+	            if (loc)         /* look for 'comma' delimiter between names */
+	               *loc = '\0';  /* terminate the first name in the string */
+
+	            strcpy(hduname, hduloc);  /* copy the next name into temporary string */
+
+	            if (loc)         
+	                hduloc = loc + 1;  /* advance to the beginning of the next name, if any */
+                    else
+	               *hduloc = '\0';  /* end of the list */
+
+                    if (isdigit( (int) hduname[0]) ) {
+	               extnum = strtol(hduname, &loc, 10); /* read the string as an integer */
+
+                      /* check for junk following the integer */
+                      if (*loc == '\0' )   /* no junk, so move to this HDU number (+1) */
+		      {	       
+                        fits_movabs_hdu(infptr, extnum + 1, &hdutype, &stat);  /* move to HDU number */
+                        if (hdutype != IMAGE_HDU)
+		        stat = NOT_IMAGE;
+
+                      } else {  /* the string is not an integer, so must be the column name */
+	                hdutype = IMAGE_HDU;
+	                fits_movnam_hdu(infptr, hdutype, hduname, 0, &stat);
+ 	              }
+
+                    } else {
+                      /* move to the named image extension */
+  	              hdutype = IMAGE_HDU;
+	              fits_movnam_hdu(infptr, hdutype, hduname, 0, &stat);
+                   }
+
+                   if (stat) {
+	              fp_msg ("Unable to find and move to extension '");
+                      fp_msg(hduname);
+	              fp_msg("'\n");
+                   }
+                }
+            } else {
+                /* increment to the next HDU */
+                fits_movrel_hdu (infptr, 1, NULL, &stat);
+            }
+        }
+
+        if (stat == END_OF_FILE) stat = 0;
+
+        /* set checksum for case of newly created primary HDU
+         */
+	if (fpvar.do_checksums) {
+	    fits_movabs_hdu (outfptr, 1, NULL, &stat);
+	    fits_write_chksum (outfptr, &stat);
+	}
+
+
+	if (stat) { 
+	    fp_abort_output(infptr, outfptr, stat);
+	}
+
+	fits_close_file (outfptr, &stat);
+	fits_close_file (infptr, &stat);
+
+	return(0);
+}
+
+/*--------------------------------------------------------------------------*/
+/* fp_test assumes the output files do not exist
+ */
+int fp_test (char *infits, char *outfits, char *outfits2, fpstate fpvar)
+{
+	fitsfile *inputfptr, *infptr, *outfptr, *outfptr2, *tempfile;
+
+	long	naxes[9] = {1, 1, 1, 1, 1, 1, 1, 1, 1};
+	long	tilesize[9] = {0,1,1,1,1,1,1,1,1};
+	int	stat=0, totpix=0, naxis=0, ii, hdutype, bitpix, extnum = 0, len;
+	int     tstatus = 0, hdunum, rescale_flag, bpix;
+	char	dtype[8], dimen[100];
+	double  bscale, rescale, noisemin;
+	long headstart, datastart, dataend;
+	float origdata = 0., whole_cpu, whole_elapse, row_elapse, row_cpu, xbits;
+	FILE	*diskfile;
+	/* structure to hold image statistics (defined in fpack.h) */
+	imgstats imagestats;
+
+	fits_open_file (&inputfptr, infits, READONLY, &stat);
+	fits_create_file (&outfptr, outfits, &stat);
+	fits_create_file (&outfptr2, outfits2, &stat);
+
+	if (stat) { fits_report_error (stderr, stat); exit (stat); }
+
+	if (fpvar.no_dither)
+	    fits_set_quantize_dither(outfptr, -1, &stat);
+
+	fits_set_quantize_level (outfptr, fpvar.quantize_level, &stat);
+	fits_set_hcomp_scale (outfptr, fpvar.scale, &stat);
+	fits_set_hcomp_smooth (outfptr, fpvar.smooth, &stat);
+	fits_set_tile_dim (outfptr, 6, fpvar.ntile, &stat);
+	fits_set_dither_offset(outfptr, fpvar.dither_offset, &stat);
+
+	while (! stat) {
+	
+	    rescale_flag = 0;
+	    
+	    /*  LOOP OVER EACH HDU */
+	    fits_get_hdu_type (inputfptr, &hdutype, &stat);
+
+	    if (hdutype == IMAGE_HDU) {
+	        fits_get_img_param (inputfptr, 9, &bitpix, &naxis, naxes, &stat);
+	        for (totpix=1, ii=0; ii < 9; ii++) totpix *= naxes[ii];
+	    }
+
+	    if ( !fits_is_compressed_image (inputfptr,  &stat) &&
+		hdutype == IMAGE_HDU && naxis != 0 && totpix != 0) {
+
+		/* rescale a scaled integer image to reduce noise? */
+		if (fpvar.rescale_noise != 0. && bitpix > 0 && bitpix < LONGLONG_IMG) {
+
+		   tstatus = 0;
+		   fits_read_key(inputfptr, TDOUBLE, "BSCALE", &bscale, 0, &tstatus);
+
+		   if (tstatus == 0 && bscale != 1.0) {  /* image must be scaled */
+
+			if (bitpix == LONG_IMG)
+			  fp_i4stat(inputfptr, naxis, naxes, &imagestats, &stat);
+			else
+			  fp_i2stat(inputfptr, naxis, naxes, &imagestats, &stat);
+
+			/* use the minimum of the MAD 2nd, 3rd, and 5th order noise estimates */
+			noisemin = imagestats.noise3;
+			if (imagestats.noise2 != 0. && imagestats.noise2 < noisemin) noisemin = imagestats.noise2;
+			if (imagestats.noise5 != 0. && imagestats.noise5 < noisemin) noisemin = imagestats.noise5;
+
+			rescale = noisemin / fpvar.rescale_noise;
+			if (rescale > 1.0) {
+			  
+			  /* all the criteria are met, so create a temporary file that */
+			  /* contains a rescaled version of the image, in CWD */
+			  
+                	  /* create temporary file name */
+			  fp_tmpnam("Tmpfile3", "", tempfilename3);
+
+			  fits_create_file(&tempfile, tempfilename3, &stat);
+
+			  fits_get_hdu_num(inputfptr, &hdunum);
+			  if (hdunum != 1) {
+
+			     /* the input hdu is an image extension, so create dummy primary */
+			     fits_create_img(tempfile, 8, 0, naxes, &stat);
+			  }
+
+			  fits_copy_header(inputfptr, tempfile, &stat); /* copy the header */
+			  
+			  /* rescale the data, so that it will compress more efficiently */
+			  if (bitpix == LONG_IMG)
+			    fp_i4rescale(inputfptr, naxis, naxes, rescale, tempfile, &stat);
+			  else
+			    fp_i2rescale(inputfptr, naxis, naxes, rescale, tempfile, &stat);
+
+			  /* scale the BSCALE keyword by the inverse factor */
+
+			  bscale = bscale * rescale;  
+			  fits_update_key(tempfile, TDOUBLE, "BSCALE", &bscale, 0, &stat);
+
+			  /* rescan the header, to reset the actual scaling parameters */
+			  fits_set_hdustruc(tempfile, &stat);
+
+			  infptr = tempfile;
+			  rescale_flag = 1;
+			}
+		   }
+		 }
+
+		if (!rescale_flag)   /* just compress the input file, without rescaling */
+		   infptr = inputfptr;
+
+		/* compute basic statistics about the input image */
+                if (bitpix == BYTE_IMG) {
+		   bpix = 8;
+		   strcpy(dtype, "8  ");
+		   fp_i2stat(infptr, naxis, naxes, &imagestats, &stat);
+		} else if (bitpix == SHORT_IMG) {
+		   bpix = 16;
+		   strcpy(dtype, "16 ");
+		   fp_i2stat(infptr, naxis, naxes, &imagestats, &stat);
+		} else if (bitpix == LONG_IMG) {
+		   bpix = 32;
+		   strcpy(dtype, "32 ");
+		   fp_i4stat(infptr, naxis, naxes, &imagestats, &stat);
+		} else if (bitpix == LONGLONG_IMG) {
+		   bpix = 64;
+		   strcpy(dtype, "64 ");
+		} else if (bitpix == FLOAT_IMG)   {
+		   bpix = 32;
+		   strcpy(dtype, "-32");
+		   fp_r4stat(infptr, naxis, naxes, &imagestats, &stat);
+		} else if (bitpix == DOUBLE_IMG)  {
+		   bpix = 64;
+		   strcpy(dtype, "-64");
+		   fp_r4stat(infptr, naxis, naxes, &imagestats, &stat);
+		}
+
+		/* use the minimum of the MAD 2nd, 3rd, and 5th order noise estimates */
+		noisemin = imagestats.noise3;
+		if (imagestats.noise2 != 0. && imagestats.noise2 < noisemin) noisemin = imagestats.noise2;
+		if (imagestats.noise5 != 0. && imagestats.noise5 < noisemin) noisemin = imagestats.noise5;
+
+                xbits = log10(noisemin)/.301 + 1.792;
+
+		printf("\n File: %s\n", infits);
+		printf("  Ext BITPIX Dimens.   Nulls    Min    Max     Mean    Sigma  Noise2  Noise3  Noise5  Nbits   MaxR\n");
+
+		printf("  %3d  %s", extnum, dtype);
+		sprintf(dimen," (%ld", naxes[0]);
+		len =strlen(dimen);
+		for (ii = 1; ii < naxis; ii++) {
+		    sprintf(dimen+len,",%ld", naxes[ii]);
+		    len =strlen(dimen);
+		}
+		strcat(dimen, ")");
+		printf("%-12s",dimen);
+
+		fits_get_hduaddr(inputfptr, &headstart, &datastart, &dataend, &stat);
+		origdata = (dataend - datastart)/1000000.;
+
+		/* get elapsed and cpu times need to read the uncompressed image */
+		fits_read_image_speed (infptr, &whole_elapse, &whole_cpu,
+		               &row_elapse, &row_cpu, &stat);
+
+		printf(" %5d %6.0f %6.0f %8.1f %#8.2g %#7.3g %#7.3g %#7.3g %#5.1f %#6.2f\n",
+		        imagestats.n_nulls, imagestats.minval, imagestats.maxval, 
+		      imagestats.mean, imagestats.sigma, 
+		      imagestats.noise2, imagestats.noise3, imagestats.noise5, xbits, bpix/xbits);
+
+		printf("\n       Type   Ratio       Size (MB)     Pk (Sec) UnPk Exact ElpN CPUN  Elp1  CPU1\n");
+
+		printf("       Native                                             %5.3f %5.3f %5.3f %5.3f\n",
+		        whole_elapse, whole_cpu, row_elapse, row_cpu);
+
+		if (fpvar.outfile[0]) {
+		    fprintf(outreport,
+	" %s  %d %d %ld %ld %#10.4g %d %#10.4g %#10.4g %#10.4g %#10.4g %#10.4g %#10.4g %#10.4g %#10.4g %#10.4g %#10.4g %#10.4g %#10.4g",
+		      infits, extnum, bitpix, naxes[0], naxes[1], origdata, imagestats.n_nulls, imagestats.minval, 
+		      imagestats.maxval, imagestats.mean, imagestats.sigma, 
+		      imagestats.noise1, imagestats.noise2, imagestats.noise3, imagestats.noise5, whole_elapse, whole_cpu, row_elapse, row_cpu);
+		}
+
+		fits_set_lossy_int (outfptr, fpvar.int_to_float, &stat);
+		if ( (bitpix > 0) && (fpvar.int_to_float != 0) ) {
+
+			if ( (noisemin < (fpvar.n3ratio * fpvar.quantize_level) ) ||
+			    (noisemin < fpvar.n3min)) {
+			
+			    /* image contains too little noise to quantize effectively */
+			    fits_set_lossy_int (outfptr, 0, &stat);
+			    fits_get_hdu_num(infptr, &hdunum);
+
+printf("    HDU %d does not meet noise criteria to be quantized, so losslessly compressed.\n", hdunum);
+			} 
+		}
+
+		/* test compression ratio and speed for each algorithm */
+
+		if (fpvar.quantize_level != 0) {
+		  fits_set_compression_type (outfptr, RICE_1, &stat);
+		  fits_set_tile_dim (outfptr, 6, fpvar.ntile, &stat);
+		  fp_test_hdu(infptr, outfptr, outfptr2, fpvar, &stat);
+		}
+
+		if (fpvar.quantize_level != 0) {
+  		  fits_set_compression_type (outfptr, HCOMPRESS_1, &stat);
+		  fits_set_tile_dim (outfptr, 6, fpvar.ntile, &stat);
+		  fp_test_hdu(infptr, outfptr, outfptr2, fpvar, &stat);
+		}
+
+		if (fpvar.comptype == GZIP_2) {
+		    fits_set_compression_type (outfptr, GZIP_2, &stat);
+		} else {
+		    fits_set_compression_type (outfptr, GZIP_1, &stat);
+		}
+		fits_set_tile_dim (outfptr, 6, fpvar.ntile, &stat);
+		fp_test_hdu(infptr, outfptr, outfptr2, fpvar, &stat);
+
+/*
+		fits_set_compression_type (outfptr, BZIP2_1, &stat);
+		fits_set_tile_dim (outfptr, 6, fpvar.ntile, &stat);
+		fp_test_hdu(infptr, outfptr, outfptr2, fpvar, &stat);
+*/
+/*
+		fits_set_compression_type (outfptr, PLIO_1, &stat);
+		fits_set_tile_dim (outfptr, 6, fpvar.ntile, &stat);
+		fp_test_hdu(infptr, outfptr, outfptr2, fpvar, &stat);
+*/
+/*
+                if (bitpix == SHORT_IMG || bitpix == LONG_IMG) {
+		  fits_set_compression_type (outfptr, NOCOMPRESS, &stat);
+		  fits_set_tile_dim (outfptr, 6, fpvar.ntile, &stat);
+		  fp_test_hdu(infptr, outfptr, outfptr2, fpvar, &stat);	  
+		}
+*/
+		if (fpvar.outfile[0])
+		    fprintf(outreport,"\n");
+
+		/* delete the temporary file */
+		if (rescale_flag)  { 
+		    fits_delete_file (infptr, &stat);
+		    tempfilename3[0] = '\0';   /* clear the temp filename */ 
+                }
+	    } else if ( (hdutype == BINARY_TBL) && fpvar.do_tables) {
+
+		printf("\n File: %s\n", infits);
+		fp_test_table(inputfptr, outfptr, outfptr2, fpvar, &stat);	  
+
+	    } else {
+		fits_copy_hdu (inputfptr, outfptr, 0, &stat);
+		fits_copy_hdu (inputfptr, outfptr2, 0, &stat);
+	    }
+
+	    fits_movrel_hdu (inputfptr, 1, NULL, &stat);
+            extnum++;
+	}
+
+
+	if (stat == END_OF_FILE) stat = 0;
+
+	fits_close_file (outfptr2, &stat);
+	fits_close_file (outfptr, &stat);
+	fits_close_file (inputfptr, &stat);
+
+	if (stat) {
+	  fits_report_error (stderr, stat);
+	}
+	return(0);
+}
+/*--------------------------------------------------------------------------*/
+int fp_pack_hdu (fitsfile *infptr, fitsfile *outfptr, fpstate fpvar,
+   int *islossless, int *status)
+{
+	fitsfile *tempfile;
+	long	naxes[9] = {1, 1, 1, 1, 1, 1, 1, 1, 1};
+	int	stat=0, totpix=0, naxis=0, ii, hdutype, bitpix;
+	int	tstatus, hdunum, rescale_flag = 0;
+	double  bscale, rescale;
+	FILE	*diskfile;
+	char	outfits[SZ_STR];
+	long 	headstart, datastart, dataend, datasize;
+	double  noisemin;
+	/* structure to hold image statistics (defined in fpack.h) */
+	imgstats imagestats;
+
+	if (*status) return(0);
+
+	fits_get_hdu_type (infptr, &hdutype, &stat);
+
+	if (hdutype == IMAGE_HDU) {
+	    fits_get_img_param (infptr, 9, &bitpix, &naxis, naxes, &stat);
+	    for (totpix=1, ii=0; ii < 9; ii++) totpix *= naxes[ii];
+	}
+
+        /* =============================================================== */
+        /* This block is only for beta testing of binary table compression */
+	if (hdutype == BINARY_TBL && fpvar.do_tables) { 
+
+	    fits_get_hduaddr(infptr, &headstart, &datastart, &dataend, status); 
+	    datasize = dataend - datastart;
+
+	    if (datasize <= 2880) {
+		/* data is less than 1 FITS block in size, so don't compress */
+	        fits_copy_hdu (infptr, outfptr, 0, &stat);
+	    } else {
+
+	        /* transpose the table and compress each column */
+		if (fpvar.do_fast) {
+		    fits_compress_table_fast (infptr, outfptr, &stat);
+		} else {
+		    fits_compress_table_best (infptr, outfptr, &stat);
+		}
+	    }
+
+	    return(0);
+	}
+        /* =============================================================== */
+
+        /* If this is not a non-null image HDU, just copy it verbatim */
+	if (fits_is_compressed_image (infptr,  &stat) ||
+	    hdutype != IMAGE_HDU || naxis == 0 || totpix == 0) {
+	    fits_copy_hdu (infptr, outfptr, 0, &stat);
+
+	} else {  /* remaining code deals only with IMAGE HDUs */
+
+		/* special case: rescale a scaled integer image to reduce noise? */
+		if (fpvar.rescale_noise != 0. && bitpix > 0 && bitpix < LONGLONG_IMG) {
+
+		   tstatus = 0;
+		   fits_read_key(infptr, TDOUBLE, "BSCALE", &bscale, 0, &tstatus);
+		   if (tstatus == 0 && bscale != 1.0) {  /* image must be scaled */
+
+			if (bitpix == LONG_IMG)
+			  fp_i4stat(infptr, naxis, naxes, &imagestats, &stat);
+			else
+			  fp_i2stat(infptr, naxis, naxes, &imagestats, &stat);
+
+			/* use the minimum of the MAD 2nd, 3rd, and 5th order noise estimates */
+			noisemin = imagestats.noise3;
+			if (imagestats.noise2 != 0. && imagestats.noise2 < noisemin) noisemin = imagestats.noise2;
+			if (imagestats.noise5 != 0. && imagestats.noise5 < noisemin) noisemin = imagestats.noise5;
+
+			rescale = noisemin / fpvar.rescale_noise;
+			if (rescale > 1.0) {
+			  
+			  /* all the criteria are met, so create a temporary file that */
+			  /* contains a rescaled version of the image, in output directory */
+			  
+			  /* create temporary file name */
+			  fits_file_name(outfptr, outfits, &stat);  /* get the output file name */
+			  fp_tmpnam("Tmp3", outfits, tempfilename3);
+
+			  fits_create_file(&tempfile, tempfilename3, &stat);
+
+			  fits_get_hdu_num(infptr, &hdunum);
+			  if (hdunum != 1) {
+
+			     /* the input hdu is an image extension, so create dummy primary */
+			     fits_create_img(tempfile, 8, 0, naxes, &stat);
+			  }
+
+			  fits_copy_header(infptr, tempfile, &stat); /* copy the header */
+			  
+			  /* rescale the data, so that it will compress more efficiently */
+			  if (bitpix == LONG_IMG)
+			    fp_i4rescale(infptr, naxis, naxes, rescale, tempfile, &stat);
+			  else
+			    fp_i2rescale(infptr, naxis, naxes, rescale, tempfile, &stat);
+
+
+			  /* scale the BSCALE keyword by the inverse factor */
+
+			  bscale = bscale * rescale;  
+			  fits_update_key(tempfile, TDOUBLE, "BSCALE", &bscale, 0, &stat);
+
+			  /* rescan the header, to reset the actual scaling parameters */
+			  fits_set_hdustruc(tempfile, &stat);
+
+		          fits_img_compress (tempfile, outfptr, &stat);
+		          fits_delete_file  (tempfile, &stat);
+		          tempfilename3[0] = '\0';   /* clear the temp filename */
+		          *islossless = 0;  /* used a lossy compression method */
+
+	                  *status = stat;
+	                  return(0);
+			}
+		   }
+		}
+
+		/* if requested to do lossy compression of integer images (by */
+		/* converting to float), then check if this HDU qualifies */
+		if ( (bitpix > 0) && (fpvar.int_to_float != 0) ) {
+		    
+			if (bitpix >= LONG_IMG)
+			  fp_i4stat(infptr, naxis, naxes, &imagestats, &stat);
+			else
+			  fp_i2stat(infptr, naxis, naxes, &imagestats, &stat);
+
+			/* use the minimum of the MAD 2nd, 3rd, and 5th order noise estimates */
+			noisemin = imagestats.noise3;
+			if (imagestats.noise2 != 0. && imagestats.noise2 < noisemin) noisemin = imagestats.noise2;
+			if (imagestats.noise5 != 0. && imagestats.noise5 < noisemin) noisemin = imagestats.noise5;
+
+			if ( (noisemin < (fpvar.n3ratio * fpvar.quantize_level) ) ||
+			    (imagestats.noise3 < fpvar.n3min)) {
+			
+			    /* image contains too little noise to quantize effectively */
+			    fits_set_lossy_int (outfptr, 0, &stat);
+
+			    fits_get_hdu_num(infptr, &hdunum);
+
+printf("    HDU %d does not meet noise criteria to be quantized, so losslessly compressed.\n", hdunum);
+
+			} else {
+			    /* compressed image is not identical to original */
+			    *islossless = 0;
+			}  
+		}
+
+                /* finally, do the actual image compression */
+		fits_img_compress (infptr, outfptr, &stat);
+
+		if (bitpix < 0 || 
+		    (fpvar.comptype == HCOMPRESS_1 && fpvar.scale != 0.)) {
+
+		    /* compressed image is not identical to original */
+		    *islossless = 0;  
+		}
+	}
+
+	*status = stat;
+	return(0);
+}
+
+/*--------------------------------------------------------------------------*/
+int fp_unpack_hdu (fitsfile *infptr, fitsfile *outfptr, fpstate fpvar, int *status)
+{
+	int hdutype, lval;
+
+        if (*status > 0) return(0);
+
+	fits_get_hdu_type (infptr, &hdutype, status);
+
+        /* =============================================================== */
+        /* This block is only for beta testing of binary table compression */
+	if (hdutype == BINARY_TBL) { 
+
+	    fits_read_key(infptr, TLOGICAL, "ZTABLE", &lval, NULL, status);
+	    
+	    if (*status == 0 && lval != 0) {
+	        /*  uncompress the table */
+	        fits_uncompress_table (infptr, outfptr, status);
+
+	    } else {
+	        if (*status == KEY_NO_EXIST)  /* table is not compressed */
+		    *status = 0;
+                fits_copy_hdu (infptr, outfptr, 0, status);
+            }
+
+	    return(0);
+        /* =============================================================== */
+
+	} else if (fits_is_compressed_image (infptr,  status)) {
+            /* uncompress the compressed image HDU */
+            fits_img_decompress (infptr, outfptr, status);
+        } else {
+            /* not a compressed image HDU, so just copy it to the output */
+            fits_copy_hdu (infptr, outfptr, 0, status);
+        }
+
+	return(0);
+}
+/*--------------------------------------------------------------------------*/
+int fits_read_image_speed (fitsfile *infptr, float *whole_elapse, 
+    float *whole_cpu, float *row_elapse, float *row_cpu, int *status)
+{
+        unsigned char *carray, cnull = 0;
+	short *sarray, snull=0;
+	int bitpix, naxis, anynull, *iarray, inull = 0;
+	long ii, naxes[9], fpixel[9]={1,1,1,1,1,1,1,1,1}, lpixel[9]={1,1,1,1,1,1,1,1,1};
+	long inc[9]={1,1,1,1,1,1,1,1,1} ;
+	float *earray, enull = 0, filesize;
+	double *darray, dnull = 0;
+	LONGLONG fpixelll[9];
+	
+	if (*status) return(*status);
+
+	fits_get_img_param (infptr, 9, &bitpix, &naxis, naxes, status);
+
+	if (naxis != 2)return(*status);
+	
+	lpixel[0] = naxes[0];
+	lpixel[1] = naxes[1];
+	
+        /* filesize in MB */
+	filesize = naxes[0] * abs(bitpix) / 8000000. * naxes[1];
+
+	/* measure time required to read the raw image */
+	fits_set_bscale(infptr, 1.0, 0.0, status);
+	*whole_elapse = 0.;
+	*whole_cpu = 0;
+
+        if (bitpix == BYTE_IMG) {
+		carray = calloc(naxes[1]*naxes[0], sizeof(char));
+
+                /* remove any cached uncompressed tile 
+		  (dangerous to directly modify the structure!) */
+                (infptr->Fptr)->tilerow = 0;
+
+		marktime(status);
+		fits_read_subset(infptr, TBYTE, fpixel, lpixel, inc, &cnull, 
+		      carray, &anynull, status);
+		
+		/* get elapsped times */
+		gettime(whole_elapse, whole_cpu, status);
+
+		/* now read the image again, row by row */
+		if (row_elapse) {
+
+                  /* remove any cached uncompressed tile 
+	  	    (dangerous to directly modify the structure!) */
+                  (infptr->Fptr)->tilerow = 0;
+
+		  marktime(status);
+		  for (ii = 0; ii < naxes[1]; ii++) {
+		   fpixel[1] = ii+1;
+		   fits_read_pix(infptr, TBYTE, fpixel, naxes[0], &cnull, 
+		      carray, &anynull, status);
+		   }
+		   /* get elapsped times */
+		   gettime(row_elapse, row_cpu, status);
+		}
+ 		free(carray);
+ 
+	} else if (bitpix == SHORT_IMG) {
+		sarray = calloc(naxes[0]*naxes[1], sizeof(short));
+
+		marktime(status);
+
+		fits_read_subset(infptr, TSHORT, fpixel, lpixel, inc, &snull, 
+		      sarray, &anynull, status);
+
+		gettime(whole_elapse, whole_cpu, status);   /* get elapsped times */
+
+		/* now read the image again, row by row */
+		if (row_elapse) {
+		  marktime(status);
+		  for (ii = 0; ii < naxes[1]; ii++) {
+		   fpixel[1] = ii+1;
+		   fits_read_pix(infptr, TSHORT, fpixel, naxes[0], &snull, 
+		      sarray, &anynull, status);
+		  }
+		  /* get elapsped times */
+		  gettime(row_elapse, row_cpu, status);
+		}
+
+		free(sarray);	
+
+	} else if (bitpix == LONG_IMG) {
+		iarray = calloc(naxes[0]*naxes[1], sizeof(int));
+
+		marktime(status);
+
+		fits_read_subset(infptr, TINT, fpixel, lpixel, inc, &inull, 
+		      iarray, &anynull, status);
+		
+		/* get elapsped times */
+		gettime(whole_elapse, whole_cpu, status);
+
+
+		/* now read the image again, row by row */
+		if (row_elapse) {
+		  marktime(status);
+		  for (ii = 0; ii < naxes[1]; ii++) {
+		   fpixel[1] = ii+1;
+		   fits_read_pix(infptr, TINT, fpixel, naxes[0], &inull, 
+		      iarray, &anynull, status);
+		  }
+		  /* get elapsped times */
+		  gettime(row_elapse, row_cpu, status);
+		}
+
+
+ 		free(iarray);	
+
+	} else if (bitpix == FLOAT_IMG)   {
+		earray = calloc(naxes[1]*naxes[0], sizeof(float));
+
+		marktime(status);
+
+		fits_read_subset(infptr, TFLOAT, fpixel, lpixel, inc, &enull, 
+		      earray, &anynull, status);
+		
+		/* get elapsped times */
+		gettime(whole_elapse, whole_cpu, status);
+
+		/* now read the image again, row by row */
+		if (row_elapse) {
+		  marktime(status);
+		  for (ii = 0; ii < naxes[1]; ii++) {
+		   fpixel[1] = ii+1;
+		   fits_read_pix(infptr, TFLOAT, fpixel, naxes[0], &enull, 
+		      earray, &anynull, status);
+		  }
+		  /* get elapsped times */
+		  gettime(row_elapse, row_cpu, status);
+		}
+
+ 		free(earray);	
+
+	} else if (bitpix == DOUBLE_IMG)  {
+		darray = calloc(naxes[1]*naxes[0], sizeof(double));
+
+		marktime(status);
+
+		fits_read_subset(infptr, TDOUBLE, fpixel, lpixel, inc, &dnull, 
+		      darray, &anynull, status);
+		
+		/* get elapsped times */
+		gettime(whole_elapse, whole_cpu, status);
+
+		/* now read the image again, row by row */
+		if (row_elapse) {
+		  marktime(status);
+		  for (ii = 0; ii < naxes[1]; ii++) {
+		   fpixel[1] = ii+1;
+		   fits_read_pix(infptr, TDOUBLE, fpixel, naxes[0], &dnull, 
+		      darray, &anynull, status);
+		  }
+		  /* get elapsped times */
+		  gettime(row_elapse, row_cpu, status);
+		}
+
+		free(darray);
+	}
+
+        if (whole_elapse) *whole_elapse = *whole_elapse / filesize;
+        if (row_elapse)   *row_elapse   = *row_elapse / filesize;
+        if (whole_cpu)    *whole_cpu    = *whole_cpu / filesize;
+        if (row_cpu)      *row_cpu      = *row_cpu / filesize;
+
+	return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fp_test_hdu (fitsfile *infptr, fitsfile *outfptr, fitsfile *outfptr2, 
+	fpstate fpvar, int *status)
+{
+   /*   This routine is only used for performance testing of image HDUs. */
+   /*   Use fp_test_table for testing binary table HDUs.    */
+
+	int stat = 0, hdutype, comptype, noloss = 0;
+        char ctype[20], lossless[4];
+	long headstart, datastart, dataend;
+	float origdata = 0., compressdata = 0.;
+	float compratio = 0., packcpu = 0., unpackcpu = 0., readcpu;
+	float elapse, whole_elapse, row_elapse, whole_cpu, row_cpu;
+	unsigned long datasum1, datasum2, hdusum;
+
+	if (*status) return(0);
+
+	origdata = 0;
+	compressdata = 0;
+	compratio = 0.;
+	lossless[0] = '\0';
+
+	fits_get_compression_type(outfptr, &comptype, &stat);
+	if (comptype == RICE_1)
+		strcpy(ctype, "RICE");
+	else if (comptype == GZIP_1)
+		strcpy(ctype, "GZIP1");
+	else if (comptype == GZIP_2)
+		strcpy(ctype, "GZIP2");/*
+	else if (comptype == BZIP2_1)
+		strcpy(ctype, "BZIP2");
+*/
+	else if (comptype == PLIO_1)
+		strcpy(ctype, "PLIO");
+	else if (comptype == HCOMPRESS_1)
+		strcpy(ctype, "HCOMP");
+	else if (comptype == NOCOMPRESS)
+		strcpy(ctype, "NONE");
+	else {
+	      fp_msg ("Error: unsupported image compression type ");
+	      *status = DATA_COMPRESSION_ERR;
+	      return(0);
+	}
+
+	/* -------------- COMPRESS the image ------------------ */
+
+	marktime(&stat);
+	
+	fits_img_compress (infptr, outfptr, &stat);
+
+	/* get elapsped times */
+	gettime(&elapse, &packcpu, &stat);
+
+	/* get elapsed and cpu times need to read the compressed image */
+
+        /* if whole image is compressed as single tile, don't read row by row
+	   because it usually takes a very long time
+	*/
+        if (fpvar.ntile[1] == 0) {
+	  fits_read_image_speed (outfptr, &whole_elapse, &whole_cpu, 
+	   0, 0, &stat);
+	  row_elapse = 0; row_cpu = 0;
+	} else {
+
+	  fits_read_image_speed (outfptr, &whole_elapse, &whole_cpu, 
+	   &row_elapse, &row_cpu, &stat);
+	}
+
+        if (!stat) {
+
+		/* -------------- UNCOMPRESS the image ------------------ */
+
+                /* remove any cached uncompressed tile 
+		  (dangerous to directly modify the structure!) */
+                (outfptr->Fptr)->tilerow = 0;
+		marktime(&stat);
+
+ 	       fits_img_decompress (outfptr, outfptr2, &stat);
+
+		/* get elapsped times */
+		gettime(&elapse, &unpackcpu, &stat);
+
+		/* ----------------------------------------------------- */
+
+		/* get sizes of original and compressed images */
+
+		fits_get_hduaddr(infptr, &headstart, &datastart, &dataend, &stat);
+		origdata = (dataend - datastart)/1000000.;
+		
+		fits_get_hduaddr(outfptr, &headstart, &datastart, &dataend, &stat);
+		compressdata = (dataend - datastart)/1000000.;
+
+		if (compressdata != 0)
+			compratio = (float) origdata / (float) compressdata;
+
+		/* is this uncompressed image identical to the original? */
+
+		fits_get_chksum(infptr, &datasum1, &hdusum, &stat);	    
+		fits_get_chksum(outfptr2, &datasum2, &hdusum, &stat);
+
+		if ( datasum1 == datasum2) {
+			strcpy(lossless, "Yes");
+			noloss = 1;
+		} else {
+			strcpy(lossless, "No");
+		}
+
+		printf("       %-5s %6.2f %7.2f ->%7.2f %7.2f %7.2f %s %5.3f %5.3f %5.3f %5.3f\n", 
+			ctype, compratio, origdata, compressdata, 
+			packcpu, unpackcpu, lossless, whole_elapse, whole_cpu,
+			row_elapse, row_cpu);
+
+
+		if (fpvar.outfile[0]) {
+		    fprintf(outreport," %6.3f %5.2f %5.2f %s %7.3f %7.3f %7.3f %7.3f", 
+		       compratio, packcpu, unpackcpu, lossless,  whole_elapse, whole_cpu,
+		       row_elapse, row_cpu);
+		}
+
+ 	       /* delete the output HDUs to concerve disk space */
+
+		fits_delete_hdu(outfptr, &hdutype, &stat);
+		fits_delete_hdu(outfptr2, &hdutype, &stat);
+
+	} else {
+		printf("       %-5s     (unable to compress image)\n", ctype);
+	}
+
+	/* try to recover from any compression errors */
+	if (stat == DATA_COMPRESSION_ERR) stat = 0;
+
+	*status = stat;
+        return(0);
+}
+/*--------------------------------------------------------------------------*/
+int fp_test_table (fitsfile *infptr, fitsfile *outfptr, fitsfile *outfptr2, 
+	fpstate fpvar, int *status)
+{
+/* this routine is for performance testing of the beta table compression methods */
+
+	int stat = 0, hdutype, comptype, noloss = 0, ii;
+	unsigned int idatasize;
+        char ctype[20], lossless[4];
+	LONGLONG headstart, datastart, dataend, datasize;
+	float origdata = 0., compressdata = 0.;
+	float compratio = 0., packcpu = 0., unpackcpu = 0., readcpu;
+	float elapse, whole_elapse, row_elapse, whole_cpu, row_cpu;
+	float gratio, tratio, sratio, pratio, bratio;
+	float grate, trate, srate, prate, brate, filesize;
+	float rratio, rrate;
+	size_t headsize, hlen, dlen;
+	LONGLONG indatasize, outdatasize;
+	char *ptr, *cptr, *iptr, *cbuff;
+
+	if (*status) return(0);
+
+        fits_get_hduaddrll(infptr, &headstart, &datastart, &dataend, status); 
+	datasize = dataend - datastart;
+
+	/* can't compress small tables with less than 2880 bytes of data */
+        if (datasize <= 2880) {
+		return(0);
+	}
+
+     /* 1  gzip  raw table **********************************  */
+ 	marktime(&stat);
+	
+ 	/* get compressed size of the data blocks */
+	fits_gzip_datablocks(infptr, &dlen, &stat);
+
+	/* get elapsped times */
+	gettime(&elapse, &packcpu, &stat);
+
+        fits_get_hduaddrll(infptr, &headstart, &datastart, &dataend, status); 
+	indatasize = dataend - datastart;
+
+	outdatasize = dlen;
+
+	gratio = (float) indatasize / (float) outdatasize;
+	grate = packcpu;
+	
+	fits_delete_hdu(outfptr, &hdutype, &stat);
+
+     /* 2  transposed table and compress each column with gzip ***********  */
+
+ 	marktime(&stat);
+	fits_transpose_table (infptr, outfptr,  &stat);
+
+	/* get elapsped times */
+	gettime(&elapse, &packcpu, &stat);
+
+        fits_get_hduaddrll(infptr, &headstart, &datastart, &dataend, status); 
+	indatasize = dataend - datastart;
+	filesize = (float) dataend / 1000000.;
+	
+        fits_get_hduaddrll(outfptr, &headstart, &datastart, &dataend, status); 
+	outdatasize = dataend - datastart;
+
+	sratio = (float) indatasize / (float) outdatasize;
+	srate = packcpu;
+
+	fits_delete_hdu(outfptr, &hdutype, &stat);
+
+     /* 3  transpose table, shuffle numeric columns, and compress each column with gzip */
+
+ 	marktime(&stat);
+	fits_compress_table_fast (infptr, outfptr, &stat);
+
+	/* get elapsped times */
+	gettime(&elapse, &packcpu, &stat);
+
+        fits_get_hduaddrll(infptr, &headstart, &datastart, &dataend, status); 
+	indatasize = dataend - datastart;
+	filesize = (float) dataend / 1000000.;
+	
+        fits_get_hduaddrll(outfptr, &headstart, &datastart, &dataend, status); 
+	outdatasize = dataend - datastart;
+
+	pratio = (float) indatasize / (float) outdatasize;
+	prate = packcpu;
+
+	fits_delete_hdu(outfptr, &hdutype, &stat);
+
+     /* 4  transposed, use Rice for integer columns, shuffled gzip for others  */
+
+ 	marktime(&stat);
+	fits_compress_table_rice (infptr, outfptr, &stat);
+
+	/* get elapsped times */
+	gettime(&elapse, &packcpu, &stat);
+
+        fits_get_hduaddrll(infptr, &headstart, &datastart, &dataend, status); 
+	indatasize = dataend - datastart;
+	filesize = (float) dataend / 1000000.;
+	
+        fits_get_hduaddrll(outfptr, &headstart, &datastart, &dataend, status); 
+	outdatasize = dataend - datastart;
+
+	rratio = (float) indatasize / (float) outdatasize;
+	rrate = packcpu;
+
+	fits_delete_hdu(outfptr, &hdutype, &stat);
+
+
+     /* 5  best  */
+
+ 	marktime(&stat);
+	fits_compress_table_best (infptr, outfptr, &stat);
+
+	/* get elapsped times */
+	gettime(&elapse, &packcpu, &stat);
+
+        fits_get_hduaddrll(infptr, &headstart, &datastart, &dataend, status); 
+	indatasize = dataend - datastart;
+	filesize = (float) dataend / 1000000.;
+	
+        fits_get_hduaddrll(outfptr, &headstart, &datastart, &dataend, status); 
+	outdatasize = dataend - datastart;
+
+	bratio = (float) indatasize / (float) outdatasize;
+	brate = packcpu;
+
+	fits_delete_hdu(outfptr, &hdutype, &stat);
+
+	printf("\n  Size       Raw        Transposed     Shuffled        Rice        Best\n");     
+	printf(" %5.2fMB %5.2f (%4.2fs) %5.2f (%4.2fs) %5.2f (%4.2fs) %5.2f (%4.2fs)  %5.2f (%4.2fs)\n",
+	    filesize, gratio, grate, sratio, srate, pratio, prate, rratio, rrate, bratio, brate);
+	printf(" Disk savings ratio:    %5.2f         %5.2f      %5.2f\n",
+	 (1. - 1./sratio) / (1. - 1./gratio), (1. - 1./pratio) / (1. - 1./gratio), (1. - 1./bratio) / (1. - 1./gratio));
+
+
+        return(0);
+}
+/*--------------------------------------------------------------------------*/
+int marktime(int *status)
+{
+#if defined(unix) || defined(__unix__)  || defined(__unix)
+        struct  timeval tv;
+/*        struct  timezone tz; */
+
+/*        gettimeofday (&tv, &tz); */
+        gettimeofday (&tv, NULL);
+
+	startsec = tv.tv_sec;
+        startmilli = tv.tv_usec/1000;
+
+        scpu = clock();
+#else
+/* don't support high timing precision on Windows machines */
+     	startsec = 0.;
+        startmilli = 0.;
+
+        scpu = clock();
+#endif
+	return( *status );
+}
+/*--------------------------------------------------------------------------*/
+int gettime(float *elapse, float *elapscpu, int *status)
+{
+#if defined(unix) || defined(__unix__)  || defined(__unix)
+        struct  timeval tv;
+/*        struct  timezone tz; */
+	int stopmilli;
+	long stopsec;
+
+/*        gettimeofday (&tv, &tz); */
+        gettimeofday (&tv, NULL);
+	ecpu = clock();
+
+        stopmilli = tv.tv_usec/1000;
+	stopsec = tv.tv_sec;
+	
+	*elapse = (stopsec - startsec) + (stopmilli - startmilli)/1000.;
+	*elapscpu = (ecpu - scpu) * 1.0 / CLOCKTICKS;
+/*
+printf(" (start: %ld + %d), stop: (%ld + %d) elapse: %f\n ",
+startsec,startmilli,stopsec, stopmilli, *elapse);
+*/
+#else
+/* set the elapsed time the same as the CPU time on Windows machines */
+	*elapscpu = (ecpu - scpu) * 1.0 / CLOCKTICKS;
+	*elapse = *elapscpu;  
+#endif
+	return( *status );
+}
+/*--------------------------------------------------------------------------*/
+int fp_i2stat(fitsfile *infptr, int naxis, long *naxes, imgstats *imagestats, int *status)
+{
+/*
+    read the central XSAMPLE by YSAMPLE region of pixels in the int*2 image, 
+    and then compute basic statistics: min, max, mean, sigma, mean diff, etc.
+*/
+
+	long fpixel[9] = {1,1,1,1,1,1,1,1,1};
+	long lpixel[9] = {1,1,1,1,1,1,1,1,1};
+	long inc[9]    = {1,1,1,1,1,1,1,1,1};
+	long i1, i2, npix, ii, ngood, nx, ny;
+	short *intarray, minvalue, maxvalue, nullvalue;
+	int anynul, tstatus, checknull = 1;
+	double mean, sigma, noise1, noise2, noise3, noise5;
+	
+         /* select the middle XSAMPLE by YSAMPLE area of the image */
+	i1 = naxes[0]/2 - (XSAMPLE/2 - 1);
+	i2 = naxes[0]/2 + (XSAMPLE/2);
+	if (i1 < 1) i1 = 1;
+	if (i2 > naxes[0]) i2 = naxes[0];
+	fpixel[0] = i1;
+	lpixel[0] = i2;
+	nx = i2 - i1 +1;
+
+        if (naxis > 1) {
+	    i1 = naxes[1]/2 - (YSAMPLE/2 - 1);
+	    i2 = naxes[1]/2 + (YSAMPLE/2);
+	    if (i1 < 1) i1 = 1;
+	    if (i2 > naxes[1]) i2 = naxes[1];
+	    fpixel[1] = i1;
+	    lpixel[1] = i2;
+	}
+	ny = i2 - i1 +1;
+
+	npix = nx * ny;
+
+        /* if there are higher dimensions, read the middle plane of the cube */
+	if (naxis > 2) {
+	    fpixel[2] = naxes[2]/2 + 1;
+	    lpixel[2] = naxes[2]/2 + 1;
+	}
+
+	intarray = calloc(npix, sizeof(short));
+	if (!intarray) {
+		*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+
+	/* turn off any scaling of the integer pixel values */
+	fits_set_bscale(infptr, 1.0, 0.0, status);
+
+	fits_read_subset_sht(infptr, 0, naxis, naxes, fpixel, lpixel, inc,
+	    0, intarray, &anynul, status);
+
+	/* read the null value keyword (BLANK) if present */
+	tstatus = 0;
+	fits_read_key(infptr, TSHORT, "BLANK", &nullvalue, 0, &tstatus);
+	if (tstatus) {
+	   nullvalue = 0;
+	   checknull = 0;
+	}
+
+	/* compute statistics of the image */
+
+        fits_img_stats_short(intarray, nx, ny, checknull, nullvalue, 
+	&ngood, &minvalue, &maxvalue, &mean, &sigma, &noise1, &noise2, &noise3, &noise5, status);
+
+	imagestats->n_nulls = npix - ngood;
+	imagestats->minval = minvalue;
+	imagestats->maxval = maxvalue; 
+	imagestats->mean = mean; 
+	imagestats->sigma = sigma; 
+	imagestats->noise1 = noise1; 
+	imagestats->noise2 = noise2; 
+	imagestats->noise3 = noise3; 
+	imagestats->noise5 = noise5; 
+    
+	free(intarray);
+	return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fp_i4stat(fitsfile *infptr, int naxis, long *naxes, imgstats *imagestats, int *status)
+{
+/*
+    read the central XSAMPLE by YSAMPLE region of pixels in the int*2 image, 
+    and then compute basic statistics: min, max, mean, sigma, mean diff, etc.
+*/
+
+	long fpixel[9] = {1,1,1,1,1,1,1,1,1};
+	long lpixel[9] = {1,1,1,1,1,1,1,1,1};
+	long inc[9]    = {1,1,1,1,1,1,1,1,1};
+	long i1, i2, npix, ii, ngood, nx, ny;
+	int *intarray, minvalue, maxvalue, nullvalue;
+	int anynul, tstatus, checknull = 1;
+	double mean, sigma, noise1, noise2, noise3, noise5;
+	
+         /* select the middle XSAMPLE by YSAMPLE area of the image */
+	i1 = naxes[0]/2 - (XSAMPLE/2 - 1);
+	i2 = naxes[0]/2 + (XSAMPLE/2);
+	if (i1 < 1) i1 = 1;
+	if (i2 > naxes[0]) i2 = naxes[0];
+	fpixel[0] = i1;
+	lpixel[0] = i2;
+	nx = i2 - i1 +1;
+
+        if (naxis > 1) {
+	    i1 = naxes[1]/2 - (YSAMPLE/2 - 1);
+	    i2 = naxes[1]/2 + (YSAMPLE/2);
+	    if (i1 < 1) i1 = 1;
+	    if (i2 > naxes[1]) i2 = naxes[1];
+	    fpixel[1] = i1;
+	    lpixel[1] = i2;
+	}
+	ny = i2 - i1 +1;
+
+	npix = nx * ny;
+
+        /* if there are higher dimensions, read the middle plane of the cube */
+	if (naxis > 2) {
+	    fpixel[2] = naxes[2]/2 + 1;
+	    lpixel[2] = naxes[2]/2 + 1;
+	}
+
+	intarray = calloc(npix, sizeof(int));
+	if (!intarray) {
+		*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+
+	/* turn off any scaling of the integer pixel values */
+	fits_set_bscale(infptr, 1.0, 0.0, status);
+
+	fits_read_subset_int(infptr, 0, naxis, naxes, fpixel, lpixel, inc,
+	    0, intarray, &anynul, status);
+
+	/* read the null value keyword (BLANK) if present */
+	tstatus = 0;
+	fits_read_key(infptr, TINT, "BLANK", &nullvalue, 0, &tstatus);
+	if (tstatus) {
+	   nullvalue = 0;
+	   checknull = 0;
+	}
+
+	/* compute statistics of the image */
+
+        fits_img_stats_int(intarray, nx, ny, checknull, nullvalue, 
+	&ngood, &minvalue, &maxvalue, &mean, &sigma, &noise1, &noise2, &noise3, &noise5, status);
+
+	imagestats->n_nulls = npix - ngood;
+	imagestats->minval = minvalue;
+	imagestats->maxval = maxvalue; 
+	imagestats->mean = mean; 
+	imagestats->sigma = sigma; 
+	imagestats->noise1 = noise1; 
+	imagestats->noise2 = noise2; 
+	imagestats->noise3 = noise3; 
+	imagestats->noise5 = noise5; 
+    
+	free(intarray);
+	return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fp_r4stat(fitsfile *infptr, int naxis, long *naxes, imgstats *imagestats, int *status)
+{
+/*
+    read the central XSAMPLE by YSAMPLE region of pixels in the int*2 image, 
+    and then compute basic statistics: min, max, mean, sigma, mean diff, etc.
+*/
+
+	long fpixel[9] = {1,1,1,1,1,1,1,1,1};
+	long lpixel[9] = {1,1,1,1,1,1,1,1,1};
+	long inc[9]    = {1,1,1,1,1,1,1,1,1};
+	long i1, i2, npix, ii, ngood, nx, ny;
+	float *array, minvalue, maxvalue, nullvalue = FLOATNULLVALUE;
+	int anynul,checknull = 1;
+	double mean, sigma, noise1, noise2, noise3, noise5;
+	
+         /* select the middle XSAMPLE by YSAMPLE area of the image */
+	i1 = naxes[0]/2 - (XSAMPLE/2 - 1);
+	i2 = naxes[0]/2 + (XSAMPLE/2);
+	if (i1 < 1) i1 = 1;
+	if (i2 > naxes[0]) i2 = naxes[0];
+	fpixel[0] = i1;
+	lpixel[0] = i2;
+	nx = i2 - i1 +1;
+
+        if (naxis > 1) {
+	    i1 = naxes[1]/2 - (YSAMPLE/2 - 1);
+	    i2 = naxes[1]/2 + (YSAMPLE/2);
+	    if (i1 < 1) i1 = 1;
+	    if (i2 > naxes[1]) i2 = naxes[1];
+	    fpixel[1] = i1;
+	    lpixel[1] = i2;
+	}
+	ny = i2 - i1 +1;
+
+	npix = nx * ny;
+
+        /* if there are higher dimensions, read the middle plane of the cube */
+	if (naxis > 2) {
+	    fpixel[2] = naxes[2]/2 + 1;
+	    lpixel[2] = naxes[2]/2 + 1;
+	}
+
+	array = calloc(npix, sizeof(float));
+	if (!array) {
+		*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+
+	fits_read_subset_flt(infptr, 0, naxis, naxes, fpixel, lpixel, inc,
+	    nullvalue, array, &anynul, status);
+
+	/* are there any null values in the array? */
+	if (!anynul) {
+	   nullvalue = 0.;
+	   checknull = 0;
+	}
+
+	/* compute statistics of the image */
+
+        fits_img_stats_float(array, nx, ny, checknull, nullvalue, 
+	&ngood, &minvalue, &maxvalue, &mean, &sigma, &noise1, &noise2, &noise3, &noise5, status);
+
+	imagestats->n_nulls = npix - ngood;
+	imagestats->minval = minvalue;
+	imagestats->maxval = maxvalue; 
+	imagestats->mean = mean; 
+	imagestats->sigma = sigma; 
+	imagestats->noise1 = noise1; 
+	imagestats->noise2 = noise2; 
+	imagestats->noise3 = noise3; 
+	imagestats->noise5 = noise5; 
+    
+	free(array);
+	return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fp_i2rescale(fitsfile *infptr, int naxis, long *naxes, double rescale,
+    fitsfile *outfptr, int *status)
+{
+/*
+    divide the integer pixel values in the input file by rescale,
+    and write back out to the output file..
+*/
+
+	long ii, jj, nelem = 1, nx, ny;
+	short *intarray, nullvalue;
+	int anynul, tstatus, checknull = 1;
+	
+	nx = naxes[0];
+	ny = 1;
+	
+	for (ii = 1; ii < naxis; ii++) {
+	    ny = ny * naxes[ii];
+	}
+
+	intarray = calloc(nx, sizeof(short));
+	if (!intarray) {
+		*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+
+	/* read the null value keyword (BLANK) if present */
+	tstatus = 0;
+	fits_read_key(infptr, TSHORT, "BLANK", &nullvalue, 0, &tstatus);
+	if (tstatus) {
+	   checknull = 0;
+	}
+
+	/* turn off any scaling of the integer pixel values */
+	fits_set_bscale(infptr,  1.0, 0.0, status);
+	fits_set_bscale(outfptr, 1.0, 0.0, status);
+
+	for (ii = 0; ii < ny; ii++) {
+
+	    fits_read_img_sht(infptr, 1, nelem, nx,
+	        0, intarray, &anynul, status);
+
+	    if (checknull) {
+	        for (jj = 0; jj < nx; jj++) {
+	            if (intarray[jj] != nullvalue)
+		        intarray[jj] = NSHRT( (intarray[jj] / rescale) );
+		}
+	    } else {
+	        for (jj = 0; jj < nx; jj++)
+		        intarray[jj] = NSHRT( (intarray[jj] / rescale) );
+	    }
+
+	    fits_write_img_sht(outfptr, 1, nelem, nx, intarray, status);
+	      
+	    nelem += nx;
+	}
+
+	free(intarray);
+	return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fp_i4rescale(fitsfile *infptr, int naxis, long *naxes, double rescale,
+    fitsfile *outfptr, int *status)
+{
+/*
+    divide the integer pixel values in the input file by rescale,
+    and write back out to the output file..
+*/
+
+	long ii, jj, nelem = 1, nx, ny;
+	int *intarray, nullvalue;
+	int anynul, tstatus, checknull = 1;
+	
+	nx = naxes[0];
+	ny = 1;
+	
+	for (ii = 1; ii < naxis; ii++) {
+	    ny = ny * naxes[ii];
+	}
+
+	intarray = calloc(nx, sizeof(int));
+	if (!intarray) {
+		*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+
+	/* read the null value keyword (BLANK) if present */
+	tstatus = 0;
+	fits_read_key(infptr, TINT, "BLANK", &nullvalue, 0, &tstatus);
+	if (tstatus) {
+	   checknull = 0;
+	}
+
+	/* turn off any scaling of the integer pixel values */
+	fits_set_bscale(infptr,  1.0, 0.0, status);
+	fits_set_bscale(outfptr, 1.0, 0.0, status);
+
+	for (ii = 0; ii < ny; ii++) {
+
+	    fits_read_img_int(infptr, 1, nelem, nx,
+	        0, intarray, &anynul, status);
+
+	    if (checknull) {
+	        for (jj = 0; jj < nx; jj++) {
+	            if (intarray[jj] != nullvalue)
+		        intarray[jj] = NINT( (intarray[jj] / rescale) );
+		}
+	    } else {
+	        for (jj = 0; jj < nx; jj++)
+		        intarray[jj] = NINT( (intarray[jj] / rescale) );
+	    }
+
+	    fits_write_img_int(outfptr, 1, nelem, nx, intarray, status);
+	      
+	    nelem += nx;
+	}
+
+	free(intarray);
+	return(*status);
+}
+/* ========================================================================
+ * Signal and error handler.
+ */
+void abort_fpack(int sig)
+{
+     /* clean up by deleting temporary files */
+     
+      if (tempfilename[0]) {
+         remove(tempfilename);
+      }
+      if (tempfilename2[0]) {
+         remove(tempfilename2);
+      }
+      if (tempfilename3[0]) {
+         remove(tempfilename3);
+      }
+      exit(-1);
+}
diff --git a/src/plugins/cfitsio/getcol.c b/src/plugins/cfitsio/getcol.c
new file mode 100644
index 0000000..f40b91d
--- /dev/null
+++ b/src/plugins/cfitsio/getcol.c
@@ -0,0 +1,1055 @@
+
+/*  This file, getcol.c, contains routines that read data elements from    */
+/*  a FITS image or table.  There are generic datatype routines.           */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+
+#include <stdlib.h>
+#include "fitsio2.h"
+
+/*--------------------------------------------------------------------------*/
+int ffgpxv( fitsfile *fptr,   /* I - FITS file pointer                       */
+            int  datatype,    /* I - datatype of the value                   */
+            long *firstpix,   /* I - coord of first pixel to read (1s based) */
+            LONGLONG nelem,   /* I - number of values to read                */
+            void *nulval,     /* I - value for undefined pixels              */
+            void *array,      /* O - array of values that are returned       */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from the primary array. The datatype of the
+  input array is defined by the 2nd argument.  Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+  Undefined elements will be set equal to NULVAL, unless NULVAL=0
+  in which case no checking for undefined values will be performed.
+  ANYNUL is returned with a value of .true. if any pixels are undefined.
+*/
+{
+    LONGLONG tfirstpix[99];
+    int naxis, ii;
+
+    if (*status > 0 || nelem == 0)   /* inherit input status value if > 0 */
+        return(*status);
+
+    /* get the size of the image */
+    ffgidm(fptr, &naxis, status);
+    
+    for (ii=0; ii < naxis; ii++)
+       tfirstpix[ii] = firstpix[ii];
+
+    ffgpxvll(fptr, datatype, tfirstpix, nelem, nulval, array, anynul, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgpxvll( fitsfile *fptr, /* I - FITS file pointer                       */
+            int  datatype,    /* I - datatype of the value                   */
+            LONGLONG *firstpix, /* I - coord of first pixel to read (1s based) */
+            LONGLONG nelem,   /* I - number of values to read                */
+            void *nulval,     /* I - value for undefined pixels              */
+            void *array,      /* O - array of values that are returned       */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from the primary array. The datatype of the
+  input array is defined by the 2nd argument.  Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+  Undefined elements will be set equal to NULVAL, unless NULVAL=0
+  in which case no checking for undefined values will be performed.
+  ANYNUL is returned with a value of .true. if any pixels are undefined.
+*/
+{
+    int naxis, ii;
+    char cdummy;
+    int nullcheck = 1;
+    LONGLONG naxes[9], trc[9]= {1,1,1,1,1,1,1,1,1};
+    long inc[9]= {1,1,1,1,1,1,1,1,1};
+    LONGLONG dimsize = 1, firstelem;
+
+    if (*status > 0 || nelem == 0)   /* inherit input status value if > 0 */
+        return(*status);
+
+    /* get the size of the image */
+    ffgidm(fptr, &naxis, status);
+
+    ffgiszll(fptr, 9, naxes, status);
+
+    if (naxis == 0 || naxes[0] == 0) {
+       *status = BAD_DIMEN;
+       return(*status);
+    }
+
+    /* calculate the position of the first element in the array */
+    firstelem = 0;
+    for (ii=0; ii < naxis; ii++)
+    {
+        firstelem += ((firstpix[ii] - 1) * dimsize);
+        dimsize *= naxes[ii];
+        trc[ii] = firstpix[ii];
+    }
+    firstelem++;
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        /* test for special case of reading an integral number of */
+        /* rows in a 2D or 3D image (which includes reading the whole image */
+
+	if (naxis > 1 && naxis < 4 && firstpix[0] == 1 &&
+            (nelem / naxes[0]) * naxes[0] == nelem) {
+
+                /* calculate coordinate of last pixel */
+		trc[0] = naxes[0];  /* reading whole rows */
+		trc[1] = firstpix[1] + (nelem / naxes[0] - 1);
+                while (trc[1] > naxes[1])  {
+		    trc[1] = trc[1] - naxes[1];
+		    trc[2] = trc[2] + 1;  /* increment to next plane of cube */
+                }
+
+                fits_read_compressed_img(fptr, datatype, firstpix, trc, inc,
+                   1, nulval, array, NULL, anynul, status);
+
+        } else {
+
+                fits_read_compressed_pixels(fptr, datatype, firstelem,
+                   nelem, nullcheck, nulval, array, NULL, anynul, status);
+        }
+
+        return(*status);
+    }
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    if (datatype == TBYTE)
+    {
+      if (nulval == 0)
+        ffgclb(fptr, 2, 1, firstelem, nelem, 1, 1, 0,
+               (unsigned char *) array, &cdummy, anynul, status);
+      else
+        ffgclb(fptr, 2, 1, firstelem, nelem, 1, 1, *(unsigned char *) nulval,
+               (unsigned char *) array, &cdummy, anynul, status);
+    }
+    else if (datatype == TSBYTE)
+    {
+      if (nulval == 0)
+        ffgclsb(fptr, 2, 1, firstelem, nelem, 1, 1, 0,
+               (signed char *) array, &cdummy, anynul, status);
+      else
+        ffgclsb(fptr, 2, 1, firstelem, nelem, 1, 1, *(signed char *) nulval,
+               (signed char *) array, &cdummy, anynul, status);
+    }
+    else if (datatype == TUSHORT)
+    {
+      if (nulval == 0)
+        ffgclui(fptr, 2, 1, firstelem, nelem, 1, 1, 0,
+               (unsigned short *) array, &cdummy, anynul, status);
+      else
+        ffgclui(fptr, 2, 1, firstelem, nelem, 1, 1, *(unsigned short *) nulval,
+               (unsigned short *) array, &cdummy, anynul, status);
+    }
+    else if (datatype == TSHORT)
+    {
+      if (nulval == 0)
+        ffgcli(fptr, 2, 1, firstelem, nelem, 1, 1, 0,
+               (short *) array, &cdummy, anynul, status);
+      else
+        ffgcli(fptr, 2, 1, firstelem, nelem, 1, 1, *(short *) nulval,
+               (short *) array, &cdummy, anynul, status);
+    }
+    else if (datatype == TUINT)
+    {
+      if (nulval == 0)
+        ffgcluk(fptr, 2, 1, firstelem, nelem, 1, 1, 0,
+               (unsigned int *) array, &cdummy, anynul, status);
+      else
+        ffgcluk(fptr, 2, 1, firstelem, nelem, 1, 1, *(unsigned int *) nulval,
+               (unsigned int *) array, &cdummy, anynul, status);
+    }
+    else if (datatype == TINT)
+    {
+      if (nulval == 0)
+        ffgclk(fptr, 2, 1, firstelem, nelem, 1, 1, 0,
+               (int *) array, &cdummy, anynul, status);
+      else
+        ffgclk(fptr, 2, 1, firstelem, nelem, 1, 1, *(int *) nulval,
+               (int *) array, &cdummy, anynul, status);
+    }
+    else if (datatype == TULONG)
+    {
+      if (nulval == 0)
+        ffgcluj(fptr, 2, 1, firstelem, nelem, 1, 1, 0,
+               (unsigned long *) array, &cdummy, anynul, status);
+      else
+        ffgcluj(fptr, 2, 1, firstelem, nelem, 1, 1, *(unsigned long *) nulval,
+               (unsigned long *) array, &cdummy, anynul, status);
+    }
+    else if (datatype == TLONG)
+    {
+      if (nulval == 0)
+        ffgclj(fptr, 2, 1, firstelem, nelem, 1, 1, 0,
+               (long *) array, &cdummy, anynul, status);
+      else
+        ffgclj(fptr, 2, 1, firstelem, nelem, 1, 1, *(long *) nulval,
+               (long *) array, &cdummy, anynul, status);
+    }
+    else if (datatype == TLONGLONG)
+    {
+      if (nulval == 0)
+        ffgcljj(fptr, 2, 1, firstelem, nelem, 1, 1, 0,
+               (LONGLONG *) array, &cdummy, anynul, status);
+      else
+        ffgcljj(fptr, 2, 1, firstelem, nelem, 1, 1, *(LONGLONG *) nulval,
+               (LONGLONG *) array, &cdummy, anynul, status);
+    }
+    else if (datatype == TFLOAT)
+    {
+      if (nulval == 0)
+        ffgcle(fptr, 2, 1, firstelem, nelem, 1, 1, 0,
+               (float *) array, &cdummy, anynul, status);
+      else
+        ffgcle(fptr, 2, 1, firstelem, nelem, 1, 1, *(float *) nulval,
+               (float *) array, &cdummy, anynul, status);
+    }
+    else if (datatype == TDOUBLE)
+    {
+      if (nulval == 0)
+        ffgcld(fptr, 2, 1, firstelem, nelem, 1, 1, 0,
+               (double *) array, &cdummy, anynul, status);
+      else
+        ffgcld(fptr, 2, 1, firstelem, nelem, 1, 1, *(double *) nulval,
+               (double *) array, &cdummy, anynul, status);
+    }
+    else
+      *status = BAD_DATATYPE;
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgpxf( fitsfile *fptr,   /* I - FITS file pointer                       */
+            int  datatype,    /* I - datatype of the value                   */
+            long *firstpix,   /* I - coord of first pixel to read (1s based) */
+            LONGLONG nelem,       /* I - number of values to read            */
+            void *array,      /* O - array of values that are returned       */
+            char *nullarray,  /* O - returned array of null value flags      */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from the primary array. The datatype of the
+  input array is defined by the 2nd argument.  Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+  The nullarray values will = 1 if the corresponding array value is null.
+  ANYNUL is returned with a value of .true. if any pixels are undefined.
+*/
+{
+    LONGLONG tfirstpix[99];
+    int naxis, ii;
+
+    if (*status > 0 || nelem == 0)   /* inherit input status value if > 0 */
+        return(*status);
+
+    /* get the size of the image */
+    ffgidm(fptr, &naxis, status);
+
+    for (ii=0; ii < naxis; ii++)
+       tfirstpix[ii] = firstpix[ii];
+
+    ffgpxfll(fptr, datatype, tfirstpix, nelem, array, nullarray, anynul, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgpxfll( fitsfile *fptr, /* I - FITS file pointer                       */
+            int  datatype,    /* I - datatype of the value                   */
+            LONGLONG *firstpix, /* I - coord of first pixel to read (1s based) */
+            LONGLONG nelem,       /* I - number of values to read              */
+            void *array,      /* O - array of values that are returned       */
+            char *nullarray,  /* O - returned array of null value flags      */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from the primary array. The datatype of the
+  input array is defined by the 2nd argument.  Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+  The nullarray values will = 1 if the corresponding array value is null.
+  ANYNUL is returned with a value of .true. if any pixels are undefined.
+*/
+{
+    int naxis, ii;
+    int nullcheck = 2;
+    LONGLONG naxes[9];
+    LONGLONG dimsize = 1, firstelem;
+
+    if (*status > 0 || nelem == 0)   /* inherit input status value if > 0 */
+        return(*status);
+
+    /* get the size of the image */
+    ffgidm(fptr, &naxis, status);
+    ffgiszll(fptr, 9, naxes, status);
+
+    /* calculate the position of the first element in the array */
+    firstelem = 0;
+    for (ii=0; ii < naxis; ii++)
+    {
+        firstelem += ((firstpix[ii] - 1) * dimsize);
+        dimsize *= naxes[ii];
+    }
+    firstelem++;
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        fits_read_compressed_pixels(fptr, datatype, firstelem, nelem,
+            nullcheck, NULL, array, nullarray, anynul, status);
+        return(*status);
+    }
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    if (datatype == TBYTE)
+    {
+        ffgclb(fptr, 2, 1, firstelem, nelem, 1, 2, 0,
+               (unsigned char *) array, nullarray, anynul, status);
+    }
+    else if (datatype == TSBYTE)
+    {
+        ffgclsb(fptr, 2, 1, firstelem, nelem, 1, 2, 0,
+               (signed char *) array, nullarray, anynul, status);
+    }
+    else if (datatype == TUSHORT)
+    {
+        ffgclui(fptr, 2, 1, firstelem, nelem, 1, 2, 0,
+               (unsigned short *) array, nullarray, anynul, status);
+    }
+    else if (datatype == TSHORT)
+    {
+        ffgcli(fptr, 2, 1, firstelem, nelem, 1, 2, 0,
+               (short *) array, nullarray, anynul, status);
+    }
+    else if (datatype == TUINT)
+    {
+        ffgcluk(fptr, 2, 1, firstelem, nelem, 1, 2, 0,
+               (unsigned int *) array, nullarray, anynul, status);
+    }
+    else if (datatype == TINT)
+    {
+        ffgclk(fptr, 2, 1, firstelem, nelem, 1, 2, 0,
+               (int *) array, nullarray, anynul, status);
+    }
+    else if (datatype == TULONG)
+    {
+        ffgcluj(fptr, 2, 1, firstelem, nelem, 1, 2, 0,
+               (unsigned long *) array, nullarray, anynul, status);
+    }
+    else if (datatype == TLONG)
+    {
+        ffgclj(fptr, 2, 1, firstelem, nelem, 1, 2, 0,
+               (long *) array, nullarray, anynul, status);
+    }
+    else if (datatype == TLONGLONG)
+    {
+        ffgcljj(fptr, 2, 1, firstelem, nelem, 1, 2, 0,
+               (LONGLONG *) array, nullarray, anynul, status);
+    }
+    else if (datatype == TFLOAT)
+    {
+        ffgcle(fptr, 2, 1, firstelem, nelem, 1, 2, 0,
+               (float *) array, nullarray, anynul, status);
+    }
+    else if (datatype == TDOUBLE)
+    {
+        ffgcld(fptr, 2, 1, firstelem, nelem, 1, 2, 0,
+               (double *) array, nullarray, anynul, status);
+    }
+    else
+      *status = BAD_DATATYPE;
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgsv(  fitsfile *fptr,   /* I - FITS file pointer                       */
+            int  datatype,    /* I - datatype of the value                   */
+            long *blc,        /* I - 'bottom left corner' of the subsection  */
+            long *trc ,       /* I - 'top right corner' of the subsection    */
+            long *inc,        /* I - increment to be applied in each dim.    */
+            void *nulval,     /* I - value for undefined pixels              */
+            void *array,      /* O - array of values that are returned       */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an section of values from the primary array. The datatype of the
+  input array is defined by the 2nd argument.  Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+  Undefined elements will be set equal to NULVAL, unless NULVAL=0
+  in which case no checking for undefined values will be performed.
+  ANYNUL is returned with a value of .true. if any pixels are undefined.
+*/
+{
+    int naxis, ii;
+    long naxes[9];
+    LONGLONG nelem = 1;
+
+    if (*status > 0)   /* inherit input status value if > 0 */
+        return(*status);
+
+    /* get the size of the image */
+    ffgidm(fptr, &naxis, status);
+    ffgisz(fptr, 9, naxes, status);
+
+    /* test for the important special case where we are reading the whole image */
+    /* this is only useful for images that are not tile-compressed */
+    if (!fits_is_compressed_image(fptr, status)) {
+        for (ii = 0; ii < naxis; ii++) {
+            if (inc[ii] != 1 || blc[ii] !=1 || trc[ii] != naxes[ii])
+                break;
+
+            nelem = nelem * naxes[ii];
+        }
+
+        if (ii == naxis) {
+            /* read the whole image more efficiently */
+            ffgpxv(fptr, datatype, blc, nelem, nulval, array, anynul, status);
+            return(*status);
+        }
+    }
+
+    if (datatype == TBYTE)
+    {
+      if (nulval == 0)
+        ffgsvb(fptr, 1, naxis, naxes, blc, trc, inc, 0,
+               (unsigned char *) array, anynul, status);
+      else
+        ffgsvb(fptr, 1, naxis, naxes, blc, trc, inc, *(unsigned char *) nulval,
+               (unsigned char *) array, anynul, status);
+    }
+    else if (datatype == TSBYTE)
+    {
+      if (nulval == 0)
+        ffgsvsb(fptr, 1, naxis, naxes, blc, trc, inc, 0,
+               (signed char *) array, anynul, status);
+      else
+        ffgsvsb(fptr, 1, naxis, naxes, blc, trc, inc, *(signed char *) nulval,
+               (signed char *) array, anynul, status);
+    }
+    else if (datatype == TUSHORT)
+    {
+      if (nulval == 0)
+        ffgsvui(fptr, 1, naxis, naxes, blc, trc, inc, 0,
+               (unsigned short *) array, anynul, status);
+      else
+        ffgsvui(fptr, 1, naxis, naxes,blc, trc, inc, *(unsigned short *) nulval,
+               (unsigned short *) array, anynul, status);
+    }
+    else if (datatype == TSHORT)
+    {
+      if (nulval == 0)
+        ffgsvi(fptr, 1, naxis, naxes, blc, trc, inc, 0,
+               (short *) array, anynul, status);
+      else
+        ffgsvi(fptr, 1, naxis, naxes, blc, trc, inc, *(short *) nulval,
+               (short *) array, anynul, status);
+    }
+    else if (datatype == TUINT)
+    {
+      if (nulval == 0)
+        ffgsvuk(fptr, 1, naxis, naxes, blc, trc, inc, 0,
+               (unsigned int *) array, anynul, status);
+      else
+        ffgsvuk(fptr, 1, naxis, naxes, blc, trc, inc, *(unsigned int *) nulval,
+               (unsigned int *) array, anynul, status);
+    }
+    else if (datatype == TINT)
+    {
+      if (nulval == 0)
+        ffgsvk(fptr, 1, naxis, naxes, blc, trc, inc, 0,
+               (int *) array, anynul, status);
+      else
+        ffgsvk(fptr, 1, naxis, naxes, blc, trc, inc, *(int *) nulval,
+               (int *) array, anynul, status);
+    }
+    else if (datatype == TULONG)
+    {
+      if (nulval == 0)
+        ffgsvuj(fptr, 1, naxis, naxes, blc, trc, inc, 0,
+               (unsigned long *) array, anynul, status);
+      else
+        ffgsvuj(fptr, 1, naxis, naxes, blc, trc, inc, *(unsigned long *) nulval,
+               (unsigned long *) array, anynul, status);
+    }
+    else if (datatype == TLONG)
+    {
+      if (nulval == 0)
+        ffgsvj(fptr, 1, naxis, naxes, blc, trc, inc, 0,
+               (long *) array, anynul, status);
+      else
+        ffgsvj(fptr, 1, naxis, naxes, blc, trc, inc, *(long *) nulval,
+               (long *) array, anynul, status);
+    }
+    else if (datatype == TLONGLONG)
+    {
+      if (nulval == 0)
+        ffgsvjj(fptr, 1, naxis, naxes, blc, trc, inc, 0,
+               (LONGLONG *) array, anynul, status);
+      else
+        ffgsvjj(fptr, 1, naxis, naxes, blc, trc, inc, *(LONGLONG *) nulval,
+               (LONGLONG *) array, anynul, status);
+    }
+    else if (datatype == TFLOAT)
+    {
+      if (nulval == 0)
+        ffgsve(fptr, 1, naxis, naxes, blc, trc, inc, 0,
+               (float *) array, anynul, status);
+      else
+        ffgsve(fptr, 1, naxis, naxes, blc, trc, inc, *(float *) nulval,
+               (float *) array, anynul, status);
+    }
+    else if (datatype == TDOUBLE)
+    {
+      if (nulval == 0)
+        ffgsvd(fptr, 1, naxis, naxes, blc, trc, inc, 0,
+               (double *) array, anynul, status);
+      else
+        ffgsvd(fptr, 1, naxis, naxes, blc, trc, inc, *(double *) nulval,
+               (double *) array, anynul, status);
+    }
+    else
+      *status = BAD_DATATYPE;
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgpv(  fitsfile *fptr,   /* I - FITS file pointer                       */
+            int  datatype,    /* I - datatype of the value                   */
+            LONGLONG firstelem,   /* I - first vector element to read (1 = 1st)  */
+            LONGLONG nelem,       /* I - number of values to read                */
+            void *nulval,     /* I - value for undefined pixels              */
+            void *array,      /* O - array of values that are returned       */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from the primary array. The datatype of the
+  input array is defined by the 2nd argument.  Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+  Undefined elements will be set equal to NULVAL, unless NULVAL=0
+  in which case no checking for undefined values will be performed.
+  ANYNUL is returned with a value of .true. if any pixels are undefined.
+*/
+{
+
+    if (*status > 0 || nelem == 0)   /* inherit input status value if > 0 */
+        return(*status);
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    if (datatype == TBYTE)
+    {
+      if (nulval == 0)
+        ffgpvb(fptr, 1, firstelem, nelem, 0,
+               (unsigned char *) array, anynul, status);
+      else
+        ffgpvb(fptr, 1, firstelem, nelem, *(unsigned char *) nulval,
+               (unsigned char *) array, anynul, status);
+    }
+    else if (datatype == TSBYTE)
+    {
+      if (nulval == 0)
+        ffgpvsb(fptr, 1, firstelem, nelem, 0,
+               (signed char *) array, anynul, status);
+      else
+        ffgpvsb(fptr, 1, firstelem, nelem, *(signed char *) nulval,
+               (signed char *) array, anynul, status);
+    }
+    else if (datatype == TUSHORT)
+    {
+      if (nulval == 0)
+        ffgpvui(fptr, 1, firstelem, nelem, 0,
+               (unsigned short *) array, anynul, status);
+      else
+        ffgpvui(fptr, 1, firstelem, nelem, *(unsigned short *) nulval,
+               (unsigned short *) array, anynul, status);
+    }
+    else if (datatype == TSHORT)
+    {
+      if (nulval == 0)
+        ffgpvi(fptr, 1, firstelem, nelem, 0,
+               (short *) array, anynul, status);
+      else
+        ffgpvi(fptr, 1, firstelem, nelem, *(short *) nulval,
+               (short *) array, anynul, status);
+    }
+    else if (datatype == TUINT)
+    {
+      if (nulval == 0)
+        ffgpvuk(fptr, 1, firstelem, nelem, 0,
+               (unsigned int *) array, anynul, status);
+      else
+        ffgpvuk(fptr, 1, firstelem, nelem, *(unsigned int *) nulval,
+               (unsigned int *) array, anynul, status);
+    }
+    else if (datatype == TINT)
+    {
+      if (nulval == 0)
+        ffgpvk(fptr, 1, firstelem, nelem, 0,
+               (int *) array, anynul, status);
+      else
+        ffgpvk(fptr, 1, firstelem, nelem, *(int *) nulval,
+               (int *) array, anynul, status);
+    }
+    else if (datatype == TULONG)
+    {
+      if (nulval == 0)
+        ffgpvuj(fptr, 1, firstelem, nelem, 0,
+               (unsigned long *) array, anynul, status);
+      else
+        ffgpvuj(fptr, 1, firstelem, nelem, *(unsigned long *) nulval,
+               (unsigned long *) array, anynul, status);
+    }
+    else if (datatype == TLONG)
+    {
+      if (nulval == 0)
+        ffgpvj(fptr, 1, firstelem, nelem, 0,
+               (long *) array, anynul, status);
+      else
+        ffgpvj(fptr, 1, firstelem, nelem, *(long *) nulval,
+               (long *) array, anynul, status);
+    }
+    else if (datatype == TLONGLONG)
+    {
+      if (nulval == 0)
+        ffgpvjj(fptr, 1, firstelem, nelem, 0,
+               (LONGLONG *) array, anynul, status);
+      else
+        ffgpvjj(fptr, 1, firstelem, nelem, *(LONGLONG *) nulval,
+               (LONGLONG *) array, anynul, status);
+    }
+    else if (datatype == TFLOAT)
+    {
+      if (nulval == 0)
+        ffgpve(fptr, 1, firstelem, nelem, 0,
+               (float *) array, anynul, status);
+      else
+        ffgpve(fptr, 1, firstelem, nelem, *(float *) nulval,
+               (float *) array, anynul, status);
+    }
+    else if (datatype == TDOUBLE)
+    {
+      if (nulval == 0)
+        ffgpvd(fptr, 1, firstelem, nelem, 0,
+               (double *) array, anynul, status);
+      else
+      {
+        ffgpvd(fptr, 1, firstelem, nelem, *(double *) nulval,
+               (double *) array, anynul, status);
+      }
+    }
+    else
+      *status = BAD_DATATYPE;
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgpf(  fitsfile *fptr,   /* I - FITS file pointer                       */
+            int  datatype,    /* I - datatype of the value                   */
+            LONGLONG firstelem,   /* I - first vector element to read (1 = 1st)  */
+            LONGLONG nelem,       /* I - number of values to read                */
+            void *array,      /* O - array of values that are returned       */
+            char *nullarray,  /* O - array of null value flags               */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from the primary array. The datatype of the
+  input array is defined by the 2nd argument.  Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+  The nullarray values will = 1 if the corresponding array value is null.
+  ANYNUL is returned with a value of .true. if any pixels are undefined.
+*/
+{
+
+    if (*status > 0 || nelem == 0)   /* inherit input status value if > 0 */
+        return(*status);
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    if (datatype == TBYTE)
+    {
+        ffgpfb(fptr, 1, firstelem, nelem, 
+               (unsigned char *) array, nullarray, anynul, status);
+    }
+    else if (datatype == TSBYTE)
+    {
+        ffgpfsb(fptr, 1, firstelem, nelem, 
+               (signed char *) array, nullarray, anynul, status);
+    }
+    else if (datatype == TUSHORT)
+    {
+        ffgpfui(fptr, 1, firstelem, nelem, 
+               (unsigned short *) array, nullarray, anynul, status);
+    }
+    else if (datatype == TSHORT)
+    {
+        ffgpfi(fptr, 1, firstelem, nelem, 
+               (short *) array, nullarray, anynul, status);
+    }
+    else if (datatype == TUINT)
+    {
+        ffgpfuk(fptr, 1, firstelem, nelem, 
+               (unsigned int *) array, nullarray, anynul, status);
+    }
+    else if (datatype == TINT)
+    {
+        ffgpfk(fptr, 1, firstelem, nelem, 
+               (int *) array, nullarray, anynul, status);
+    }
+    else if (datatype == TULONG)
+    {
+        ffgpfuj(fptr, 1, firstelem, nelem, 
+               (unsigned long *) array, nullarray, anynul, status);
+    }
+    else if (datatype == TLONG)
+    {
+        ffgpfj(fptr, 1, firstelem, nelem,
+               (long *) array, nullarray, anynul, status);
+    }
+    else if (datatype == TLONGLONG)
+    {
+        ffgpfjj(fptr, 1, firstelem, nelem,
+               (LONGLONG *) array, nullarray, anynul, status);
+    }
+    else if (datatype == TFLOAT)
+    {
+        ffgpfe(fptr, 1, firstelem, nelem, 
+               (float *) array, nullarray, anynul, status);
+    }
+    else if (datatype == TDOUBLE)
+    {
+        ffgpfd(fptr, 1, firstelem, nelem,
+               (double *) array, nullarray, anynul, status);
+    }
+    else
+      *status = BAD_DATATYPE;
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcv(  fitsfile *fptr,   /* I - FITS file pointer                       */
+            int  datatype,    /* I - datatype of the value                   */
+            int  colnum,      /* I - number of column to write (1 = 1st col) */
+            LONGLONG  firstrow,   /* I - first row to write (1 = 1st row)        */
+            LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+            LONGLONG nelem,       /* I - number of values to read                */
+            void *nulval,     /* I - value for undefined pixels              */
+            void *array,      /* O - array of values that are returned       */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from a table column. The datatype of the
+  input array is defined by the 2nd argument.  Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+  Undefined elements will be set equal to NULVAL, unless NULVAL=0
+  in which case no checking for undefined values will be performed.
+  ANYNUL is returned with a value of true if any pixels are undefined.
+*/
+{
+    char cdummy[2];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (datatype == TBIT)
+    {
+      ffgcx(fptr, colnum, firstrow, firstelem, nelem, (char *) array, status);
+    }
+    else if (datatype == TBYTE)
+    {
+      if (nulval == 0)
+        ffgclb(fptr, colnum, firstrow, firstelem, nelem, 1, 1, 0,
+              (unsigned char *) array, cdummy, anynul, status);
+      else
+       ffgclb(fptr, colnum, firstrow, firstelem, nelem, 1, 1, *(unsigned char *)
+              nulval, (unsigned char *) array, cdummy, anynul, status);
+    }
+    else if (datatype == TSBYTE)
+    {
+      if (nulval == 0)
+        ffgclsb(fptr, colnum, firstrow, firstelem, nelem, 1, 1, 0,
+              (signed char *) array, cdummy, anynul, status);
+      else
+       ffgclsb(fptr, colnum, firstrow, firstelem, nelem, 1, 1, *(signed char *)
+              nulval, (signed char *) array, cdummy, anynul, status);
+    }
+    else if (datatype == TUSHORT)
+    {
+      if (nulval == 0)
+        ffgclui(fptr, colnum, firstrow, firstelem, nelem, 1, 1, 0,
+               (unsigned short *) array, cdummy, anynul, status);
+      else
+        ffgclui(fptr, colnum, firstrow, firstelem, nelem, 1, 1,
+               *(unsigned short *) nulval,
+               (unsigned short *) array, cdummy, anynul, status);
+    }
+    else if (datatype == TSHORT)
+    {
+      if (nulval == 0)
+        ffgcli(fptr, colnum, firstrow, firstelem, nelem, 1, 1, 0,
+              (short *) array, cdummy, anynul, status);
+      else
+        ffgcli(fptr, colnum, firstrow, firstelem, nelem, 1, 1, *(short *)
+              nulval, (short *) array, cdummy, anynul, status);
+    }
+    else if (datatype == TUINT)
+    {
+      if (nulval == 0)
+        ffgcluk(fptr, colnum, firstrow, firstelem, nelem, 1, 1, 0,
+              (unsigned int *) array, cdummy, anynul, status);
+      else
+        ffgcluk(fptr, colnum, firstrow, firstelem, nelem, 1, 1,
+         *(unsigned int *) nulval, (unsigned int *) array, cdummy, anynul,
+         status);
+    }
+    else if (datatype == TINT)
+    {
+      if (nulval == 0)
+        ffgclk(fptr, colnum, firstrow, firstelem, nelem, 1, 1, 0,
+              (int *) array, cdummy, anynul, status);
+      else
+        ffgclk(fptr, colnum, firstrow, firstelem, nelem, 1, 1, *(int *)
+            nulval, (int *) array, cdummy, anynul, status);
+    }
+    else if (datatype == TULONG)
+    {
+      if (nulval == 0)
+        ffgcluj(fptr, colnum, firstrow, firstelem, nelem, 1, 1, 0,
+               (unsigned long *) array, cdummy, anynul, status);
+      else
+        ffgcluj(fptr, colnum, firstrow, firstelem, nelem, 1, 1,
+               *(unsigned long *) nulval, 
+               (unsigned long *) array, cdummy, anynul, status);
+    }
+    else if (datatype == TLONG)
+    {
+      if (nulval == 0)
+        ffgclj(fptr, colnum, firstrow, firstelem, nelem, 1, 1, 0,
+              (long *) array, cdummy, anynul, status);
+      else
+        ffgclj(fptr, colnum, firstrow, firstelem, nelem, 1, 1, *(long *)
+              nulval, (long *) array, cdummy, anynul, status);
+    }
+    else if (datatype == TLONGLONG)
+    {
+      if (nulval == 0)
+        ffgcljj(fptr, colnum, firstrow, firstelem, nelem, 1, 1, 0,
+              (LONGLONG *) array, cdummy, anynul, status);
+      else
+        ffgcljj(fptr, colnum, firstrow, firstelem, nelem, 1, 1, *(LONGLONG *)
+              nulval, (LONGLONG *) array, cdummy, anynul, status);
+    }
+    else if (datatype == TFLOAT)
+    {
+      if (nulval == 0)
+        ffgcle(fptr, colnum, firstrow, firstelem, nelem, 1, 1, 0.,
+              (float *) array, cdummy, anynul, status);
+      else
+      ffgcle(fptr, colnum, firstrow, firstelem, nelem, 1, 1, *(float *)
+               nulval,(float *) array, cdummy, anynul, status);
+    }
+    else if (datatype == TDOUBLE)
+    {
+      if (nulval == 0)
+        ffgcld(fptr, colnum, firstrow, firstelem, nelem, 1, 1, 0.,
+              (double *) array, cdummy, anynul, status);
+      else
+        ffgcld(fptr, colnum, firstrow, firstelem, nelem, 1, 1, *(double *)
+              nulval, (double *) array, cdummy, anynul, status);
+    }
+    else if (datatype == TCOMPLEX)
+    {
+      if (nulval == 0)
+        ffgcle(fptr, colnum, firstrow, (firstelem - 1) * 2 + 1, nelem * 2,
+           1, 1, 0., (float *) array, cdummy, anynul, status);
+      else
+        ffgcle(fptr, colnum, firstrow, (firstelem - 1) * 2 + 1, nelem * 2,
+           1, 1, *(float *) nulval, (float *) array, cdummy, anynul, status);
+    }
+    else if (datatype == TDBLCOMPLEX)
+    {
+      if (nulval == 0)
+        ffgcld(fptr, colnum, firstrow, (firstelem - 1) * 2 + 1, nelem * 2, 
+         1, 1, 0., (double *) array, cdummy, anynul, status);
+      else
+        ffgcld(fptr, colnum, firstrow, (firstelem - 1) * 2 + 1, nelem * 2, 
+         1, 1, *(double *) nulval, (double *) array, cdummy, anynul, status);
+    }
+
+    else if (datatype == TLOGICAL)
+    {
+      if (nulval == 0)
+        ffgcll(fptr, colnum, firstrow, firstelem, nelem, 1, 0,
+          (char *) array, cdummy, anynul, status);
+      else
+        ffgcll(fptr, colnum, firstrow, firstelem, nelem, 1, *(char *) nulval,
+          (char *) array, cdummy, anynul, status);
+    }
+    else if (datatype == TSTRING)
+    {
+      if (nulval == 0)
+      {
+        cdummy[0] = '\0';
+        ffgcls(fptr, colnum, firstrow, firstelem, nelem, 1, 
+             cdummy, (char **) array, cdummy, anynul, status);
+      }
+      else
+        ffgcls(fptr, colnum, firstrow, firstelem, nelem, 1, (char *)
+             nulval, (char **) array, cdummy, anynul, status);
+    }
+    else
+      *status = BAD_DATATYPE;
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcf(  fitsfile *fptr,   /* I - FITS file pointer                       */
+            int  datatype,    /* I - datatype of the value                   */
+            int  colnum,      /* I - number of column to write (1 = 1st col) */
+            LONGLONG  firstrow,   /* I - first row to write (1 = 1st row)        */
+            LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+            LONGLONG nelem,       /* I - number of values to read                */
+            void *array,      /* O - array of values that are returned       */
+            char *nullarray,  /* O - array of null value flags               */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from a table column. The datatype of the
+  input array is defined by the 2nd argument.  Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+  ANYNUL is returned with a value of true if any pixels are undefined.
+*/
+{
+    void *nulval;         /* dummy argument */
+    double dnulval = 0.;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    nulval = &dnulval;  /* set to a harmless value; this is never used */
+
+    if (datatype == TBIT)
+    {
+      ffgcx(fptr, colnum, firstrow, firstelem, nelem, (char *) array, status);
+    }
+    else if (datatype == TBYTE)
+    {
+       ffgclb(fptr, colnum, firstrow, firstelem, nelem, 1, 2, *(unsigned char *)
+              nulval, (unsigned char *) array, nullarray, anynul, status);
+    }
+    else if (datatype == TSBYTE)
+    {
+       ffgclsb(fptr, colnum, firstrow, firstelem, nelem, 1, 2, *(signed char *)
+              nulval, (signed char *) array, nullarray, anynul, status);
+    }
+    else if (datatype == TUSHORT)
+    {
+        ffgclui(fptr, colnum, firstrow, firstelem, nelem, 1, 2,
+               *(unsigned short *) nulval,
+               (unsigned short *) array, nullarray, anynul, status);
+    }
+    else if (datatype == TSHORT)
+    {
+        ffgcli(fptr, colnum, firstrow, firstelem, nelem, 1, 2, *(short *)
+              nulval, (short *) array, nullarray, anynul, status);
+    }
+    else if (datatype == TUINT)
+    {
+        ffgcluk(fptr, colnum, firstrow, firstelem, nelem, 1, 2,
+         *(unsigned int *) nulval, (unsigned int *) array, nullarray, anynul,
+         status);
+    }
+    else if (datatype == TINT)
+    {
+        ffgclk(fptr, colnum, firstrow, firstelem, nelem, 1, 2, *(int *)
+            nulval, (int *) array, nullarray, anynul, status);
+    }
+    else if (datatype == TULONG)
+    {
+        ffgcluj(fptr, colnum, firstrow, firstelem, nelem, 1, 2,
+               *(unsigned long *) nulval, 
+               (unsigned long *) array, nullarray, anynul, status);
+    }
+    else if (datatype == TLONG)
+    {
+        ffgclj(fptr, colnum, firstrow, firstelem, nelem, 1, 2, *(long *)
+              nulval, (long *) array, nullarray, anynul, status);
+    }
+    else if (datatype == TLONGLONG)
+    {
+        ffgcljj(fptr, colnum, firstrow, firstelem, nelem, 1, 2, *(LONGLONG *)
+              nulval, (LONGLONG *) array, nullarray, anynul, status);
+    }
+    else if (datatype == TFLOAT)
+    {
+      ffgcle(fptr, colnum, firstrow, firstelem, nelem, 1, 2, *(float *)
+               nulval,(float *) array, nullarray, anynul, status);
+    }
+    else if (datatype == TDOUBLE)
+    {
+        ffgcld(fptr, colnum, firstrow, firstelem, nelem, 1, 2, *(double *)
+              nulval, (double *) array, nullarray, anynul, status);
+    }
+    else if (datatype == TCOMPLEX)
+    {
+        ffgcfc(fptr, colnum, firstrow, firstelem, nelem,
+           (float *) array, nullarray, anynul, status);
+    }
+    else if (datatype == TDBLCOMPLEX)
+    {
+        ffgcfm(fptr, colnum, firstrow, firstelem, nelem, 
+           (double *) array, nullarray, anynul, status);
+    }
+
+    else if (datatype == TLOGICAL)
+    {
+        ffgcll(fptr, colnum, firstrow, firstelem, nelem, 2, *(char *) nulval,
+          (char *) array, nullarray, anynul, status);
+    }
+    else if (datatype == TSTRING)
+    {
+        ffgcls(fptr, colnum, firstrow, firstelem, nelem, 2, (char *)
+             nulval, (char **) array, nullarray, anynul, status);
+    }
+    else
+      *status = BAD_DATATYPE;
+
+    return(*status);
+}
+
diff --git a/src/plugins/cfitsio/getcolb.c b/src/plugins/cfitsio/getcolb.c
new file mode 100644
index 0000000..4fff57d
--- /dev/null
+++ b/src/plugins/cfitsio/getcolb.c
@@ -0,0 +1,2001 @@
+/*  This file, getcolb.c, contains routines that read data elements from   */
+/*  a FITS image or table, with unsigned char (unsigned byte) data type.   */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+
+#include <math.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <string.h>
+#include "fitsio2.h"
+
+/*--------------------------------------------------------------------------*/
+int ffgpvb( fitsfile *fptr,   /* I - FITS file pointer                       */
+            long  group,      /* I - group to read (1 = 1st group)           */
+            LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+            LONGLONG  nelem,      /* I - number of values to read                */
+            unsigned char nulval, /* I - value for undefined pixels          */
+            unsigned char *array, /* O - array of values that are returned   */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+  Undefined elements will be set equal to NULVAL, unless NULVAL=0
+  in which case no checking for undefined values will be performed.
+  ANYNUL is returned with a value of .true. if any pixels are undefined.
+*/
+{
+    long row;
+    char cdummy;
+    int nullcheck = 1;
+    unsigned char nullvalue;
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+         nullvalue = nulval;  /* set local variable */
+
+        fits_read_compressed_pixels(fptr, TBYTE, firstelem, nelem,
+            nullcheck, &nullvalue, array, NULL, anynul, status);
+        return(*status);
+    }
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffgclb(fptr, 2, row, firstelem, nelem, 1, 1, nulval,
+               array, &cdummy, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgpfb( fitsfile *fptr,   /* I - FITS file pointer                       */
+            long  group,      /* I - group to read (1 = 1st group)           */
+            LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+            LONGLONG  nelem,      /* I - number of values to read                */
+            unsigned char *array, /* O - array of values that are returned   */
+            char *nularray,   /* O - array of null pixel flags               */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+  Any undefined pixels in the returned array will be set = 0 and the 
+  corresponding nularray value will be set = 1.
+  ANYNUL is returned with a value of .true. if any pixels are undefined.
+*/
+{
+    long row;
+    int nullcheck = 2;
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        fits_read_compressed_pixels(fptr, TBYTE, firstelem, nelem,
+            nullcheck, NULL, array, nularray, anynul, status);
+        return(*status);
+    }
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffgclb(fptr, 2, row, firstelem, nelem, 1, 2, 0,
+               array, nularray, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffg2db(fitsfile *fptr,  /* I - FITS file pointer                       */
+           long  group,     /* I - group to read (1 = 1st group)           */
+           unsigned char nulval, /* set undefined pixels equal to this     */
+           LONGLONG  ncols,     /* I - number of pixels in each row of array   */
+           LONGLONG  naxis1,    /* I - FITS image NAXIS1 value                 */
+           LONGLONG  naxis2,    /* I - FITS image NAXIS2 value                 */
+           unsigned char *array, /* O - array to be filled and returned    */
+           int  *anynul,    /* O - set to 1 if any values are null; else 0 */
+           int  *status)    /* IO - error status                           */
+/*
+  Read an entire 2-D array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being read).  Any null
+  values in the array will be set equal to the value of nulval, unless
+  nulval = 0 in which case no null checking will be performed.
+*/
+{
+    /* call the 3D reading routine, with the 3rd dimension = 1 */
+
+    ffg3db(fptr, group, nulval, ncols, naxis2, naxis1, naxis2, 1, array, 
+           anynul, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffg3db(fitsfile *fptr,  /* I - FITS file pointer                       */
+           long  group,     /* I - group to read (1 = 1st group)           */
+           unsigned char nulval, /* set undefined pixels equal to this     */
+           LONGLONG  ncols,     /* I - number of pixels in each row of array   */
+           LONGLONG  nrows,     /* I - number of rows in each plane of array   */
+           LONGLONG  naxis1,    /* I - FITS image NAXIS1 value                 */
+           LONGLONG  naxis2,    /* I - FITS image NAXIS2 value                 */
+           LONGLONG  naxis3,    /* I - FITS image NAXIS3 value                 */
+           unsigned char *array, /* O - array to be filled and returned    */
+           int  *anynul,    /* O - set to 1 if any values are null; else 0 */
+           int  *status)    /* IO - error status                           */
+/*
+  Read an entire 3-D array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being read).  Any null
+  values in the array will be set equal to the value of nulval, unless
+  nulval = 0 in which case no null checking will be performed.
+*/
+{
+    long tablerow, ii, jj;
+    LONGLONG narray, nfits;
+    char cdummy;
+    int  nullcheck = 1;
+    long inc[] = {1,1,1};
+    LONGLONG fpixel[] = {1,1,1};
+    LONGLONG lpixel[3];
+    unsigned char nullvalue;
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        lpixel[0] = ncols;
+        lpixel[1] = nrows;
+        lpixel[2] = naxis3;
+        nullvalue = nulval;  /* set local variable */
+
+        fits_read_compressed_img(fptr, TBYTE, fpixel, lpixel, inc,
+            nullcheck, &nullvalue, array, NULL, anynul, status);
+        return(*status);
+    }
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+    tablerow=maxvalue(1,group);
+
+    if (ncols == naxis1 && nrows == naxis2)  /* arrays have same size? */
+    {
+       /* all the image pixels are contiguous, so read all at once */
+       ffgclb(fptr, 2, tablerow, 1, naxis1 * naxis2 * naxis3, 1, 1, nulval,
+               array, &cdummy, anynul, status);
+       return(*status);
+    }
+
+    if (ncols < naxis1 || nrows < naxis2)
+       return(*status = BAD_DIMEN);
+
+    nfits = 1;   /* next pixel in FITS image to read */
+    narray = 0;  /* next pixel in output array to be filled */
+
+    /* loop over naxis3 planes in the data cube */
+    for (jj = 0; jj < naxis3; jj++)
+    {
+      /* loop over the naxis2 rows in the FITS image, */
+      /* reading naxis1 pixels to each row            */
+
+      for (ii = 0; ii < naxis2; ii++)
+      {
+       if (ffgclb(fptr, 2, tablerow, nfits, naxis1, 1, 1, nulval,
+          &array[narray], &cdummy, anynul, status) > 0)
+          return(*status);
+
+       nfits += naxis1;
+       narray += ncols;
+      }
+      narray += (nrows - naxis2) * ncols;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgsvb(fitsfile *fptr, /* I - FITS file pointer                         */
+           int  colnum,    /* I - number of the column to read (1 = 1st)    */
+           int naxis,      /* I - number of dimensions in the FITS array    */
+           long  *naxes,   /* I - size of each dimension                    */
+           long  *blc,     /* I - 'bottom left corner' of the subsection    */
+           long  *trc,     /* I - 'top right corner' of the subsection      */
+           long  *inc,     /* I - increment to be applied in each dimension */
+           unsigned char nulval, /* I - value to set undefined pixels       */
+           unsigned char *array, /* O - array to be filled and returned     */
+           int  *anynul,   /* O - set to 1 if any values are null; else 0   */
+           int  *status)   /* IO - error status                             */
+/*
+  Read a subsection of data values from an image or a table column.
+  This routine is set up to handle a maximum of nine dimensions.
+*/
+{
+    long ii, i0, i1, i2, i3, i4, i5, i6, i7, i8, row, rstr, rstp, rinc;
+    long str[9], stp[9], incr[9], dir[9];
+    long nelem, nultyp, ninc, numcol;
+    LONGLONG felem, dsize[10], blcll[9], trcll[9];
+    int hdutype, anyf;
+    char ldummy, msg[FLEN_ERRMSG];
+    int  nullcheck = 1;
+    unsigned char nullvalue;
+
+    if (naxis < 1 || naxis > 9)
+    {
+        sprintf(msg, "NAXIS = %d in call to ffgsvb is out of range", naxis);
+        ffpmsg(msg);
+        return(*status = BAD_DIMEN);
+    }
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        for (ii=0; ii < naxis; ii++) {
+	    blcll[ii] = blc[ii];
+	    trcll[ii] = trc[ii];
+	}
+
+        nullvalue = nulval;  /* set local variable */
+
+        fits_read_compressed_img(fptr, TBYTE, blcll, trcll, inc,
+            nullcheck, &nullvalue, array, NULL, anynul, status);
+        return(*status);
+    }
+
+/*
+    if this is a primary array, then the input COLNUM parameter should
+    be interpreted as the row number, and we will alway read the image
+    data from column 2 (any group parameters are in column 1).
+*/
+    if (ffghdt(fptr, &hdutype, status) > 0)
+        return(*status);
+
+    if (hdutype == IMAGE_HDU)
+    {
+        /* this is a primary array, or image extension */
+        if (colnum == 0)
+        {
+            rstr = 1;
+            rstp = 1;
+        }
+        else
+        {
+            rstr = colnum;
+            rstp = colnum;
+        }
+        rinc = 1;
+        numcol = 2;
+    }
+    else
+    {
+        /* this is a table, so the row info is in the (naxis+1) elements */
+        rstr = blc[naxis];
+        rstp = trc[naxis];
+        rinc = inc[naxis];
+        numcol = colnum;
+    }
+
+    nultyp = 1;
+    if (anynul)
+        *anynul = FALSE;
+
+    i0 = 0;
+    for (ii = 0; ii < 9; ii++)
+    {
+        str[ii] = 1;
+        stp[ii] = 1;
+        incr[ii] = 1;
+        dsize[ii] = 1;
+        dir[ii] = 1;
+    }
+
+    for (ii = 0; ii < naxis; ii++)
+    {
+      if (trc[ii] < blc[ii])
+      {
+        if (hdutype == IMAGE_HDU)
+        {
+           dir[ii] = -1;
+        }
+        else
+        {
+          sprintf(msg, "ffgsvb: illegal range specified for axis %ld", ii + 1);
+          ffpmsg(msg);
+          return(*status = BAD_PIX_NUM);
+        }
+      }
+
+      str[ii] = blc[ii];
+      stp[ii] = trc[ii];
+      incr[ii] = inc[ii];
+      dsize[ii + 1] = dsize[ii] * naxes[ii];
+      dsize[ii] = dsize[ii] * dir[ii];
+    }
+    dsize[naxis] = dsize[naxis] * dir[naxis];
+
+    if (naxis == 1 && naxes[0] == 1)
+    {
+      /* This is not a vector column, so read all the rows at once */
+      nelem = (rstp - rstr) / rinc + 1;
+      ninc = rinc;
+      rstp = rstr;
+    }
+    else
+    {
+      /* have to read each row individually, in all dimensions */
+      nelem = (stp[0]*dir[0] - str[0]*dir[0]) / inc[0] + 1;
+      ninc = incr[0] * dir[0];
+    }
+
+    for (row = rstr; row <= rstp; row += rinc)
+    {
+     for (i8 = str[8]*dir[8]; i8 <= stp[8]*dir[8]; i8 += incr[8])
+     {
+      for (i7 = str[7]*dir[7]; i7 <= stp[7]*dir[7]; i7 += incr[7])
+      {
+       for (i6 = str[6]*dir[6]; i6 <= stp[6]*dir[6]; i6 += incr[6])
+       {
+        for (i5 = str[5]*dir[5]; i5 <= stp[5]*dir[5]; i5 += incr[5])
+        {
+         for (i4 = str[4]*dir[4]; i4 <= stp[4]*dir[4]; i4 += incr[4])
+         {
+          for (i3 = str[3]*dir[3]; i3 <= stp[3]*dir[3]; i3 += incr[3])
+          {
+           for (i2 = str[2]*dir[2]; i2 <= stp[2]*dir[2]; i2 += incr[2])
+           {
+            for (i1 = str[1]*dir[1]; i1 <= stp[1]*dir[1]; i1 += incr[1])
+            {
+
+              felem=str[0] + (i1 - dir[1]) * dsize[1] + (i2 - dir[2]) * dsize[2] + 
+                             (i3 - dir[3]) * dsize[3] + (i4 - dir[4]) * dsize[4] +
+                             (i5 - dir[5]) * dsize[5] + (i6 - dir[6]) * dsize[6] +
+                             (i7 - dir[7]) * dsize[7] + (i8 - dir[8]) * dsize[8];
+
+              if ( ffgclb(fptr, numcol, row, felem, nelem, ninc, nultyp,
+                   nulval, &array[i0], &ldummy, &anyf, status) > 0)
+                   return(*status);
+
+              if (anyf && anynul)
+                  *anynul = TRUE;
+
+              i0 += nelem;
+            }
+           }
+          }
+         }
+        }
+       }
+      }
+     }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgsfb(fitsfile *fptr, /* I - FITS file pointer                         */
+           int  colnum,    /* I - number of the column to read (1 = 1st)    */
+           int naxis,      /* I - number of dimensions in the FITS array    */
+           long  *naxes,   /* I - size of each dimension                    */
+           long  *blc,     /* I - 'bottom left corner' of the subsection    */
+           long  *trc,     /* I - 'top right corner' of the subsection      */
+           long  *inc,     /* I - increment to be applied in each dimension */
+           unsigned char *array, /* O - array to be filled and returned     */
+           char *flagval,  /* O - set to 1 if corresponding value is null   */
+           int  *anynul,   /* O - set to 1 if any values are null; else 0   */
+           int  *status)   /* IO - error status                             */
+/*
+  Read a subsection of data values from an image or a table column.
+  This routine is set up to handle a maximum of nine dimensions.
+*/
+{
+    long ii,i0, i1,i2,i3,i4,i5,i6,i7,i8,row,rstr,rstp,rinc;
+    long str[9],stp[9],incr[9],dsize[10];
+    LONGLONG blcll[9], trcll[9];
+    long felem, nelem, nultyp, ninc, numcol;
+    int hdutype, anyf;
+    unsigned char nulval = 0;
+    char msg[FLEN_ERRMSG];
+    int  nullcheck = 2;
+
+    if (naxis < 1 || naxis > 9)
+    {
+        sprintf(msg, "NAXIS = %d in call to ffgsvb is out of range", naxis);
+        ffpmsg(msg);
+        return(*status = BAD_DIMEN);
+    }
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        for (ii=0; ii < naxis; ii++) {
+	    blcll[ii] = blc[ii];
+	    trcll[ii] = trc[ii];
+	}
+
+        fits_read_compressed_img(fptr, TBYTE, blcll, trcll, inc,
+            nullcheck, NULL, array, flagval, anynul, status);
+        return(*status);
+    }
+
+/*
+    if this is a primary array, then the input COLNUM parameter should
+    be interpreted as the row number, and we will alway read the image
+    data from column 2 (any group parameters are in column 1).
+*/
+    if (ffghdt(fptr, &hdutype, status) > 0)
+        return(*status);
+
+    if (hdutype == IMAGE_HDU)
+    {
+        /* this is a primary array, or image extension */
+        if (colnum == 0)
+        {
+            rstr = 1;
+            rstp = 1;
+        }
+        else
+        {
+            rstr = colnum;
+            rstp = colnum;
+        }
+        rinc = 1;
+        numcol = 2;
+    }
+    else
+    {
+        /* this is a table, so the row info is in the (naxis+1) elements */
+        rstr = blc[naxis];
+        rstp = trc[naxis];
+        rinc = inc[naxis];
+        numcol = colnum;
+    }
+
+    nultyp = 2;
+    if (anynul)
+        *anynul = FALSE;
+
+    i0 = 0;
+    for (ii = 0; ii < 9; ii++)
+    {
+        str[ii] = 1;
+        stp[ii] = 1;
+        incr[ii] = 1;
+        dsize[ii] = 1;
+    }
+
+    for (ii = 0; ii < naxis; ii++)
+    {
+      if (trc[ii] < blc[ii])
+      {
+        sprintf(msg, "ffgsvb: illegal range specified for axis %ld", ii + 1);
+        ffpmsg(msg);
+        return(*status = BAD_PIX_NUM);
+      }
+
+      str[ii] = blc[ii];
+      stp[ii] = trc[ii];
+      incr[ii] = inc[ii];
+      dsize[ii + 1] = dsize[ii] * naxes[ii];
+    }
+
+    if (naxis == 1 && naxes[0] == 1)
+    {
+      /* This is not a vector column, so read all the rows at once */
+      nelem = (rstp - rstr) / rinc + 1;
+      ninc = rinc;
+      rstp = rstr;
+    }
+    else
+    {
+      /* have to read each row individually, in all dimensions */
+      nelem = (stp[0] - str[0]) / inc[0] + 1;
+      ninc = incr[0];
+    }
+
+    for (row = rstr; row <= rstp; row += rinc)
+    {
+     for (i8 = str[8]; i8 <= stp[8]; i8 += incr[8])
+     {
+      for (i7 = str[7]; i7 <= stp[7]; i7 += incr[7])
+      {
+       for (i6 = str[6]; i6 <= stp[6]; i6 += incr[6])
+       {
+        for (i5 = str[5]; i5 <= stp[5]; i5 += incr[5])
+        {
+         for (i4 = str[4]; i4 <= stp[4]; i4 += incr[4])
+         {
+          for (i3 = str[3]; i3 <= stp[3]; i3 += incr[3])
+          {
+           for (i2 = str[2]; i2 <= stp[2]; i2 += incr[2])
+           {
+            for (i1 = str[1]; i1 <= stp[1]; i1 += incr[1])
+            {
+              felem=str[0] + (i1 - 1) * dsize[1] + (i2 - 1) * dsize[2] + 
+                             (i3 - 1) * dsize[3] + (i4 - 1) * dsize[4] +
+                             (i5 - 1) * dsize[5] + (i6 - 1) * dsize[6] +
+                             (i7 - 1) * dsize[7] + (i8 - 1) * dsize[8];
+
+              if ( ffgclb(fptr, numcol, row, felem, nelem, ninc, nultyp,
+                   nulval, &array[i0], &flagval[i0], &anyf, status) > 0)
+                   return(*status);
+
+              if (anyf && anynul)
+                  *anynul = TRUE;
+
+              i0 += nelem;
+            }
+           }
+          }
+         }
+        }
+       }
+      }
+     }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffggpb( fitsfile *fptr,   /* I - FITS file pointer                       */
+            long  group,      /* I - group to read (1 = 1st group)           */
+            long  firstelem,  /* I - first vector element to read (1 = 1st)  */
+            long  nelem,      /* I - number of values to read                */
+            unsigned char *array, /* O - array of values that are returned   */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of group parameters from the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+*/
+{
+    long row;
+    int idummy;
+    char cdummy;
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffgclb(fptr, 1, row, firstelem, nelem, 1, 1, 0,
+               array, &cdummy, &idummy, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcvb(fitsfile *fptr,   /* I - FITS file pointer                       */
+           int  colnum,      /* I - number of column to read (1 = 1st col)  */
+           LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+           LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+           LONGLONG  nelem,      /* I - number of values to read                */
+           unsigned char nulval, /* I - value for null pixels               */
+           unsigned char *array, /* O - array of values that are read       */
+           int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+           int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from a column in the current FITS HDU. Automatic
+  datatype conversion will be performed if the datatype of the column does not
+  match the datatype of the array parameter. The output values will be scaled 
+  by the FITS TSCALn and TZEROn values if these values have been defined.
+  Any undefined pixels will be set equal to the value of 'nulval' unless
+  nulval = 0 in which case no checks for undefined pixels will be made.
+*/
+{
+    char cdummy;
+
+    ffgclb(fptr, colnum, firstrow, firstelem, nelem, 1, 1, nulval,
+           array, &cdummy, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcfb(fitsfile *fptr,   /* I - FITS file pointer                       */
+           int  colnum,      /* I - number of column to read (1 = 1st col)  */
+           LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+           LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+           LONGLONG  nelem,      /* I - number of values to read                */
+           unsigned char *array, /* O - array of values that are read       */
+           char *nularray,   /* O - array of flags: 1 if null pixel; else 0 */
+           int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+           int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from a column in the current FITS HDU. Automatic
+  datatype conversion will be performed if the datatype of the column does not
+  match the datatype of the array parameter. The output values will be scaled 
+  by the FITS TSCALn and TZEROn values if these values have been defined.
+  Nularray will be set = 1 if the corresponding array pixel is undefined, 
+  otherwise nularray will = 0.
+*/
+{
+    unsigned char dummy = 0;
+
+    ffgclb(fptr, colnum, firstrow, firstelem, nelem, 1, 2, dummy,
+           array, nularray, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgclb( fitsfile *fptr,   /* I - FITS file pointer                       */
+            int  colnum,      /* I - number of column to read (1 = 1st col)  */
+            LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+            LONGLONG firstelem,  /* I - first vector element to read (1 = 1st)  */
+            LONGLONG  nelem,      /* I - number of values to read                */
+            long  elemincre,  /* I - pixel increment; e.g., 2 = every other  */
+            int   nultyp,     /* I - null value handling code:               */
+                              /*     1: set undefined pixels = nulval        */
+                              /*     2: set nularray=1 for undefined pixels  */
+            unsigned char nulval, /* I - value for null pixels if nultyp = 1 */
+            unsigned char *array, /* O - array of values that are read       */
+            char *nularray,   /* O - array of flags = 1 if nultyp = 2        */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from a column in the current FITS HDU.
+  The column number may refer to a real column in an ASCII or binary table, 
+  or it may refer be a virtual column in a 1 or more grouped FITS primary
+  array or image extension.  FITSIO treats a primary array as a binary table
+  with 2 vector columns: the first column contains the group parameters (often
+  with length = 0) and the second column contains the array of image pixels.
+  Each row of the table represents a group in the case of multigroup FITS
+  images.
+
+  The output array of values will be converted from the datatype of the column 
+  and will be scaled by the FITS TSCALn and TZEROn values if necessary.
+*/
+{
+    double scale, zero, power = 1., dtemp;
+    int tcode, maxelem, hdutype, xcode, decimals;
+    long twidth, incre, ntodo;
+    long ii, xwidth;
+    int convert, nulcheck, readcheck = 0;
+    LONGLONG repeat, startpos, elemnum, readptr, tnull;
+    LONGLONG rowlen, rownum, remain, next, rowincre;
+    char tform[20];
+    char message[81];
+    char snull[20];   /*  the FITS null value if reading from ASCII table  */
+
+    double cbuff[DBUFFSIZE / sizeof(double)]; /* align cbuff on word boundary */
+    void *buffer;
+
+    union u_tag {
+       char charval;
+       unsigned char ucharval;
+    } u;
+
+    if (*status > 0 || nelem == 0)  /* inherit input status value if > 0 */
+        return(*status);
+
+    buffer = cbuff;
+
+    if (anynul)
+        *anynul = 0;
+
+    if (nultyp == 2)      
+       memset(nularray, 0, (size_t) nelem);   /* initialize nullarray */
+
+    /*---------------------------------------------------*/
+    /*  Check input and get parameters about the column: */
+    /*---------------------------------------------------*/
+    if (elemincre < 0)
+        readcheck = -1;  /* don't do range checking in this case */
+
+    ffgcprll( fptr, colnum, firstrow, firstelem, nelem, readcheck, &scale, &zero,
+         tform, &twidth, &tcode, &maxelem, &startpos, &elemnum, &incre,
+         &repeat, &rowlen, &hdutype, &tnull, snull, status);
+
+    /* special case */
+    if (tcode == TLOGICAL && elemincre == 1)
+    {
+        u.ucharval = nulval;
+        ffgcll(fptr, colnum, firstrow, firstelem, nelem, nultyp,
+               u.charval, (char *) array, nularray, anynul, status);
+
+        return(*status);
+    }
+
+    if (strchr(tform,'A') != NULL) 
+    {
+        if (*status == BAD_ELEM_NUM)
+        {
+            /* ignore this error message */
+            *status = 0;
+            ffcmsg();   /* clear error stack */
+        }
+
+        /*  interpret a 'A' ASCII column as a 'B' byte column ('8A' == '8B') */
+        /*  This is an undocumented 'feature' in CFITSIO */
+
+        /*  we have to reset some of the values returned by ffgcpr */
+        
+        tcode = TBYTE;
+        incre = 1;         /* each element is 1 byte wide */
+        repeat = twidth;   /* total no. of chars in the col */
+        twidth = 1;        /* width of each element */
+        scale = 1.0;       /* no scaling */
+        zero  = 0.0;
+        tnull = NULL_UNDEFINED;  /* don't test for nulls */
+        maxelem = DBUFFSIZE;
+    }
+
+    if (*status > 0)
+        return(*status);
+        
+    incre *= elemincre;   /* multiply incre to just get every nth pixel */
+
+    if (tcode == TSTRING && hdutype == ASCII_TBL) /* setup for ASCII tables */
+    {
+      /* get the number of implied decimal places if no explicit decmal point */
+      ffasfm(tform, &xcode, &xwidth, &decimals, status); 
+      for(ii = 0; ii < decimals; ii++)
+        power *= 10.;
+    }
+    /*------------------------------------------------------------------*/
+    /*  Decide whether to check for null values in the input FITS file: */
+    /*------------------------------------------------------------------*/
+    nulcheck = nultyp; /* by default, check for null values in the FITS file */
+
+    if (nultyp == 1 && nulval == 0)
+       nulcheck = 0;    /* calling routine does not want to check for nulls */
+
+    else if (tcode%10 == 1 &&        /* if reading an integer column, and  */ 
+            tnull == NULL_UNDEFINED) /* if a null value is not defined,    */
+            nulcheck = 0;            /* then do not check for null values. */
+
+    else if (tcode == TSHORT && (tnull > SHRT_MAX || tnull < SHRT_MIN) )
+            nulcheck = 0;            /* Impossible null value */
+
+    else if (tcode == TBYTE && (tnull > 255 || tnull < 0) )
+            nulcheck = 0;            /* Impossible null value */
+
+    else if (tcode == TSTRING && snull[0] == ASCII_NULL_UNDEFINED)
+         nulcheck = 0;
+
+    /*----------------------------------------------------------------------*/
+    /*  If FITS column and output data array have same datatype, then we do */
+    /*  not need to use a temporary buffer to store intermediate datatype.  */
+    /*----------------------------------------------------------------------*/
+    convert = 1;
+    if (tcode == TBYTE) /* Special Case:                        */
+    {                             /* no type convertion required, so read */
+        maxelem = (int) nelem;          /* data directly into output buffer.    */
+
+        if (nulcheck == 0 && scale == 1. && zero == 0.)
+            convert = 0;  /* no need to scale data or find nulls */
+    }
+
+    /*---------------------------------------------------------------------*/
+    /*  Now read the pixels from the FITS column. If the column does not   */
+    /*  have the same datatype as the output array, then we have to read   */
+    /*  the raw values into a temporary buffer (of limited size).  In      */
+    /*  the case of a vector colum read only 1 vector of values at a time  */
+    /*  then skip to the next row if more values need to be read.          */
+    /*  After reading the raw values, then call the fffXXYY routine to (1) */
+    /*  test for undefined values, (2) convert the datatype if necessary,  */
+    /*  and (3) scale the values by the FITS TSCALn and TZEROn linear      */
+    /*  scaling parameters.                                                */
+    /*---------------------------------------------------------------------*/
+    remain = nelem;           /* remaining number of values to read */
+    next = 0;                 /* next element in array to be read   */
+    rownum = 0;               /* row number, relative to firstrow   */
+
+    while (remain)
+    {
+        /* limit the number of pixels to read at one time to the number that
+           will fit in the buffer or to the number of pixels that remain in
+           the current vector, which ever is smaller.
+        */
+        ntodo = (long) minvalue(remain, maxelem);
+        if (elemincre >= 0)
+        {
+          ntodo = (long) minvalue(ntodo, ((repeat - elemnum - 1)/elemincre +1));
+        }
+        else
+        {
+          ntodo = (long) minvalue(ntodo, (elemnum/(-elemincre) +1));
+        }
+
+        readptr = startpos + ((LONGLONG)rownum * rowlen) + (elemnum * (incre / elemincre));
+
+        switch (tcode) 
+        {
+            case (TBYTE):
+                ffgi1b(fptr, readptr, ntodo, incre, &array[next], status);
+                if (convert)
+                    fffi1i1(&array[next], ntodo, scale, zero, nulcheck, 
+                    (unsigned char) tnull, nulval, &nularray[next], anynul, 
+                           &array[next], status);
+                break;
+            case (TSHORT):
+                ffgi2b(fptr, readptr, ntodo, incre, (short *) buffer, status);
+                fffi2i1((short  *) buffer, ntodo, scale, zero, nulcheck, 
+                       (short) tnull, nulval, &nularray[next], anynul, 
+                       &array[next], status);
+                break;
+            case (TLONG):
+                ffgi4b(fptr, readptr, ntodo, incre, (INT32BIT *) buffer,
+                       status);
+                fffi4i1((INT32BIT *) buffer, ntodo, scale, zero, nulcheck, 
+                       (INT32BIT) tnull, nulval, &nularray[next], anynul, 
+                       &array[next], status);
+                break;
+            case (TLONGLONG):
+                ffgi8b(fptr, readptr, ntodo, incre, (long *) buffer, status);
+                fffi8i1( (LONGLONG *) buffer, ntodo, scale, zero, 
+                           nulcheck, tnull, nulval, &nularray[next], 
+                            anynul, &array[next], status);
+                break;
+            case (TFLOAT):
+                ffgr4b(fptr, readptr, ntodo, incre, (float  *) buffer, status);
+                fffr4i1((float  *) buffer, ntodo, scale, zero, nulcheck, 
+                       nulval, &nularray[next], anynul, 
+                       &array[next], status);
+                break;
+            case (TDOUBLE):
+                ffgr8b(fptr, readptr, ntodo, incre, (double *) buffer, status);
+                fffr8i1((double *) buffer, ntodo, scale, zero, nulcheck, 
+                          nulval, &nularray[next], anynul, 
+                          &array[next], status);
+                break;
+            case (TSTRING):
+                ffmbyt(fptr, readptr, REPORT_EOF, status);
+       
+                if (incre == twidth)    /* contiguous bytes */
+                     ffgbyt(fptr, ntodo * twidth, buffer, status);
+                else
+                     ffgbytoff(fptr, twidth, ntodo, incre - twidth, buffer,
+                               status);
+
+                /* interpret the string as an ASCII formated number */
+                fffstri1((char *) buffer, ntodo, scale, zero, twidth, power,
+                      nulcheck, snull, nulval, &nularray[next], anynul,
+                      &array[next], status);
+                break;
+
+            default:  /*  error trap for invalid column format */
+                sprintf(message, 
+                   "Cannot read bytes from column %d which has format %s",
+                    colnum, tform);
+                ffpmsg(message);
+                if (hdutype == ASCII_TBL)
+                    return(*status = BAD_ATABLE_FORMAT);
+                else
+                    return(*status = BAD_BTABLE_FORMAT);
+
+        } /* End of switch block */
+
+        /*-------------------------*/
+        /*  Check for fatal error  */
+        /*-------------------------*/
+        if (*status > 0)  /* test for error during previous read operation */
+        {
+	  dtemp = (double) next;
+          if (hdutype > 0)
+            sprintf(message,
+            "Error reading elements %.0f thru %.0f from column %d (ffgclb).",
+              dtemp+1., dtemp+ntodo, colnum);
+          else
+            sprintf(message,
+            "Error reading elements %.0f thru %.0f from image (ffgclb).",
+              dtemp+1., dtemp+ntodo);
+
+         ffpmsg(message);
+         return(*status);
+        }
+
+        /*--------------------------------------------*/
+        /*  increment the counters for the next loop  */
+        /*--------------------------------------------*/
+        remain -= ntodo;
+        if (remain)
+        {
+            next += ntodo;
+            elemnum = elemnum + (ntodo * elemincre);
+
+            if (elemnum >= repeat)  /* completed a row; start on later row */
+            {
+                rowincre = elemnum / repeat;
+                rownum += rowincre;
+                elemnum = elemnum - (rowincre * repeat);
+            }
+            else if (elemnum < 0)  /* completed a row; start on a previous row */
+            {
+                rowincre = (-elemnum - 1) / repeat + 1;
+                rownum -= rowincre;
+                elemnum = (rowincre * repeat) + elemnum;
+            }
+        }
+    }  /*  End of main while Loop  */
+
+
+    /*--------------------------------*/
+    /*  check for numerical overflow  */
+    /*--------------------------------*/
+    if (*status == OVERFLOW_ERR)
+    {
+        ffpmsg(
+        "Numerical overflow during type conversion while reading FITS data.");
+        *status = NUM_OVERFLOW;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgextn( fitsfile *fptr,        /* I - FITS file pointer                        */
+            LONGLONG  offset,      /* I - byte offset from start of extension data */
+            LONGLONG  nelem,       /* I - number of elements to read               */
+            void *buffer,          /* I - stream of bytes to read                  */
+            int  *status)          /* IO - error status                            */
+/*
+  Read a stream of bytes from the current FITS HDU.  This primative routine is mainly
+  for reading non-standard "conforming" extensions and should not be used
+  for standard IMAGE, TABLE or BINTABLE extensions.
+*/
+{
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    /* rescan header if data structure is undefined */
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+        if ( ffrdef(fptr, status) > 0)               
+            return(*status);
+
+    /* move to write position */
+    ffmbyt(fptr, (fptr->Fptr)->datastart+ offset, IGNORE_EOF, status);
+    
+    /* read the buffer */
+    ffgbyt(fptr, nelem, buffer, status); 
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi1i1(unsigned char *input, /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            unsigned char tnull,  /* I - value of FITS TNULLn keyword if any */
+            unsigned char nullval,/* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            unsigned char *output,/* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {              /* this routine is normally not called in this case */
+           memcpy(output, input, ntodo );
+        }
+        else             /* must scale the data */
+        {                
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DUCHAR_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else if (dvalue > DUCHAR_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = UCHAR_MAX;
+                }
+                else
+                    output[ii] = (unsigned char) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                    output[ii] = input[ii];
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DUCHAR_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (dvalue > DUCHAR_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = UCHAR_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned char) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi2i1(short *input,         /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            short tnull,          /* I - value of FITS TNULLn keyword if any */
+            unsigned char nullval,/* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            unsigned char *output,/* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] < 0)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else if (input[ii] > UCHAR_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = UCHAR_MAX;
+                }
+                else
+                    output[ii] = (unsigned char) input[ii];
+            }
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DUCHAR_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else if (dvalue > DUCHAR_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = UCHAR_MAX;
+                }
+                else
+                    output[ii] = (unsigned char) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+
+                else
+                {
+                    if (input[ii] < 0)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (input[ii] > UCHAR_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = UCHAR_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned char) input[ii];
+                }
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DUCHAR_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (dvalue > DUCHAR_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = UCHAR_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned char) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi4i1(INT32BIT *input,          /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            INT32BIT tnull,       /* I - value of FITS TNULLn keyword if any */
+            unsigned char nullval,/* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            unsigned char *output,/* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] < 0)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else if (input[ii] > UCHAR_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = UCHAR_MAX;
+                }
+                else
+                    output[ii] = (unsigned char) input[ii];
+            }
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DUCHAR_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else if (dvalue > DUCHAR_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = UCHAR_MAX;
+                }
+                else
+                    output[ii] = (unsigned char) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    if (input[ii] < 0)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (input[ii] > UCHAR_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = UCHAR_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned char) input[ii];
+                }
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DUCHAR_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (dvalue > DUCHAR_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = UCHAR_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned char) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi8i1(LONGLONG *input,      /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            LONGLONG tnull,       /* I - value of FITS TNULLn keyword if any */
+            unsigned char nullval,/* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            unsigned char *output,/* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] < 0)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else if (input[ii] > UCHAR_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = UCHAR_MAX;
+                }
+                else
+                    output[ii] = (unsigned char) input[ii];
+            }
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DUCHAR_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else if (dvalue > DUCHAR_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = UCHAR_MAX;
+                }
+                else
+                    output[ii] = (unsigned char) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    if (input[ii] < 0)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (input[ii] > UCHAR_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = UCHAR_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned char) input[ii];
+                }
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DUCHAR_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (dvalue > DUCHAR_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = UCHAR_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned char) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffr4i1(float *input,         /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            unsigned char nullval,/* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            unsigned char *output,/* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to NaN.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+    short *sptr, iret;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] < DUCHAR_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else if (input[ii] > DUCHAR_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = UCHAR_MAX;
+                }
+                else
+                    output[ii] = (unsigned char) input[ii];
+            }
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DUCHAR_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else if (dvalue > DUCHAR_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = UCHAR_MAX;
+                }
+                else
+                    output[ii] = (unsigned char) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        sptr = (short *) input;
+
+#if BYTESWAPPED && MACHINE != VAXVMS && MACHINE != ALPHAVMS
+        sptr++;       /* point to MSBs */
+#endif
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++, sptr += 2)
+            {
+              /* use redundant boolean logic in following statement */
+              /* to suppress irritating Borland compiler warning message */
+              if (0 != (iret = fnan(*sptr) ) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                  }
+                  else            /* it's an underflow */
+                     output[ii] = 0;
+              }
+              else
+                {
+                    if (input[ii] < DUCHAR_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (input[ii] > DUCHAR_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = UCHAR_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned char) input[ii];
+                }
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++, sptr += 2)
+            {
+              if (0 != (iret = fnan(*sptr) ) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {  
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                  }
+                  else            /* it's an underflow */
+                  {
+                    if (zero < DUCHAR_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (zero > DUCHAR_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = UCHAR_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned char) zero;
+                  }
+              }
+              else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DUCHAR_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (dvalue > DUCHAR_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = UCHAR_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned char) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffr8i1(double *input,        /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            unsigned char nullval,/* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            unsigned char *output,/* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to NaN.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+    short *sptr, iret;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] < DUCHAR_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else if (input[ii] > DUCHAR_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = UCHAR_MAX;
+                }
+                else
+                    output[ii] = (unsigned char) input[ii];
+            }
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DUCHAR_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else if (dvalue > DUCHAR_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = UCHAR_MAX;
+                }
+                else
+                    output[ii] = (unsigned char) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        sptr = (short *) input;
+
+#if BYTESWAPPED && MACHINE != VAXVMS && MACHINE != ALPHAVMS
+        sptr += 3;       /* point to MSBs */
+#endif
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++, sptr += 4)
+            {
+              if (0 != (iret = dnan(*sptr)) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                  }
+                  else            /* it's an underflow */
+                     output[ii] = 0;
+              }
+              else
+                {
+                    if (input[ii] < DUCHAR_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (input[ii] > DUCHAR_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = UCHAR_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned char) input[ii];
+                }
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++, sptr += 4)
+            {
+              if (0 != (iret = dnan(*sptr)) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {  
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                  }
+                  else            /* it's an underflow */
+                  {
+                    if (zero < DUCHAR_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (zero > DUCHAR_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = UCHAR_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned char) zero;
+                  }
+              }
+              else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DUCHAR_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (dvalue > DUCHAR_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = UCHAR_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned char) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffstri1(char *input,         /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            long twidth,          /* I - width of each substring of chars    */
+            double implipower,    /* I - power of 10 of implied decimal      */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            char  *snull,         /* I - value of FITS null string, if any   */
+            unsigned char nullval, /* I - set null pixels, if nullcheck = 1  */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            unsigned char *output, /* O - array of converted pixels          */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file. Check
+  for null values and do scaling if required. The nullcheck code value
+  determines how any null values in the input array are treated. A null
+  value is an input pixel that is equal to snull.  If nullcheck= 0, then
+  no special checking for nulls is performed.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    int  nullen;
+    long ii;
+    double dvalue;
+    char *cstring, message[81];
+    char *cptr, *tpos;
+    char tempstore, chrzero = '0';
+    double val, power;
+    int exponent, sign, esign, decpt;
+
+    nullen = strlen(snull);
+    cptr = input;  /* pointer to start of input string */
+    for (ii = 0; ii < ntodo; ii++)
+    {
+      cstring = cptr;
+      /* temporarily insert a null terminator at end of the string */
+      tpos = cptr + twidth;
+      tempstore = *tpos;
+      *tpos = 0;
+
+      /* check if null value is defined, and if the    */
+      /* column string is identical to the null string */
+      if (snull[0] != ASCII_NULL_UNDEFINED && 
+         !strncmp(snull, cptr, nullen) )
+      {
+        if (nullcheck)  
+        {
+          *anynull = 1;    
+          if (nullcheck == 1)
+            output[ii] = nullval;
+          else
+            nullarray[ii] = 1;
+        }
+        cptr += twidth;
+      }
+      else
+      {
+        /* value is not the null value, so decode it */
+        /* remove any embedded blank characters from the string */
+
+        decpt = 0;
+        sign = 1;
+        val  = 0.;
+        power = 1.;
+        exponent = 0;
+        esign = 1;
+
+        while (*cptr == ' ')               /* skip leading blanks */
+           cptr++;
+
+        if (*cptr == '-' || *cptr == '+')  /* check for leading sign */
+        {
+          if (*cptr == '-')
+             sign = -1;
+
+          cptr++;
+
+          while (*cptr == ' ')         /* skip blanks between sign and value */
+            cptr++;
+        }
+
+        while (*cptr >= '0' && *cptr <= '9')
+        {
+          val = val * 10. + *cptr - chrzero;  /* accumulate the value */
+          cptr++;
+
+          while (*cptr == ' ')         /* skip embedded blanks in the value */
+            cptr++;
+        }
+
+        if (*cptr == '.' || *cptr == ',')       /* check for decimal point */
+        {
+          decpt = 1;
+          cptr++;
+          while (*cptr == ' ')         /* skip any blanks */
+            cptr++;
+
+          while (*cptr >= '0' && *cptr <= '9')
+          {
+            val = val * 10. + *cptr - chrzero;  /* accumulate the value */
+            power = power * 10.;
+            cptr++;
+
+            while (*cptr == ' ')         /* skip embedded blanks in the value */
+              cptr++;
+          }
+        }
+
+        if (*cptr == 'E' || *cptr == 'D')  /* check for exponent */
+        {
+          cptr++;
+          while (*cptr == ' ')         /* skip blanks */
+              cptr++;
+  
+          if (*cptr == '-' || *cptr == '+')  /* check for exponent sign */
+          {
+            if (*cptr == '-')
+               esign = -1;
+
+            cptr++;
+
+            while (*cptr == ' ')        /* skip blanks between sign and exp */
+              cptr++;
+          }
+
+          while (*cptr >= '0' && *cptr <= '9')
+          {
+            exponent = exponent * 10 + *cptr - chrzero;  /* accumulate exp */
+            cptr++;
+
+            while (*cptr == ' ')         /* skip embedded blanks */
+              cptr++;
+          }
+        }
+
+        if (*cptr  != 0)  /* should end up at the null terminator */
+        {
+          sprintf(message, "Cannot read number from ASCII table");
+          ffpmsg(message);
+          sprintf(message, "Column field = %s.", cstring);
+          ffpmsg(message);
+          /* restore the char that was overwritten by the null */
+          *tpos = tempstore;
+          return(*status = BAD_C2D);
+        }
+
+        if (!decpt)  /* if no explicit decimal, use implied */
+           power = implipower;
+
+        dvalue = (sign * val / power) * pow(10., (double) (esign * exponent));
+
+        dvalue = dvalue * scale + zero;   /* apply the scaling */
+
+        if (dvalue < DUCHAR_MIN)
+        {
+            *status = OVERFLOW_ERR;
+            output[ii] = 0;
+        }
+        else if (dvalue > DUCHAR_MAX)
+        {
+            *status = OVERFLOW_ERR;
+            output[ii] = UCHAR_MAX;
+        }
+        else
+            output[ii] = (unsigned char) dvalue;
+      }
+      /* restore the char that was overwritten by the null */
+      *tpos = tempstore;
+    }
+    return(*status);
+}
diff --git a/src/plugins/cfitsio/getcold.c b/src/plugins/cfitsio/getcold.c
new file mode 100644
index 0000000..8c33931
--- /dev/null
+++ b/src/plugins/cfitsio/getcold.c
@@ -0,0 +1,1676 @@
+/*  This file, getcold.c, contains routines that read data elements from   */
+/*  a FITS image or table, with double datatype.                           */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+
+#include <math.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <string.h>
+#include "fitsio2.h"
+
+/*--------------------------------------------------------------------------*/
+int ffgpvd( fitsfile *fptr,   /* I - FITS file pointer                       */
+            long  group,      /* I - group to read (1 = 1st group)           */
+            LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+            LONGLONG  nelem,      /* I - number of values to read                */
+            double nulval,    /* I - value for undefined pixels              */
+            double *array,    /* O - array of values that are returned       */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+  Undefined elements will be set equal to NULVAL, unless NULVAL=0
+  in which case no checking for undefined values will be performed.
+  ANYNUL is returned with a value of .true. if any pixels are undefined.
+*/
+{
+    long row;
+    char cdummy;
+    int nullcheck = 1;
+    double nullvalue;
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+         nullvalue = nulval;  /* set local variable */
+
+        fits_read_compressed_pixels(fptr, TDOUBLE, firstelem, nelem,
+            nullcheck, &nullvalue, array, NULL, anynul, status);
+        return(*status);
+    }
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffgcld(fptr, 2, row, firstelem, nelem, 1, 1, nulval,
+               array, &cdummy, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgpfd( fitsfile *fptr,   /* I - FITS file pointer                       */
+            long  group,      /* I - group to read (1 = 1st group)           */
+            LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+            LONGLONG  nelem,      /* I - number of values to read                */
+            double *array,    /* O - array of values that are returned       */
+            char *nularray,   /* O - array of null pixel flags               */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+  Any undefined pixels in the returned array will be set = 0 and the 
+  corresponding nularray value will be set = 1.
+  ANYNUL is returned with a value of .true. if any pixels are undefined.
+*/
+{
+    long row;
+    int nullcheck = 2;
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        fits_read_compressed_pixels(fptr, TDOUBLE, firstelem, nelem,
+            nullcheck, NULL, array, nularray, anynul, status);
+        return(*status);
+    }
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffgcld(fptr, 2, row, firstelem, nelem, 1, 2, 0.,
+               array, nularray, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffg2dd(fitsfile *fptr,  /* I - FITS file pointer                       */
+           long  group,     /* I - group to read (1 = 1st group)           */
+           double nulval,   /* set undefined pixels equal to this          */
+           LONGLONG  ncols,     /* I - number of pixels in each row of array   */
+           LONGLONG  naxis1,    /* I - FITS image NAXIS1 value                 */
+           LONGLONG  naxis2,    /* I - FITS image NAXIS2 value                 */
+           double *array,   /* O - array to be filled and returned         */
+           int  *anynul,    /* O - set to 1 if any values are null; else 0 */
+           int  *status)    /* IO - error status                           */
+/*
+  Read an entire 2-D array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being read).  Any null
+  values in the array will be set equal to the value of nulval, unless
+  nulval = 0 in which case no null checking will be performed.
+*/
+{
+    /* call the 3D reading routine, with the 3rd dimension = 1 */
+
+    ffg3dd(fptr, group, nulval, ncols, naxis2, naxis1, naxis2, 1, array, 
+           anynul, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffg3dd(fitsfile *fptr,  /* I - FITS file pointer                       */
+           long  group,     /* I - group to read (1 = 1st group)           */
+           double nulval,   /* set undefined pixels equal to this          */
+           LONGLONG  ncols,     /* I - number of pixels in each row of array   */
+           LONGLONG  nrows,     /* I - number of rows in each plane of array   */
+           LONGLONG  naxis1,    /* I - FITS image NAXIS1 value                 */
+           LONGLONG  naxis2,    /* I - FITS image NAXIS2 value                 */
+           LONGLONG  naxis3,    /* I - FITS image NAXIS3 value                 */
+           double *array,   /* O - array to be filled and returned         */
+           int  *anynul,    /* O - set to 1 if any values are null; else 0 */
+           int  *status)    /* IO - error status                           */
+/*
+  Read an entire 3-D array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being read).  Any null
+  values in the array will be set equal to the value of nulval, unless
+  nulval = 0 in which case no null checking will be performed.
+*/
+{
+    LONGLONG nfits, narray;
+    long tablerow, ii, jj;
+    char cdummy;
+    int nullcheck = 1;
+    long inc[] = {1,1,1};
+    LONGLONG fpixel[] = {1,1,1};
+    LONGLONG lpixel[3];
+    double nullvalue;
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        lpixel[0] =  (long) ncols;
+        lpixel[1] = (long) nrows;
+        lpixel[2] = (long) naxis3;
+        nullvalue = nulval;  /* set local variable */
+
+        fits_read_compressed_img(fptr, TDOUBLE, fpixel, lpixel, inc,
+            nullcheck, &nullvalue, array, NULL, anynul, status);
+        return(*status);
+    }
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+    tablerow=maxvalue(1,group);
+
+    if (ncols == naxis1 && nrows == naxis2)  /* arrays have same size? */
+    {
+       /* all the image pixels are contiguous, so read all at once */
+       ffgcld(fptr, 2, tablerow, 1, naxis1 * naxis2 * naxis3, 1, 1, nulval,
+               array, &cdummy, anynul, status);
+       return(*status);
+    }
+
+    if (ncols < naxis1 || nrows < naxis2)
+       return(*status = BAD_DIMEN);
+
+    nfits = 1;   /* next pixel in FITS image to read */
+    narray = 0;  /* next pixel in output array to be filled */
+
+    /* loop over naxis3 planes in the data cube */
+    for (jj = 0; jj < naxis3; jj++)
+    {
+      /* loop over the naxis2 rows in the FITS image, */
+      /* reading naxis1 pixels to each row            */
+
+      for (ii = 0; ii < naxis2; ii++)
+      {
+       if (ffgcld(fptr, 2, tablerow, nfits, naxis1, 1, 1, nulval,
+          &array[narray], &cdummy, anynul, status) > 0)
+          return(*status);
+
+       nfits += naxis1;
+       narray += ncols;
+      }
+      narray += (nrows - naxis2) * ncols;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgsvd(fitsfile *fptr, /* I - FITS file pointer                         */
+           int  colnum,    /* I - number of the column to read (1 = 1st)    */
+           int naxis,      /* I - number of dimensions in the FITS array    */
+           long  *naxes,   /* I - size of each dimension                    */
+           long  *blc,     /* I - 'bottom left corner' of the subsection    */
+           long  *trc,     /* I - 'top right corner' of the subsection      */
+           long  *inc,     /* I - increment to be applied in each dimension */
+           double nulval,  /* I - value to set undefined pixels             */
+           double *array,  /* O - array to be filled and returned           */
+           int  *anynul,   /* O - set to 1 if any values are null; else 0   */
+           int  *status)   /* IO - error status                             */
+/*
+  Read a subsection of data values from an image or a table column.
+  This routine is set up to handle a maximum of nine dimensions.
+*/
+{
+    long ii,i0, i1,i2,i3,i4,i5,i6,i7,i8,row,rstr,rstp,rinc;
+    long str[9],stp[9],incr[9],dir[9];
+    long nelem, nultyp, ninc, numcol;
+    LONGLONG felem, dsize[10], blcll[9], trcll[9];
+    int hdutype, anyf;
+    char ldummy, msg[FLEN_ERRMSG];
+    int nullcheck = 1;
+    double nullvalue;
+
+    if (naxis < 1 || naxis > 9)
+    {
+        sprintf(msg, "NAXIS = %d in call to ffgsvd is out of range", naxis);
+        ffpmsg(msg);
+        return(*status = BAD_DIMEN);
+    }
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        for (ii=0; ii < naxis; ii++) {
+	    blcll[ii] = blc[ii];
+	    trcll[ii] = trc[ii];
+	}
+
+        nullvalue = nulval;  /* set local variable */
+
+        fits_read_compressed_img(fptr, TDOUBLE, blcll, trcll, inc,
+            nullcheck, &nullvalue, array, NULL, anynul, status);
+        return(*status);
+    }
+
+/*
+    if this is a primary array, then the input COLNUM parameter should
+    be interpreted as the row number, and we will alway read the image
+    data from column 2 (any group parameters are in column 1).
+*/
+    if (ffghdt(fptr, &hdutype, status) > 0)
+        return(*status);
+
+    if (hdutype == IMAGE_HDU)
+    {
+        /* this is a primary array, or image extension */
+        if (colnum == 0)
+        {
+            rstr = 1;
+            rstp = 1;
+        }
+        else
+        {
+            rstr = colnum;
+            rstp = colnum;
+        }
+        rinc = 1;
+        numcol = 2;
+    }
+    else
+    {
+        /* this is a table, so the row info is in the (naxis+1) elements */
+        rstr = blc[naxis];
+        rstp = trc[naxis];
+        rinc = inc[naxis];
+        numcol = colnum;
+    }
+
+    nultyp = 1;
+    if (anynul)
+        *anynul = FALSE;
+
+    i0 = 0;
+    for (ii = 0; ii < 9; ii++)
+    {
+        str[ii] = 1;
+        stp[ii] = 1;
+        incr[ii] = 1;
+        dsize[ii] = 1;
+        dir[ii] = 1;
+    }
+
+    for (ii = 0; ii < naxis; ii++)
+    {
+      if (trc[ii] < blc[ii])
+      {
+        if (hdutype == IMAGE_HDU)
+        {
+           dir[ii] = -1;
+        }
+        else
+        {
+          sprintf(msg, "ffgsvd: illegal range specified for axis %ld", ii + 1);
+          ffpmsg(msg);
+          return(*status = BAD_PIX_NUM);
+        }
+      }
+
+      str[ii] = blc[ii];
+      stp[ii] = trc[ii];
+      incr[ii] = inc[ii];
+      dsize[ii + 1] = dsize[ii] * naxes[ii];
+      dsize[ii] = dsize[ii] * dir[ii];
+    }
+    dsize[naxis] = dsize[naxis] * dir[naxis];
+
+    if (naxis == 1 && naxes[0] == 1)
+    {
+      /* This is not a vector column, so read all the rows at once */
+      nelem = (rstp - rstr) / rinc + 1;
+      ninc = rinc;
+      rstp = rstr;
+    }
+    else
+    {
+      /* have to read each row individually, in all dimensions */
+      nelem = (stp[0]*dir[0] - str[0]*dir[0]) / inc[0] + 1;
+      ninc = incr[0] * dir[0];
+    }
+
+    for (row = rstr; row <= rstp; row += rinc)
+    {
+     for (i8 = str[8]*dir[8]; i8 <= stp[8]*dir[8]; i8 += incr[8])
+     {
+      for (i7 = str[7]*dir[7]; i7 <= stp[7]*dir[7]; i7 += incr[7])
+      {
+       for (i6 = str[6]*dir[6]; i6 <= stp[6]*dir[6]; i6 += incr[6])
+       {
+        for (i5 = str[5]*dir[5]; i5 <= stp[5]*dir[5]; i5 += incr[5])
+        {
+         for (i4 = str[4]*dir[4]; i4 <= stp[4]*dir[4]; i4 += incr[4])
+         {
+          for (i3 = str[3]*dir[3]; i3 <= stp[3]*dir[3]; i3 += incr[3])
+          {
+           for (i2 = str[2]*dir[2]; i2 <= stp[2]*dir[2]; i2 += incr[2])
+           {
+            for (i1 = str[1]*dir[1]; i1 <= stp[1]*dir[1]; i1 += incr[1])
+            {
+
+              felem=str[0] + (i1 - dir[1]) * dsize[1] + (i2 - dir[2]) * dsize[2] + 
+                             (i3 - dir[3]) * dsize[3] + (i4 - dir[4]) * dsize[4] +
+                             (i5 - dir[5]) * dsize[5] + (i6 - dir[6]) * dsize[6] +
+                             (i7 - dir[7]) * dsize[7] + (i8 - dir[8]) * dsize[8];
+
+              if ( ffgcld(fptr, numcol, row, felem, nelem, ninc, nultyp,
+                   nulval, &array[i0], &ldummy, &anyf, status) > 0)
+                   return(*status);
+
+              if (anyf && anynul)
+                  *anynul = TRUE;
+
+              i0 += nelem;
+            }
+           }
+          }
+         }
+        }
+       }
+      }
+     }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgsfd(fitsfile *fptr, /* I - FITS file pointer                         */
+           int  colnum,    /* I - number of the column to read (1 = 1st)    */
+           int naxis,      /* I - number of dimensions in the FITS array    */
+           long  *naxes,   /* I - size of each dimension                    */
+           long  *blc,     /* I - 'bottom left corner' of the subsection    */
+           long  *trc,     /* I - 'top right corner' of the subsection      */
+           long  *inc,     /* I - increment to be applied in each dimension */
+           double *array,  /* O - array to be filled and returned           */
+           char *flagval,  /* O - set to 1 if corresponding value is null   */
+           int  *anynul,   /* O - set to 1 if any values are null; else 0   */
+           int  *status)   /* IO - error status                             */
+/*
+  Read a subsection of data values from an image or a table column.
+  This routine is set up to handle a maximum of nine dimensions.
+*/
+{
+    long ii,i0, i1,i2,i3,i4,i5,i6,i7,i8,row,rstr,rstp,rinc;
+    long str[9],stp[9],incr[9],dsize[10];
+    LONGLONG blcll[9], trcll[9];
+    long felem, nelem, nultyp, ninc, numcol;
+    int hdutype, anyf;
+    double nulval = 0;
+    char msg[FLEN_ERRMSG];
+    int nullcheck = 2;
+
+    if (naxis < 1 || naxis > 9)
+    {
+        sprintf(msg, "NAXIS = %d in call to ffgsvd is out of range", naxis);
+        ffpmsg(msg);
+        return(*status = BAD_DIMEN);
+    }
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        for (ii=0; ii < naxis; ii++) {
+	    blcll[ii] = blc[ii];
+	    trcll[ii] = trc[ii];
+	}
+
+        fits_read_compressed_img(fptr, TDOUBLE, blcll, trcll, inc,
+            nullcheck, NULL, array, flagval, anynul, status);
+        return(*status);
+    }
+/*
+    if this is a primary array, then the input COLNUM parameter should
+    be interpreted as the row number, and we will alway read the image
+    data from column 2 (any group parameters are in column 1).
+*/
+    if (ffghdt(fptr, &hdutype, status) > 0)
+        return(*status);
+
+    if (hdutype == IMAGE_HDU)
+    {
+        /* this is a primary array, or image extension */
+        if (colnum == 0)
+        {
+            rstr = 1;
+            rstp = 1;
+        }
+        else
+        {
+            rstr = colnum;
+            rstp = colnum;
+        }
+        rinc = 1;
+        numcol = 2;
+    }
+    else
+    {
+        /* this is a table, so the row info is in the (naxis+1) elements */
+        rstr = blc[naxis];
+        rstp = trc[naxis];
+        rinc = inc[naxis];
+        numcol = colnum;
+    }
+
+    nultyp = 2;
+    if (anynul)
+        *anynul = FALSE;
+
+    i0 = 0;
+    for (ii = 0; ii < 9; ii++)
+    {
+        str[ii] = 1;
+        stp[ii] = 1;
+        incr[ii] = 1;
+        dsize[ii] = 1;
+    }
+
+    for (ii = 0; ii < naxis; ii++)
+    {
+      if (trc[ii] < blc[ii])
+      {
+        sprintf(msg, "ffgsvd: illegal range specified for axis %ld", ii + 1);
+        ffpmsg(msg);
+        return(*status = BAD_PIX_NUM);
+      }
+
+      str[ii] = blc[ii];
+      stp[ii] = trc[ii];
+      incr[ii] = inc[ii];
+      dsize[ii + 1] = dsize[ii] * naxes[ii];
+    }
+
+    if (naxis == 1 && naxes[0] == 1)
+    {
+      /* This is not a vector column, so read all the rows at once */
+      nelem = (rstp - rstr) / rinc + 1;
+      ninc = rinc;
+      rstp = rstr;
+    }
+    else
+    {
+      /* have to read each row individually, in all dimensions */
+      nelem = (stp[0] - str[0]) / inc[0] + 1;
+      ninc = incr[0];
+    }
+
+    for (row = rstr; row <= rstp; row += rinc)
+    {
+     for (i8 = str[8]; i8 <= stp[8]; i8 += incr[8])
+     {
+      for (i7 = str[7]; i7 <= stp[7]; i7 += incr[7])
+      {
+       for (i6 = str[6]; i6 <= stp[6]; i6 += incr[6])
+       {
+        for (i5 = str[5]; i5 <= stp[5]; i5 += incr[5])
+        {
+         for (i4 = str[4]; i4 <= stp[4]; i4 += incr[4])
+         {
+          for (i3 = str[3]; i3 <= stp[3]; i3 += incr[3])
+          {
+           for (i2 = str[2]; i2 <= stp[2]; i2 += incr[2])
+           {
+            for (i1 = str[1]; i1 <= stp[1]; i1 += incr[1])
+            {
+              felem=str[0] + (i1 - 1) * dsize[1] + (i2 - 1) * dsize[2] + 
+                             (i3 - 1) * dsize[3] + (i4 - 1) * dsize[4] +
+                             (i5 - 1) * dsize[5] + (i6 - 1) * dsize[6] +
+                             (i7 - 1) * dsize[7] + (i8 - 1) * dsize[8];
+
+              if ( ffgcld(fptr, numcol, row, felem, nelem, ninc, nultyp,
+                   nulval, &array[i0], &flagval[i0], &anyf, status) > 0)
+                   return(*status);
+
+              if (anyf && anynul)
+                  *anynul = TRUE;
+
+              i0 += nelem;
+            }
+           }
+          }
+         }
+        }
+       }
+      }
+     }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffggpd( fitsfile *fptr,   /* I - FITS file pointer                       */
+            long  group,      /* I - group to read (1 = 1st group)           */
+            long  firstelem,  /* I - first vector element to read (1 = 1st)  */
+            long  nelem,      /* I - number of values to read                */
+            double *array,    /* O - array of values that are returned       */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of group parameters from the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+*/
+{
+    long row;
+    int idummy;
+    char cdummy;
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffgcld(fptr, 1, row, firstelem, nelem, 1, 1, 0.,
+               array, &cdummy, &idummy, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcvd(fitsfile *fptr,   /* I - FITS file pointer                       */
+           int  colnum,      /* I - number of column to read (1 = 1st col)  */
+           LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+           LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+           LONGLONG  nelem,      /* I - number of values to read                */
+           double nulval,    /* I - value for null pixels                   */
+           double *array,    /* O - array of values that are read           */
+           int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+           int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from a column in the current FITS HDU. Automatic
+  datatype conversion will be performed if the datatype of the column does not
+  match the datatype of the array parameter. The output values will be scaled 
+  by the FITS TSCALn and TZEROn values if these values have been defined.
+  Any undefined pixels will be set equal to the value of 'nulval' unless
+  nulval = 0 in which case no checks for undefined pixels will be made.
+*/
+{
+    char cdummy;
+
+    ffgcld(fptr, colnum, firstrow, firstelem, nelem, 1, 1, nulval,
+           array, &cdummy, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcvm(fitsfile *fptr,   /* I - FITS file pointer                       */
+           int  colnum,      /* I - number of column to read (1 = 1st col)  */
+           LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+           LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+           LONGLONG  nelem,      /* I - number of values to read                */
+           double nulval,    /* I - value for null pixels                   */
+           double *array,    /* O - array of values that are read           */
+           int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+           int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from a column in the current FITS HDU. Automatic
+  datatype conversion will be performed if the datatype of the column does not
+  match the datatype of the array parameter. The output values will be scaled 
+  by the FITS TSCALn and TZEROn values if these values have been defined.
+  Any undefined pixels will be set equal to the value of 'nulval' unless
+  nulval = 0 in which case no checks for undefined pixels will be made.
+
+  TSCAL and ZERO should not be used with complex values. 
+*/
+{
+    char cdummy;
+
+    /* a complex double value is interpreted as a pair of double values,   */
+    /* thus need to multiply the first element and number of elements by 2 */
+
+    ffgcld(fptr, colnum, firstrow, (firstelem - 1) * 2 + 1, nelem * 2,
+        1, 1, nulval, array, &cdummy, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcfd(fitsfile *fptr,   /* I - FITS file pointer                       */
+           int  colnum,      /* I - number of column to read (1 = 1st col)  */
+           LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+           LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+           LONGLONG  nelem,      /* I - number of values to read                */
+           double *array,    /* O - array of values that are read           */
+           char *nularray,   /* O - array of flags: 1 if null pixel; else 0 */
+           int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+           int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from a column in the current FITS HDU. Automatic
+  datatype conversion will be performed if the datatype of the column does not
+  match the datatype of the array parameter. The output values will be scaled 
+  by the FITS TSCALn and TZEROn values if these values have been defined.
+  Nularray will be set = 1 if the corresponding array pixel is undefined, 
+  otherwise nularray will = 0.
+*/
+{
+    double dummy = 0;
+
+    ffgcld(fptr, colnum, firstrow, firstelem, nelem, 1, 2, dummy,
+           array, nularray, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcfm(fitsfile *fptr,   /* I - FITS file pointer                       */
+           int  colnum,      /* I - number of column to read (1 = 1st col)  */
+           LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+           LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+           LONGLONG  nelem,      /* I - number of values to read                */
+           double *array,    /* O - array of values that are read           */
+           char *nularray,   /* O - array of flags: 1 if null pixel; else 0 */
+           int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+           int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from a column in the current FITS HDU. Automatic
+  datatype conversion will be performed if the datatype of the column does not
+  match the datatype of the array parameter. The output values will be scaled 
+  by the FITS TSCALn and TZEROn values if these values have been defined.
+  Nularray will be set = 1 if the corresponding array pixel is undefined, 
+  otherwise nularray will = 0.
+
+  TSCAL and ZERO should not be used with complex values. 
+*/
+{
+    long ii, jj;
+    float dummy = 0;
+    char *carray;
+
+    /* a complex double value is interpreted as a pair of double values,   */
+    /* thus need to multiply the first element and number of elements by 2 */
+
+    /* allocate temporary array */
+    carray = (char *) calloc( (size_t) (nelem * 2), 1); 
+
+    ffgcld(fptr, colnum, firstrow, (firstelem - 1) * 2 + 1, nelem * 2,
+     1, 2, dummy, array, carray, anynul, status);
+
+    for (ii = 0, jj = 0; jj < nelem; ii += 2, jj++)
+    {
+       if (carray[ii] || carray[ii + 1])
+          nularray[jj] = 1;
+       else
+          nularray[jj] = 0;
+    }
+
+    free(carray);    
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcld( fitsfile *fptr,   /* I - FITS file pointer                       */
+            int  colnum,      /* I - number of column to read (1 = 1st col)  */
+            LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+            LONGLONG firstelem,  /* I - first vector element to read (1 = 1st)  */
+            LONGLONG  nelem,      /* I - number of values to read                */
+            long  elemincre,  /* I - pixel increment; e.g., 2 = every other  */
+            int   nultyp,     /* I - null value handling code:               */
+                              /*     1: set undefined pixels = nulval        */
+                              /*     2: set nularray=1 for undefined pixels  */
+            double nulval,    /* I - value for null pixels if nultyp = 1     */
+            double *array,    /* O - array of values that are read           */
+            char *nularray,   /* O - array of flags = 1 if nultyp = 2        */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from a column in the current FITS HDU.
+  The column number may refer to a real column in an ASCII or binary table, 
+  or it may refer be a virtual column in a 1 or more grouped FITS primary
+  array or image extension.  FITSIO treats a primary array as a binary table
+  with 2 vector columns: the first column contains the group parameters (often
+  with length = 0) and the second column contains the array of image pixels.
+  Each row of the table represents a group in the case of multigroup FITS
+  images.
+
+  The output array of values will be converted from the datatype of the column
+  and will be scaled by the FITS TSCALn and TZEROn values if necessary.
+*/
+{
+    double scale, zero, power = 1, dtemp;
+    int tcode, hdutype, xcode, decimals, maxelem;
+    long twidth, incre;
+    long ii, xwidth, ntodo;
+    int convert, nulcheck, readcheck = 0;
+    LONGLONG repeat, startpos, elemnum, readptr, tnull;
+    LONGLONG rowlen, rownum, remain, next, rowincre;
+    char tform[20];
+    char message[81];
+    char snull[20];   /*  the FITS null value if reading from ASCII table  */
+
+    double cbuff[DBUFFSIZE / sizeof(double)]; /* align cbuff on word boundary */
+    void *buffer;
+
+    if (*status > 0 || nelem == 0)  /* inherit input status value if > 0 */
+        return(*status);
+
+    buffer = cbuff;
+
+    if (anynul)
+        *anynul = 0;
+
+    if (nultyp == 2)
+        memset(nularray, 0, (size_t) nelem);   /* initialize nullarray */
+
+    /*---------------------------------------------------*/
+    /*  Check input and get parameters about the column: */
+    /*---------------------------------------------------*/
+    if (elemincre < 0)
+        readcheck = -1;  /* don't do range checking in this case */
+
+    if ( ffgcprll( fptr, colnum, firstrow, firstelem, nelem, readcheck, &scale, &zero,
+         tform, &twidth, &tcode, &maxelem, &startpos, &elemnum, &incre,
+         &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0 )
+         return(*status);
+
+    incre *= elemincre;   /* multiply incre to just get every nth pixel */
+
+    if (tcode == TSTRING)    /* setup for ASCII tables */
+    {
+      /* get the number of implied decimal places if no explicit decmal point */
+      ffasfm(tform, &xcode, &xwidth, &decimals, status); 
+      for(ii = 0; ii < decimals; ii++)
+        power *= 10.;
+    }
+
+    /*------------------------------------------------------------------*/
+    /*  Decide whether to check for null values in the input FITS file: */
+    /*------------------------------------------------------------------*/
+    nulcheck = nultyp; /* by default check for null values in the FITS file */
+
+    if (nultyp == 1 && nulval == 0)
+       nulcheck = 0;    /* calling routine does not want to check for nulls */
+
+    else if (tcode%10 == 1 &&        /* if reading an integer column, and  */ 
+            tnull == NULL_UNDEFINED) /* if a null value is not defined,    */
+            nulcheck = 0;            /* then do not check for null values. */
+
+    else if (tcode == TSHORT && (tnull > SHRT_MAX || tnull < SHRT_MIN) )
+            nulcheck = 0;            /* Impossible null value */
+
+    else if (tcode == TBYTE && (tnull > 255 || tnull < 0) )
+            nulcheck = 0;            /* Impossible null value */
+
+    else if (tcode == TSTRING && snull[0] == ASCII_NULL_UNDEFINED)
+         nulcheck = 0;
+
+    /*----------------------------------------------------------------------*/
+    /*  If FITS column and output data array have same datatype, then we do */
+    /*  not need to use a temporary buffer to store intermediate datatype.  */
+    /*----------------------------------------------------------------------*/
+    convert = 1;
+    if (tcode == TDOUBLE) /* Special Case:                        */
+    {                              /* no type convertion required, so read */
+        maxelem = (int) nelem;           /* data directly into output buffer.    */
+
+        if (nulcheck == 0 && scale == 1. && zero == 0.)
+            convert = 0;  /* no need to scale data or find nulls */
+    }
+
+    /*---------------------------------------------------------------------*/
+    /*  Now read the pixels from the FITS column. If the column does not   */
+    /*  have the same datatype as the output array, then we have to read   */
+    /*  the raw values into a temporary buffer (of limited size).  In      */
+    /*  the case of a vector colum read only 1 vector of values at a time  */
+    /*  then skip to the next row if more values need to be read.          */
+    /*  After reading the raw values, then call the fffXXYY routine to (1) */
+    /*  test for undefined values, (2) convert the datatype if necessary,  */
+    /*  and (3) scale the values by the FITS TSCALn and TZEROn linear      */
+    /*  scaling parameters.                                                */
+    /*---------------------------------------------------------------------*/
+    remain = nelem;           /* remaining number of values to read */
+    next = 0;                 /* next element in array to be read   */
+    rownum = 0;               /* row number, relative to firstrow   */
+
+    while (remain)
+    {
+        /* limit the number of pixels to read at one time to the number that
+           will fit in the buffer or to the number of pixels that remain in
+           the current vector, which ever is smaller.
+        */
+        ntodo = (long) minvalue(remain, maxelem);
+        if (elemincre >= 0)
+        {
+          ntodo = (long) minvalue(ntodo, ((repeat - elemnum - 1)/elemincre +1));
+        }
+        else
+        {
+          ntodo = (long) minvalue(ntodo, (elemnum/(-elemincre) +1));
+        }
+
+        readptr = startpos + ((LONGLONG)rownum * rowlen) + (elemnum * (incre / elemincre));
+
+        switch (tcode) 
+        {
+            case (TDOUBLE):
+                ffgr8b(fptr, readptr, ntodo, incre, &array[next], status);
+                if (convert)
+                    fffr8r8(&array[next], ntodo, scale, zero, nulcheck, 
+                           nulval, &nularray[next], anynul, 
+                           &array[next], status);
+                break;
+            case (TBYTE):
+                ffgi1b(fptr, readptr, ntodo, incre, (unsigned char *) buffer,
+                       status);
+                fffi1r8((unsigned char *) buffer, ntodo, scale, zero, nulcheck, 
+                   (unsigned char) tnull, nulval, &nularray[next], anynul, 
+                   &array[next], status);
+                break;
+            case (TSHORT):
+                ffgi2b(fptr, readptr, ntodo, incre, (short  *) buffer, status);
+                fffi2r8((short  *) buffer, ntodo, scale, zero, nulcheck, 
+                    (short) tnull, nulval, &nularray[next], anynul, 
+                       &array[next], status);
+                break;
+            case (TLONG):
+                ffgi4b(fptr, readptr, ntodo, incre, (INT32BIT *) buffer,
+                       status);
+                fffi4r8((INT32BIT *) buffer, ntodo, scale, zero, nulcheck, 
+                       (INT32BIT) tnull, nulval, &nularray[next], anynul, 
+                       &array[next], status);
+                break;
+            case (TLONGLONG):
+                ffgi8b(fptr, readptr, ntodo, incre, (long *) buffer, status);
+                fffi8r8( (LONGLONG *) buffer, ntodo, scale, zero, 
+                           nulcheck, tnull, nulval, &nularray[next], 
+                            anynul, &array[next], status);
+                break;
+            case (TFLOAT):
+                ffgr4b(fptr, readptr, ntodo, incre, (float  *) buffer, status);
+                fffr4r8((float  *) buffer, ntodo, scale, zero, nulcheck, 
+                          nulval, &nularray[next], anynul, 
+                          &array[next], status);
+                break;
+            case (TSTRING):
+                ffmbyt(fptr, readptr, REPORT_EOF, status);
+       
+                if (incre == twidth)    /* contiguous bytes */
+                     ffgbyt(fptr, ntodo * twidth, buffer, status);
+                else
+                     ffgbytoff(fptr, twidth, ntodo, incre - twidth, buffer,
+                               status);
+
+                fffstrr8((char *) buffer, ntodo, scale, zero, twidth, power,
+                     nulcheck, snull, nulval, &nularray[next], anynul,
+                     &array[next], status);
+                break;
+
+
+            default:  /*  error trap for invalid column format */
+                sprintf(message, 
+                   "Cannot read numbers from column %d which has format %s",
+                    colnum, tform);
+                ffpmsg(message);
+                if (hdutype == ASCII_TBL)
+                    return(*status = BAD_ATABLE_FORMAT);
+                else
+                    return(*status = BAD_BTABLE_FORMAT);
+
+        } /* End of switch block */
+
+        /*-------------------------*/
+        /*  Check for fatal error  */
+        /*-------------------------*/
+        if (*status > 0)  /* test for error during previous read operation */
+        {
+	  dtemp = (double) next;
+          if (hdutype > 0)
+            sprintf(message,
+            "Error reading elements %.0f thru %.0f from column %d (ffgcld).",
+              dtemp+1., dtemp+ntodo, colnum);
+          else
+            sprintf(message,
+            "Error reading elements %.0f thru %.0f from image (ffgcld).",
+              dtemp+1., dtemp+ntodo);
+
+          ffpmsg(message);
+          return(*status);
+        }
+
+        /*--------------------------------------------*/
+        /*  increment the counters for the next loop  */
+        /*--------------------------------------------*/
+        remain -= ntodo;
+        if (remain)
+        {
+            next += ntodo;
+            elemnum = elemnum + (ntodo * elemincre);
+
+            if (elemnum >= repeat)  /* completed a row; start on later row */
+            {
+                rowincre = (long) (elemnum / repeat);
+                rownum += rowincre;
+                elemnum = elemnum - (rowincre * repeat);
+            }
+            else if (elemnum < 0)  /* completed a row; start on a previous row */
+            {
+                rowincre = (long) ((-elemnum - 1) / repeat + 1);
+                rownum -= rowincre;
+                elemnum = (rowincre * repeat) + elemnum;
+            }
+        }
+    }  /*  End of main while Loop  */
+
+
+    /*--------------------------------*/
+    /*  check for numerical overflow  */
+    /*--------------------------------*/
+    if (*status == OVERFLOW_ERR)
+    {
+        ffpmsg(
+        "Numerical overflow during type conversion while reading FITS data.");
+        *status = NUM_OVERFLOW;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi1r8(unsigned char *input, /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            unsigned char tnull,  /* I - value of FITS TNULLn keyword if any */
+            double nullval,       /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            double *output,       /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (double) input[ii]; /* copy input to output */
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                output[ii] = input[ii] * scale + zero;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                    output[ii] = (double) input[ii];
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    output[ii] = input[ii] * scale + zero;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi2r8(short *input,         /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            short tnull,          /* I - value of FITS TNULLn keyword if any */
+            double nullval,       /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            double *output,       /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (double) input[ii]; /* copy input to output */
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                output[ii] = input[ii] * scale + zero;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                    output[ii] = (double) input[ii];
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    output[ii] = input[ii] * scale + zero;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi4r8(INT32BIT *input,      /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            INT32BIT tnull,       /* I - value of FITS TNULLn keyword if any */
+            double nullval,       /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            double *output,       /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (double) input[ii]; /* copy input to output */
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                output[ii] = input[ii] * scale + zero;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                    output[ii] = (double) input[ii];
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    output[ii] = input[ii] * scale + zero;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi8r8(LONGLONG *input,      /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            LONGLONG tnull,       /* I - value of FITS TNULLn keyword if any */
+            double nullval,       /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            double *output,       /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (double) input[ii]; /* copy input to output */
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                output[ii] = input[ii] * scale + zero;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                    output[ii] = (double) input[ii];
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    output[ii] = input[ii] * scale + zero;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffr4r8(float *input,         /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            double nullval,       /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            double *output,       /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to NaN.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    short *sptr, iret;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (double) input[ii]; /* copy input to output */
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                output[ii] = input[ii] * scale + zero;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        sptr = (short *) input;
+
+#if BYTESWAPPED && MACHINE != VAXVMS && MACHINE != ALPHAVMS
+        sptr++;       /* point to MSBs */
+#endif
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++, sptr += 2)
+            {
+              if (0 != (iret = fnan(*sptr) ) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                  }
+                  else            /* it's an underflow */
+                     output[ii] = 0;
+              }
+              else
+                output[ii] = (double) input[ii];
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++, sptr += 2)
+            {
+              if (0 != (iret = fnan(*sptr) ) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {  
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                  }
+                  else            /* it's an underflow */
+                     output[ii] = zero;
+              }
+              else
+                  output[ii] = input[ii] * scale + zero;
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffr8r8(double *input,        /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            double nullval,       /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            double *output,       /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to NaN.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    short *sptr, iret;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            memcpy(output, input, ntodo * sizeof(double) );
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                output[ii] = input[ii] * scale + zero;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        sptr = (short *) input;
+
+#if BYTESWAPPED && MACHINE != VAXVMS && MACHINE != ALPHAVMS
+        sptr += 3;       /* point to MSBs */
+#endif
+
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++, sptr += 4)
+            {
+              if (0 != (iret = dnan(*sptr)) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                    {
+                        nullarray[ii] = 1;
+                       /* explicitly set value in case output contains a NaN */
+                        output[ii] = DOUBLENULLVALUE;
+                    }
+                  }
+                  else            /* it's an underflow */
+                     output[ii] = 0;
+              }
+              else
+                  output[ii] = input[ii];
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++, sptr += 4)
+            {
+              if (0 != (iret = dnan(*sptr)) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {  
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                    {
+                        nullarray[ii] = 1;
+                       /* explicitly set value in case output contains a NaN */
+                        output[ii] = DOUBLENULLVALUE;
+                    }
+                  }
+                  else            /* it's an underflow */
+                     output[ii] = zero;
+              }
+              else
+                  output[ii] = input[ii] * scale + zero;
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffstrr8(char *input,         /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            long twidth,          /* I - width of each substring of chars    */
+            double implipower,    /* I - power of 10 of implied decimal      */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            char  *snull,         /* I - value of FITS null string, if any   */
+            double nullval,       /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            double *output,       /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file. Check
+  for null values and do scaling if required. The nullcheck code value
+  determines how any null values in the input array are treated. A null
+  value is an input pixel that is equal to snull.  If nullcheck= 0, then
+  no special checking for nulls is performed.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    int nullen;
+    long ii;
+    double dvalue;
+    char *cstring, message[81];
+    char *cptr, *tpos;
+    char tempstore, chrzero = '0';
+    double val, power;
+    int exponent, sign, esign, decpt;
+
+    nullen = strlen(snull);
+    cptr = input;  /* pointer to start of input string */
+    for (ii = 0; ii < ntodo; ii++)
+    {
+      cstring = cptr;
+      /* temporarily insert a null terminator at end of the string */
+      tpos = cptr + twidth;
+      tempstore = *tpos;
+      *tpos = 0;
+
+      /* check if null value is defined, and if the    */
+      /* column string is identical to the null string */
+      if (snull[0] != ASCII_NULL_UNDEFINED && 
+         !strncmp(snull, cptr, nullen) )
+      {
+        if (nullcheck)  
+        {
+          *anynull = 1;    
+          if (nullcheck == 1)
+            output[ii] = nullval;
+          else
+            nullarray[ii] = 1;
+        }
+        cptr += twidth;
+      }
+      else
+      {
+        /* value is not the null value, so decode it */
+        /* remove any embedded blank characters from the string */
+
+        decpt = 0;
+        sign = 1;
+        val  = 0.;
+        power = 1.;
+        exponent = 0;
+        esign = 1;
+
+        while (*cptr == ' ')               /* skip leading blanks */
+           cptr++;
+
+        if (*cptr == '-' || *cptr == '+')  /* check for leading sign */
+        {
+          if (*cptr == '-')
+             sign = -1;
+
+          cptr++;
+
+          while (*cptr == ' ')         /* skip blanks between sign and value */
+            cptr++;
+        }
+
+        while (*cptr >= '0' && *cptr <= '9')
+        {
+          val = val * 10. + *cptr - chrzero;  /* accumulate the value */
+          cptr++;
+
+          while (*cptr == ' ')         /* skip embedded blanks in the value */
+            cptr++;
+        }
+
+        if (*cptr == '.' || *cptr == ',')              /* check for decimal point */
+        {
+          decpt = 1;       /* set flag to show there was a decimal point */
+          cptr++;
+          while (*cptr == ' ')         /* skip any blanks */
+            cptr++;
+
+          while (*cptr >= '0' && *cptr <= '9')
+          {
+            val = val * 10. + *cptr - chrzero;  /* accumulate the value */
+            power = power * 10.;
+            cptr++;
+
+            while (*cptr == ' ')         /* skip embedded blanks in the value */
+              cptr++;
+          }
+        }
+
+        if (*cptr == 'E' || *cptr == 'D')  /* check for exponent */
+        {
+          cptr++;
+          while (*cptr == ' ')         /* skip blanks */
+              cptr++;
+  
+          if (*cptr == '-' || *cptr == '+')  /* check for exponent sign */
+          {
+            if (*cptr == '-')
+               esign = -1;
+
+            cptr++;
+
+            while (*cptr == ' ')        /* skip blanks between sign and exp */
+              cptr++;
+          }
+
+          while (*cptr >= '0' && *cptr <= '9')
+          {
+            exponent = exponent * 10 + *cptr - chrzero;  /* accumulate exp */
+            cptr++;
+
+            while (*cptr == ' ')         /* skip embedded blanks */
+              cptr++;
+          }
+        }
+
+        if (*cptr  != 0)  /* should end up at the null terminator */
+        {
+          sprintf(message, "Cannot read number from ASCII table");
+          ffpmsg(message);
+          sprintf(message, "Column field = %s.", cstring);
+          ffpmsg(message);
+          /* restore the char that was overwritten by the null */
+          *tpos = tempstore;
+          return(*status = BAD_C2D);
+        }
+
+        if (!decpt)  /* if no explicit decimal, use implied */
+           power = implipower;
+
+        dvalue = (sign * val / power) * pow(10., (double) (esign * exponent));
+
+        output[ii] = (dvalue * scale + zero);   /* apply the scaling */
+      }
+      /* restore the char that was overwritten by the null */
+      *tpos = tempstore;
+    }
+    return(*status);
+}
diff --git a/src/plugins/cfitsio/getcole.c b/src/plugins/cfitsio/getcole.c
new file mode 100644
index 0000000..441c657
--- /dev/null
+++ b/src/plugins/cfitsio/getcole.c
@@ -0,0 +1,1679 @@
+/*  This file, getcole.c, contains routines that read data elements from   */
+/*  a FITS image or table, with float datatype                             */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+
+#include <math.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <string.h>
+#include "fitsio2.h"
+
+/*--------------------------------------------------------------------------*/
+int ffgpve( fitsfile *fptr,   /* I - FITS file pointer                       */
+            long  group,      /* I - group to read (1 = 1st group)           */
+            LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+            LONGLONG  nelem,      /* I - number of values to read                */
+            float nulval,     /* I - value for undefined pixels              */
+            float *array,     /* O - array of values that are returned       */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+  Undefined elements will be set equal to NULVAL, unless NULVAL=0
+  in which case no checking for undefined values will be performed.
+  ANYNUL is returned with a value of .true. if any pixels are undefined.
+*/
+{
+    long row;
+    char cdummy;
+    int nullcheck = 1;
+    float nullvalue;
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+         nullvalue = nulval;  /* set local variable */
+
+        fits_read_compressed_pixels(fptr, TFLOAT, firstelem, nelem,
+            nullcheck, &nullvalue, array, NULL, anynul, status);
+        return(*status);
+    }
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffgcle(fptr, 2, row, firstelem, nelem, 1, 1, nulval,
+               array, &cdummy, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgpfe( fitsfile *fptr,   /* I - FITS file pointer                       */
+            long  group,      /* I - group to read (1 = 1st group)           */
+            LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+            LONGLONG  nelem,      /* I - number of values to read                */
+            float *array,     /* O - array of values that are returned       */
+            char *nularray,   /* O - array of null pixel flags               */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+  Any undefined pixels in the returned array will be set = 0 and the 
+  corresponding nularray value will be set = 1.
+  ANYNUL is returned with a value of .true. if any pixels are undefined.
+*/
+{
+    long row;
+    int nullcheck = 2;
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        fits_read_compressed_pixels(fptr, TFLOAT, firstelem, nelem,
+            nullcheck, NULL, array, nularray, anynul, status);
+        return(*status);
+    }
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffgcle(fptr, 2, row, firstelem, nelem, 1, 2, 0.F,
+               array, nularray, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffg2de(fitsfile *fptr,  /* I - FITS file pointer                       */
+           long  group,     /* I - group to read (1 = 1st group)           */
+           float nulval,    /* set undefined pixels equal to this          */
+           LONGLONG  ncols,     /* I - number of pixels in each row of array   */
+           LONGLONG  naxis1,    /* I - FITS image NAXIS1 value                 */
+           LONGLONG  naxis2,    /* I - FITS image NAXIS2 value                 */
+           float *array,    /* O - array to be filled and returned         */
+           int  *anynul,    /* O - set to 1 if any values are null; else 0 */
+           int  *status)    /* IO - error status                           */
+/*
+  Read an entire 2-D array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being read).  Any null
+  values in the array will be set equal to the value of nulval, unless
+  nulval = 0 in which case no null checking will be performed.
+*/
+{
+    /* call the 3D reading routine, with the 3rd dimension = 1 */
+
+    ffg3de(fptr, group, nulval, ncols, naxis2, naxis1, naxis2, 1, array, 
+           anynul, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffg3de(fitsfile *fptr,  /* I - FITS file pointer                       */
+           long  group,     /* I - group to read (1 = 1st group)           */
+           float nulval,    /* set undefined pixels equal to this          */
+           LONGLONG  ncols,     /* I - number of pixels in each row of array   */
+           LONGLONG  nrows,     /* I - number of rows in each plane of array   */
+           LONGLONG  naxis1,    /* I - FITS image NAXIS1 value                 */
+           LONGLONG  naxis2,    /* I - FITS image NAXIS2 value                 */
+           LONGLONG  naxis3,    /* I - FITS image NAXIS3 value                 */
+           float *array,    /* O - array to be filled and returned         */
+           int  *anynul,    /* O - set to 1 if any values are null; else 0 */
+           int  *status)    /* IO - error status                           */
+/*
+  Read an entire 3-D array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being read).  Any null
+  values in the array will be set equal to the value of nulval, unless
+  nulval = 0 in which case no null checking will be performed.
+*/
+{
+    long tablerow, ii, jj;
+    LONGLONG narray, nfits;
+    char cdummy;
+    int nullcheck = 1;
+    long inc[] = {1,1,1};
+    LONGLONG fpixel[] = {1,1,1};
+    LONGLONG lpixel[3];
+    float nullvalue;
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        lpixel[0] = ncols;
+        lpixel[1] = nrows;
+        lpixel[2] = naxis3;
+        nullvalue = nulval;  /* set local variable */
+
+        fits_read_compressed_img(fptr, TFLOAT, fpixel, lpixel, inc,
+            nullcheck, &nullvalue, array, NULL, anynul, status);
+        return(*status);
+    }
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+    tablerow=maxvalue(1,group);
+
+    if (ncols == naxis1 && nrows == naxis2)  /* arrays have same size? */
+    {
+       /* all the image pixels are contiguous, so read all at once */
+       ffgcle(fptr, 2, tablerow, 1, naxis1 * naxis2 * naxis3, 1, 1, nulval,
+               array, &cdummy, anynul, status);
+       return(*status);
+    }
+
+    if (ncols < naxis1 || nrows < naxis2)
+       return(*status = BAD_DIMEN);
+
+    nfits = 1;   /* next pixel in FITS image to read */
+    narray = 0;  /* next pixel in output array to be filled */
+
+    /* loop over naxis3 planes in the data cube */
+    for (jj = 0; jj < naxis3; jj++)
+    {
+      /* loop over the naxis2 rows in the FITS image, */
+      /* reading naxis1 pixels to each row            */
+
+      for (ii = 0; ii < naxis2; ii++)
+      {
+       if (ffgcle(fptr, 2, tablerow, nfits, naxis1, 1, 1, nulval,
+          &array[narray], &cdummy, anynul, status) > 0)
+          return(*status);
+
+       nfits += naxis1;
+       narray += ncols;
+      }
+      narray += (nrows - naxis2) * ncols;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgsve(fitsfile *fptr, /* I - FITS file pointer                         */
+           int  colnum,    /* I - number of the column to read (1 = 1st)    */
+           int naxis,      /* I - number of dimensions in the FITS array    */
+           long  *naxes,   /* I - size of each dimension                    */
+           long  *blc,     /* I - 'bottom left corner' of the subsection    */
+           long  *trc,     /* I - 'top right corner' of the subsection      */
+           long  *inc,     /* I - increment to be applied in each dimension */
+           float nulval,   /* I - value to set undefined pixels             */
+           float *array,   /* O - array to be filled and returned           */
+           int  *anynul,   /* O - set to 1 if any values are null; else 0   */
+           int  *status)   /* IO - error status                             */
+/*
+  Read a subsection of data values from an image or a table column.
+  This routine is set up to handle a maximum of nine dimensions.
+*/
+{
+    long ii,i0, i1,i2,i3,i4,i5,i6,i7,i8,row,rstr,rstp,rinc;
+    long str[9],stp[9],incr[9],dir[9];
+    long nelem, nultyp, ninc, numcol;
+    LONGLONG felem, dsize[10], blcll[9], trcll[9];
+    int hdutype, anyf;
+    char ldummy, msg[FLEN_ERRMSG];
+    int nullcheck = 1;
+    float nullvalue;
+
+    if (naxis < 1 || naxis > 9)
+    {
+        sprintf(msg, "NAXIS = %d in call to ffgsve is out of range", naxis);
+        ffpmsg(msg);
+        return(*status = BAD_DIMEN);
+    }
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        for (ii=0; ii < naxis; ii++) {
+	    blcll[ii] = blc[ii];
+	    trcll[ii] = trc[ii];
+	}
+
+        nullvalue = nulval;  /* set local variable */
+
+        fits_read_compressed_img(fptr, TFLOAT, blcll, trcll, inc,
+            nullcheck, &nullvalue, array, NULL, anynul, status);
+        return(*status);
+    }
+
+/*
+    if this is a primary array, then the input COLNUM parameter should
+    be interpreted as the row number, and we will alway read the image
+    data from column 2 (any group parameters are in column 1).
+*/
+    if (ffghdt(fptr, &hdutype, status) > 0)
+        return(*status);
+
+    if (hdutype == IMAGE_HDU)
+    {
+        /* this is a primary array, or image extension */
+        if (colnum == 0)
+        {
+            rstr = 1;
+            rstp = 1;
+        }
+        else
+        {
+            rstr = colnum;
+            rstp = colnum;
+        }
+        rinc = 1;
+        numcol = 2;
+    }
+    else
+    {
+        /* this is a table, so the row info is in the (naxis+1) elements */
+        rstr = blc[naxis];
+        rstp = trc[naxis];
+        rinc = inc[naxis];
+        numcol = colnum;
+    }
+
+    nultyp = 1;
+    if (anynul)
+        *anynul = FALSE;
+
+    i0 = 0;
+    for (ii = 0; ii < 9; ii++)
+    {
+        str[ii] = 1;
+        stp[ii] = 1;
+        incr[ii] = 1;
+        dsize[ii] = 1;
+        dir[ii] = 1;
+    }
+
+    for (ii = 0; ii < naxis; ii++)
+    {
+      if (trc[ii] < blc[ii])
+      {
+        if (hdutype == IMAGE_HDU)
+        {
+           dir[ii] = -1;
+        }
+        else
+        {
+          sprintf(msg, "ffgsve: illegal range specified for axis %ld", ii + 1);
+          ffpmsg(msg);
+          return(*status = BAD_PIX_NUM);
+        }
+      }
+
+      str[ii] = blc[ii];
+      stp[ii] = trc[ii];
+      incr[ii] = inc[ii];
+      dsize[ii + 1] = dsize[ii] * naxes[ii];
+      dsize[ii] = dsize[ii] * dir[ii];
+    }
+    dsize[naxis] = dsize[naxis] * dir[naxis];
+
+    if (naxis == 1 && naxes[0] == 1)
+    {
+      /* This is not a vector column, so read all the rows at once */
+      nelem = (rstp - rstr) / rinc + 1;
+      ninc = rinc;
+      rstp = rstr;
+    }
+    else
+    {
+      /* have to read each row individually, in all dimensions */
+      nelem = (stp[0]*dir[0] - str[0]*dir[0]) / inc[0] + 1;
+      ninc = incr[0] * dir[0];
+    }
+
+    for (row = rstr; row <= rstp; row += rinc)
+    {
+     for (i8 = str[8]*dir[8]; i8 <= stp[8]*dir[8]; i8 += incr[8])
+     {
+      for (i7 = str[7]*dir[7]; i7 <= stp[7]*dir[7]; i7 += incr[7])
+      {
+       for (i6 = str[6]*dir[6]; i6 <= stp[6]*dir[6]; i6 += incr[6])
+       {
+        for (i5 = str[5]*dir[5]; i5 <= stp[5]*dir[5]; i5 += incr[5])
+        {
+         for (i4 = str[4]*dir[4]; i4 <= stp[4]*dir[4]; i4 += incr[4])
+         {
+          for (i3 = str[3]*dir[3]; i3 <= stp[3]*dir[3]; i3 += incr[3])
+          {
+           for (i2 = str[2]*dir[2]; i2 <= stp[2]*dir[2]; i2 += incr[2])
+           {
+            for (i1 = str[1]*dir[1]; i1 <= stp[1]*dir[1]; i1 += incr[1])
+            {
+
+              felem=str[0] + (i1 - dir[1]) * dsize[1] + (i2 - dir[2]) * dsize[2] + 
+                             (i3 - dir[3]) * dsize[3] + (i4 - dir[4]) * dsize[4] +
+                             (i5 - dir[5]) * dsize[5] + (i6 - dir[6]) * dsize[6] +
+                             (i7 - dir[7]) * dsize[7] + (i8 - dir[8]) * dsize[8];
+
+              if ( ffgcle(fptr, numcol, row, felem, nelem, ninc, nultyp,
+                   nulval, &array[i0], &ldummy, &anyf, status) > 0)
+                   return(*status);
+
+              if (anyf && anynul)
+                  *anynul = TRUE;
+
+              i0 += nelem;
+            }
+           }
+          }
+         }
+        }
+       }
+      }
+     }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgsfe(fitsfile *fptr, /* I - FITS file pointer                         */
+           int  colnum,    /* I - number of the column to read (1 = 1st)    */
+           int naxis,      /* I - number of dimensions in the FITS array    */
+           long  *naxes,   /* I - size of each dimension                    */
+           long  *blc,     /* I - 'bottom left corner' of the subsection    */
+           long  *trc,     /* I - 'top right corner' of the subsection      */
+           long  *inc,     /* I - increment to be applied in each dimension */
+           float *array,   /* O - array to be filled and returned           */
+           char *flagval,  /* O - set to 1 if corresponding value is null   */
+           int  *anynul,   /* O - set to 1 if any values are null; else 0   */
+           int  *status)   /* IO - error status                             */
+/*
+  Read a subsection of data values from an image or a table column.
+  This routine is set up to handle a maximum of nine dimensions.
+*/
+{
+    long ii,i0, i1,i2,i3,i4,i5,i6,i7,i8,row,rstr,rstp,rinc;
+    long str[9],stp[9],incr[9],dsize[10];
+    LONGLONG blcll[9], trcll[9];
+    long felem, nelem, nultyp, ninc, numcol;
+    int hdutype, anyf;
+    float nulval = 0;
+    char msg[FLEN_ERRMSG];
+    int nullcheck = 2;
+
+    if (naxis < 1 || naxis > 9)
+    {
+        sprintf(msg, "NAXIS = %d in call to ffgsve is out of range", naxis);
+        ffpmsg(msg);
+        return(*status = BAD_DIMEN);
+    }
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        for (ii=0; ii < naxis; ii++) {
+	    blcll[ii] = blc[ii];
+	    trcll[ii] = trc[ii];
+	}
+
+        fits_read_compressed_img(fptr, TFLOAT, blcll, trcll, inc,
+            nullcheck, NULL, array, flagval, anynul, status);
+        return(*status);
+    }
+
+/*
+    if this is a primary array, then the input COLNUM parameter should
+    be interpreted as the row number, and we will alway read the image
+    data from column 2 (any group parameters are in column 1).
+*/
+    if (ffghdt(fptr, &hdutype, status) > 0)
+        return(*status);
+
+    if (hdutype == IMAGE_HDU)
+    {
+        /* this is a primary array, or image extension */
+        if (colnum == 0)
+        {
+            rstr = 1;
+            rstp = 1;
+        }
+        else
+        {
+            rstr = colnum;
+            rstp = colnum;
+        }
+        rinc = 1;
+        numcol = 2;
+    }
+    else
+    {
+        /* this is a table, so the row info is in the (naxis+1) elements */
+        rstr = blc[naxis];
+        rstp = trc[naxis];
+        rinc = inc[naxis];
+        numcol = colnum;
+    }
+
+    nultyp = 2;
+    if (anynul)
+        *anynul = FALSE;
+
+    i0 = 0;
+    for (ii = 0; ii < 9; ii++)
+    {
+        str[ii] = 1;
+        stp[ii] = 1;
+        incr[ii] = 1;
+        dsize[ii] = 1;
+    }
+
+    for (ii = 0; ii < naxis; ii++)
+    {
+      if (trc[ii] < blc[ii])
+      {
+        sprintf(msg, "ffgsve: illegal range specified for axis %ld", ii + 1);
+        ffpmsg(msg);
+        return(*status = BAD_PIX_NUM);
+      }
+
+      str[ii] = blc[ii];
+      stp[ii] = trc[ii];
+      incr[ii] = inc[ii];
+      dsize[ii + 1] = dsize[ii] * naxes[ii];
+    }
+
+    if (naxis == 1 && naxes[0] == 1)
+    {
+      /* This is not a vector column, so read all the rows at once */
+      nelem = (rstp - rstr) / rinc + 1;
+      ninc = rinc;
+      rstp = rstr;
+    }
+    else
+    {
+      /* have to read each row individually, in all dimensions */
+      nelem = (stp[0] - str[0]) / inc[0] + 1;
+      ninc = incr[0];
+    }
+
+    for (row = rstr; row <= rstp; row += rinc)
+    {
+     for (i8 = str[8]; i8 <= stp[8]; i8 += incr[8])
+     {
+      for (i7 = str[7]; i7 <= stp[7]; i7 += incr[7])
+      {
+       for (i6 = str[6]; i6 <= stp[6]; i6 += incr[6])
+       {
+        for (i5 = str[5]; i5 <= stp[5]; i5 += incr[5])
+        {
+         for (i4 = str[4]; i4 <= stp[4]; i4 += incr[4])
+         {
+          for (i3 = str[3]; i3 <= stp[3]; i3 += incr[3])
+          {
+           for (i2 = str[2]; i2 <= stp[2]; i2 += incr[2])
+           {
+            for (i1 = str[1]; i1 <= stp[1]; i1 += incr[1])
+            {
+              felem=str[0] + (i1 - 1) * dsize[1] + (i2 - 1) * dsize[2] + 
+                             (i3 - 1) * dsize[3] + (i4 - 1) * dsize[4] +
+                             (i5 - 1) * dsize[5] + (i6 - 1) * dsize[6] +
+                             (i7 - 1) * dsize[7] + (i8 - 1) * dsize[8];
+
+              if ( ffgcle(fptr, numcol, row, felem, nelem, ninc, nultyp,
+                   nulval, &array[i0], &flagval[i0], &anyf, status) > 0)
+                   return(*status);
+
+              if (anyf && anynul)
+                  *anynul = TRUE;
+
+              i0 += nelem;
+            }
+           }
+          }
+         }
+        }
+       }
+      }
+     }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffggpe( fitsfile *fptr,   /* I - FITS file pointer                       */
+            long  group,      /* I - group to read (1 = 1st group)           */
+            long  firstelem,  /* I - first vector element to read (1 = 1st)  */
+            long  nelem,      /* I - number of values to read                */
+            float *array,     /* O - array of values that are returned       */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of group parameters from the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+*/
+{
+    long row;
+    int idummy;
+    char cdummy;
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffgcle(fptr, 1, row, firstelem, nelem, 1, 1, 0.F,
+               array, &cdummy, &idummy, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcve(fitsfile *fptr,   /* I - FITS file pointer                       */
+           int  colnum,      /* I - number of column to read (1 = 1st col)  */
+           LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+           LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+           LONGLONG  nelem,      /* I - number of values to read                */
+           float nulval,     /* I - value for null pixels                   */
+           float *array,     /* O - array of values that are read           */
+           int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+           int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from a column in the current FITS HDU. Automatic
+  datatype conversion will be performed if the datatype of the column does not
+  match the datatype of the array parameter. The output values will be scaled 
+  by the FITS TSCALn and TZEROn values if these values have been defined.
+  Any undefined pixels will be set equal to the value of 'nulval' unless
+  nulval = 0 in which case no checks for undefined pixels will be made.
+*/
+{
+    char cdummy;
+
+    ffgcle(fptr, colnum, firstrow, firstelem, nelem, 1, 1, nulval,
+           array, &cdummy, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcvc(fitsfile *fptr,   /* I - FITS file pointer                       */
+           int  colnum,      /* I - number of column to read (1 = 1st col)  */
+           LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+           LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+           LONGLONG  nelem,      /* I - number of values to read                */
+           float nulval,     /* I - value for null pixels                   */
+           float *array,     /* O - array of values that are read           */
+           int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+           int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from a column in the current FITS HDU. Automatic
+  datatype conversion will be performed if the datatype of the column does not
+  match the datatype of the array parameter. The output values will be scaled 
+  by the FITS TSCALn and TZEROn values if these values have been defined.
+  Any undefined pixels will be set equal to the value of 'nulval' unless
+  nulval = 0 in which case no checks for undefined pixels will be made.
+
+  TSCAL and ZERO should not be used with complex values. 
+*/
+{
+    char cdummy;
+
+    /* a complex value is interpreted as a pair of float values, thus */
+    /* need to multiply the first element and number of elements by 2 */
+
+    ffgcle(fptr, colnum, firstrow, (firstelem - 1) * 2 + 1, nelem *2,
+           1, 1, nulval, array, &cdummy, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcfe(fitsfile *fptr,   /* I - FITS file pointer                       */
+           int  colnum,      /* I - number of column to read (1 = 1st col)  */
+           LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+           LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+           LONGLONG  nelem,      /* I - number of values to read                */
+           float *array,     /* O - array of values that are read           */
+           char *nularray,   /* O - array of flags: 1 if null pixel; else 0 */
+           int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+           int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from a column in the current FITS HDU. Automatic
+  datatype conversion will be performed if the datatype of the column does not
+  match the datatype of the array parameter. The output values will be scaled 
+  by the FITS TSCALn and TZEROn values if these values have been defined.
+  Nularray will be set = 1 if the corresponding array pixel is undefined, 
+  otherwise nularray will = 0.
+*/
+{
+    float dummy = 0;
+
+    ffgcle(fptr, colnum, firstrow, firstelem, nelem, 1, 2, dummy,
+           array, nularray, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcfc(fitsfile *fptr,   /* I - FITS file pointer                       */
+           int  colnum,      /* I - number of column to read (1 = 1st col)  */
+           LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+           LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+           LONGLONG  nelem,      /* I - number of values to read                */
+           float *array,     /* O - array of values that are read           */
+           char *nularray,   /* O - array of flags: 1 if null pixel; else 0 */
+           int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+           int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from a column in the current FITS HDU. Automatic
+  datatype conversion will be performed if the datatype of the column does not
+  match the datatype of the array parameter. The output values will be scaled 
+  by the FITS TSCALn and TZEROn values if these values have been defined.
+  Nularray will be set = 1 if the corresponding array pixel is undefined, 
+  otherwise nularray will = 0.
+
+  TSCAL and ZERO should not be used with complex values. 
+*/
+{
+    long ii, jj;
+    float dummy = 0;
+    char *carray;
+
+    /* a complex value is interpreted as a pair of float values, thus */
+    /* need to multiply the first element and number of elements by 2 */
+    
+    /* allocate temporary array */
+    carray = (char *) calloc( (size_t) (nelem * 2), 1); 
+
+    ffgcle(fptr, colnum, firstrow, (firstelem - 1) * 2 + 1, nelem * 2,
+           1, 2, dummy, array, carray, anynul, status);
+
+    for (ii = 0, jj = 0; jj < nelem; ii += 2, jj++)
+    {
+       if (carray[ii] || carray[ii + 1])
+          nularray[jj] = 1;
+       else
+          nularray[jj] = 0;
+    }
+
+    free(carray);    
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcle( fitsfile *fptr,   /* I - FITS file pointer                       */
+            int  colnum,      /* I - number of column to read (1 = 1st col)  */
+            LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+            LONGLONG firstelem,  /* I - first vector element to read (1 = 1st)  */
+            LONGLONG  nelem,      /* I - number of values to read                */
+            long  elemincre,  /* I - pixel increment; e.g., 2 = every other  */
+            int   nultyp,     /* I - null value handling code:               */
+                              /*     1: set undefined pixels = nulval        */
+                              /*     2: set nularray=1 for undefined pixels  */
+            float nulval,     /* I - value for null pixels if nultyp = 1     */
+            float *array,     /* O - array of values that are read           */
+            char *nularray,   /* O - array of flags = 1 if nultyp = 2        */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from a column in the current FITS HDU.
+  The column number may refer to a real column in an ASCII or binary table, 
+  or it may refer be a virtual column in a 1 or more grouped FITS primary
+  array or image extension.  FITSIO treats a primary array as a binary table
+  with 2 vector columns: the first column contains the group parameters (often
+  with length = 0) and the second column contains the array of image pixels.
+  Each row of the table represents a group in the case of multigroup FITS
+  images.
+
+  The output array of values will be converted from the datatype of the column 
+  and will be scaled by the FITS TSCALn and TZEROn values if necessary.
+*/
+{
+    double scale, zero, power = 1., dtemp;
+    int tcode, maxelem, hdutype, xcode, decimals;
+    long twidth, incre;
+    long ii, xwidth, ntodo;
+    int convert, nulcheck, readcheck = 0;
+    LONGLONG repeat, startpos, elemnum, readptr, tnull;
+    LONGLONG rowlen, rownum, remain, next, rowincre;
+    char tform[20];
+    char message[81];
+    char snull[20];   /*  the FITS null value if reading from ASCII table  */
+
+    double cbuff[DBUFFSIZE / sizeof(double)]; /* align cbuff on word boundary */
+    void *buffer;
+
+    if (*status > 0 || nelem == 0)  /* inherit input status value if > 0 */
+        return(*status);
+
+    buffer = cbuff;
+
+    if (anynul)
+       *anynul = 0;
+
+    if (nultyp == 2)
+        memset(nularray, 0, (size_t) nelem);   /* initialize nullarray */
+
+    /*---------------------------------------------------*/
+    /*  Check input and get parameters about the column: */
+    /*---------------------------------------------------*/
+    if (elemincre < 0)
+        readcheck = -1;  /* don't do range checking in this case */
+
+    if ( ffgcprll( fptr, colnum, firstrow, firstelem, nelem, readcheck, &scale, &zero,
+         tform, &twidth, &tcode, &maxelem, &startpos, &elemnum, &incre,
+         &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0 )
+         return(*status);
+
+    incre *= elemincre;   /* multiply incre to just get every nth pixel */
+
+    if (tcode == TSTRING)    /* setup for ASCII tables */
+    {
+      /* get the number of implied decimal places if no explicit decmal point */
+      ffasfm(tform, &xcode, &xwidth, &decimals, status); 
+      for(ii = 0; ii < decimals; ii++)
+        power *= 10.;
+    }
+
+    /*------------------------------------------------------------------*/
+    /*  Decide whether to check for null values in the input FITS file: */
+    /*------------------------------------------------------------------*/
+    nulcheck = nultyp; /* by default check for null values in the FITS file */
+
+    if (nultyp == 1 && nulval == 0)
+       nulcheck = 0;    /* calling routine does not want to check for nulls */
+
+    else if (tcode%10 == 1 &&        /* if reading an integer column, and  */ 
+            tnull == NULL_UNDEFINED) /* if a null value is not defined,    */
+            nulcheck = 0;            /* then do not check for null values. */
+
+    else if (tcode == TSHORT && (tnull > SHRT_MAX || tnull < SHRT_MIN) )
+            nulcheck = 0;            /* Impossible null value */
+
+    else if (tcode == TBYTE && (tnull > 255 || tnull < 0) )
+            nulcheck = 0;            /* Impossible null value */
+
+    else if (tcode == TSTRING && snull[0] == ASCII_NULL_UNDEFINED)
+         nulcheck = 0;
+
+    /*----------------------------------------------------------------------*/
+    /*  If FITS column and output data array have same datatype, then we do */
+    /*  not need to use a temporary buffer to store intermediate datatype.  */
+    /*----------------------------------------------------------------------*/
+    convert = 1;
+    if (tcode == TFLOAT) /* Special Case:                        */
+    {                             /* no type convertion required, so read */
+        maxelem = (int) nelem;          /* data directly into output buffer.    */
+
+        if (nulcheck == 0 && scale == 1. && zero == 0.)
+            convert = 0;  /* no need to scale data or find nulls */
+    }
+
+    /*---------------------------------------------------------------------*/
+    /*  Now read the pixels from the FITS column. If the column does not   */
+    /*  have the same datatype as the output array, then we have to read   */
+    /*  the raw values into a temporary buffer (of limited size).  In      */
+    /*  the case of a vector colum read only 1 vector of values at a time  */
+    /*  then skip to the next row if more values need to be read.          */
+    /*  After reading the raw values, then call the fffXXYY routine to (1) */
+    /*  test for undefined values, (2) convert the datatype if necessary,  */
+    /*  and (3) scale the values by the FITS TSCALn and TZEROn linear      */
+    /*  scaling parameters.                                                */
+    /*---------------------------------------------------------------------*/
+    remain = nelem;           /* remaining number of values to read */
+    next = 0;                 /* next element in array to be read   */
+    rownum = 0;               /* row number, relative to firstrow   */
+
+    while (remain)
+    {
+        /* limit the number of pixels to read at one time to the number that
+           will fit in the buffer or to the number of pixels that remain in
+           the current vector, which ever is smaller.
+        */
+        ntodo = (long) minvalue(remain, maxelem);
+        if (elemincre >= 0)
+        {
+          ntodo = (long) minvalue(ntodo, ((repeat - elemnum - 1)/elemincre +1));
+        }
+        else
+        {
+          ntodo = (long) minvalue(ntodo, (elemnum/(-elemincre) +1));
+        }
+
+        readptr = startpos + ((LONGLONG)rownum * rowlen) + (elemnum * (incre / elemincre));
+
+        switch (tcode) 
+        {
+            case (TFLOAT):
+                ffgr4b(fptr, readptr, ntodo, incre, &array[next], status);
+                if (convert)
+                    fffr4r4(&array[next], ntodo, scale, zero, nulcheck, 
+                           nulval, &nularray[next], anynul, 
+                           &array[next], status);
+                break;
+            case (TBYTE):
+                ffgi1b(fptr, readptr, ntodo, incre, (unsigned char *) buffer,
+                       status);
+                fffi1r4((unsigned char *) buffer, ntodo, scale, zero, nulcheck, 
+                    (unsigned char) tnull, nulval, &nularray[next], anynul, 
+                     &array[next], status);
+                break;
+            case (TSHORT):
+                ffgi2b(fptr, readptr, ntodo, incre, (short  *) buffer, status);
+                fffi2r4((short  *) buffer, ntodo, scale, zero, nulcheck, 
+                       (short) tnull, nulval, &nularray[next], anynul, 
+                       &array[next], status);
+                break;
+            case (TLONG):
+                ffgi4b(fptr, readptr, ntodo, incre, (INT32BIT *) buffer,
+                       status);
+                fffi4r4((INT32BIT *) buffer, ntodo, scale, zero, nulcheck, 
+                       (INT32BIT) tnull, nulval, &nularray[next], anynul, 
+                       &array[next], status);
+                break;
+
+            case (TLONGLONG):
+                ffgi8b(fptr, readptr, ntodo, incre, (long *) buffer, status);
+                fffi8r4( (LONGLONG *) buffer, ntodo, scale, zero, 
+                           nulcheck, tnull, nulval, &nularray[next], 
+                            anynul, &array[next], status);
+                break;
+            case (TDOUBLE):
+                ffgr8b(fptr, readptr, ntodo, incre, (double *) buffer, status);
+                fffr8r4((double *) buffer, ntodo, scale, zero, nulcheck, 
+                          nulval, &nularray[next], anynul, 
+                          &array[next], status);
+                break;
+            case (TSTRING):
+                ffmbyt(fptr, readptr, REPORT_EOF, status);
+       
+                if (incre == twidth)    /* contiguous bytes */
+                     ffgbyt(fptr, ntodo * twidth, buffer, status);
+                else
+                     ffgbytoff(fptr, twidth, ntodo, incre - twidth, buffer,
+                               status);
+
+                fffstrr4((char *) buffer, ntodo, scale, zero, twidth, power,
+                     nulcheck, snull, nulval, &nularray[next], anynul,
+                     &array[next], status);
+                break;
+
+
+            default:  /*  error trap for invalid column format */
+                sprintf(message, 
+                   "Cannot read numbers from column %d which has format %s",
+                    colnum, tform);
+                ffpmsg(message);
+                if (hdutype == ASCII_TBL)
+                    return(*status = BAD_ATABLE_FORMAT);
+                else
+                    return(*status = BAD_BTABLE_FORMAT);
+
+        } /* End of switch block */
+
+        /*-------------------------*/
+        /*  Check for fatal error  */
+        /*-------------------------*/
+        if (*status > 0)  /* test for error during previous read operation */
+        {
+	  dtemp = (double) next;
+          if (hdutype > 0)
+            sprintf(message,
+            "Error reading elements %.0f thru %.0f from column %d (ffgcle).",
+              dtemp+1., dtemp+ntodo, colnum);
+          else
+            sprintf(message,
+            "Error reading elements %.0f thru %.0f from image (ffgcle).",
+              dtemp+1., dtemp+ntodo);
+
+          ffpmsg(message);
+          return(*status);
+        }
+
+        /*--------------------------------------------*/
+        /*  increment the counters for the next loop  */
+        /*--------------------------------------------*/
+        remain -= ntodo;
+        if (remain)
+        {
+            next += ntodo;
+            elemnum = elemnum + (ntodo * elemincre);
+
+            if (elemnum >= repeat)  /* completed a row; start on later row */
+            {
+                rowincre = elemnum / repeat;
+                rownum += rowincre;
+                elemnum = elemnum - (rowincre * repeat);
+            }
+            else if (elemnum < 0)  /* completed a row; start on a previous row */
+            {
+                rowincre = (-elemnum - 1) / repeat + 1;
+                rownum -= rowincre;
+                elemnum = (rowincre * repeat) + elemnum;
+            }
+        }
+    }  /*  End of main while Loop  */
+
+
+    /*--------------------------------*/
+    /*  check for numerical overflow  */
+    /*--------------------------------*/
+    if (*status == OVERFLOW_ERR)
+    {
+        ffpmsg(
+        "Numerical overflow during type conversion while reading FITS data.");
+        *status = NUM_OVERFLOW;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi1r4(unsigned char *input, /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            unsigned char tnull,  /* I - value of FITS TNULLn keyword if any */
+            float nullval,        /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            float *output,        /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (float) input[ii];  /* copy input to output */
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                output[ii] = (float) (( (double) input[ii] ) * scale + zero);
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                    output[ii] = (float) input[ii];
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    output[ii] = (float) (( (double) input[ii] ) * scale + zero);
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi2r4(short *input,         /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            short tnull,          /* I - value of FITS TNULLn keyword if any */
+            float nullval,        /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            float *output,        /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (float) input[ii];  /* copy input to output */
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                output[ii] = (float) (input[ii] * scale + zero);
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                    output[ii] = (float) input[ii];
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    output[ii] = (float) (input[ii] * scale + zero);
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi4r4(INT32BIT *input,      /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            INT32BIT tnull,       /* I - value of FITS TNULLn keyword if any */
+            float nullval,        /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            float *output,        /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (float) input[ii];  /* copy input to output */
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                output[ii] = (float) (input[ii] * scale + zero);
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                    output[ii] = (float) input[ii];
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    output[ii] = (float) (input[ii] * scale + zero);
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi8r4(LONGLONG *input,      /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            LONGLONG tnull,       /* I - value of FITS TNULLn keyword if any */
+            float nullval,        /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            float *output,        /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (float) input[ii];  /* copy input to output */
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                output[ii] = (float) (input[ii] * scale + zero);
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                    output[ii] = (float) input[ii];
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    output[ii] = (float) (input[ii] * scale + zero);
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffr4r4(float *input,         /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            float nullval,        /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            float *output,        /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to NaN.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    short *sptr, iret;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            memcpy(output, input, ntodo * sizeof(float) );
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                output[ii] = (float) (input[ii] * scale + zero);
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        sptr = (short *) input;
+
+#if BYTESWAPPED && MACHINE != VAXVMS && MACHINE != ALPHAVMS
+        sptr++;       /* point to MSBs */
+#endif
+
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++, sptr += 2)
+            {
+              if (0 != (iret = fnan(*sptr) ) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                    {
+                        nullarray[ii] = 1;
+                       /* explicitly set value in case output contains a NaN */
+                        output[ii] = FLOATNULLVALUE;
+                    }
+                  }
+                  else            /* it's an underflow */
+                     output[ii] = 0;
+              }
+              else
+                output[ii] = input[ii];
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++, sptr += 2)
+            {
+              if (0 != (iret = fnan(*sptr) ) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {  
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                    {
+                        nullarray[ii] = 1;
+                       /* explicitly set value in case output contains a NaN */
+                        output[ii] = FLOATNULLVALUE;
+                    }
+                  }
+                  else            /* it's an underflow */
+                     output[ii] = (float) zero;
+              }
+              else
+                  output[ii] = (float) (input[ii] * scale + zero);
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffr8r4(double *input,        /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            float nullval,        /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            float *output,        /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to NaN.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    short *sptr, iret;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (float) input[ii]; /* copy input to output */
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                output[ii] = (float) (input[ii] * scale + zero);
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        sptr = (short *) input;
+
+#if BYTESWAPPED && MACHINE != VAXVMS && MACHINE != ALPHAVMS
+        sptr += 3;       /* point to MSBs */
+#endif
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++, sptr += 4)
+            {
+              if (0 != (iret = dnan(*sptr)) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                  }
+                  else            /* it's an underflow */
+                     output[ii] = 0;
+              }
+              else
+                  output[ii] = (float) input[ii];
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++, sptr += 4)
+            {
+              if (0 != (iret = dnan(*sptr)) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {  
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                  }
+                  else            /* it's an underflow */
+                     output[ii] = (float) zero;
+              }
+              else
+                  output[ii] = (float) (input[ii] * scale + zero);
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffstrr4(char *input,         /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            long twidth,          /* I - width of each substring of chars    */
+            double implipower,    /* I - power of 10 of implied decimal      */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            char  *snull,         /* I - value of FITS null string, if any   */
+            float nullval,        /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            float *output,        /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file. Check
+  for null values and do scaling if required. The nullcheck code value
+  determines how any null values in the input array are treated. A null
+  value is an input pixel that is equal to snull.  If nullcheck= 0, then
+  no special checking for nulls is performed.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    int nullen;
+    long ii;
+    double dvalue;
+    char *cstring, message[81];
+    char *cptr, *tpos;
+    char tempstore, chrzero = '0';
+    double val, power;
+    int exponent, sign, esign, decpt;
+
+    nullen = strlen(snull);
+    cptr = input;  /* pointer to start of input string */
+    for (ii = 0; ii < ntodo; ii++)
+    {
+      cstring = cptr;
+      /* temporarily insert a null terminator at end of the string */
+      tpos = cptr + twidth;
+      tempstore = *tpos;
+      *tpos = 0;
+
+      /* check if null value is defined, and if the    */
+      /* column string is identical to the null string */
+      if (snull[0] != ASCII_NULL_UNDEFINED && 
+         !strncmp(snull, cptr, nullen) )
+      {
+        if (nullcheck)  
+        {
+          *anynull = 1;    
+          if (nullcheck == 1)
+            output[ii] = nullval;
+          else
+            nullarray[ii] = 1;
+        }
+        cptr += twidth;
+      }
+      else
+      {
+        /* value is not the null value, so decode it */
+        /* remove any embedded blank characters from the string */
+
+        decpt = 0;
+        sign = 1;
+        val  = 0.;
+        power = 1.;
+        exponent = 0;
+        esign = 1;
+
+        while (*cptr == ' ')               /* skip leading blanks */
+           cptr++;
+
+        if (*cptr == '-' || *cptr == '+')  /* check for leading sign */
+        {
+          if (*cptr == '-')
+             sign = -1;
+
+          cptr++;
+
+          while (*cptr == ' ')         /* skip blanks between sign and value */
+            cptr++;
+        }
+
+        while (*cptr >= '0' && *cptr <= '9')
+        {
+          val = val * 10. + *cptr - chrzero;  /* accumulate the value */
+          cptr++;
+
+          while (*cptr == ' ')         /* skip embedded blanks in the value */
+            cptr++;
+        }
+
+        if (*cptr == '.' || *cptr == ',')       /* check for decimal point */
+        {
+          decpt = 1;       /* set flag to show there was a decimal point */
+          cptr++;
+          while (*cptr == ' ')         /* skip any blanks */
+            cptr++;
+
+          while (*cptr >= '0' && *cptr <= '9')
+          {
+            val = val * 10. + *cptr - chrzero;  /* accumulate the value */
+            power = power * 10.;
+            cptr++;
+
+            while (*cptr == ' ')         /* skip embedded blanks in the value */
+              cptr++;
+          }
+        }
+
+        if (*cptr == 'E' || *cptr == 'D')  /* check for exponent */
+        {
+          cptr++;
+          while (*cptr == ' ')         /* skip blanks */
+              cptr++;
+  
+          if (*cptr == '-' || *cptr == '+')  /* check for exponent sign */
+          {
+            if (*cptr == '-')
+               esign = -1;
+
+            cptr++;
+
+            while (*cptr == ' ')        /* skip blanks between sign and exp */
+              cptr++;
+          }
+
+          while (*cptr >= '0' && *cptr <= '9')
+          {
+            exponent = exponent * 10 + *cptr - chrzero;  /* accumulate exp */
+            cptr++;
+
+            while (*cptr == ' ')         /* skip embedded blanks */
+              cptr++;
+          }
+        }
+
+        if (*cptr  != 0)  /* should end up at the null terminator */
+        {
+          sprintf(message, "Cannot read number from ASCII table");
+          ffpmsg(message);
+          sprintf(message, "Column field = %s.", cstring);
+          ffpmsg(message);
+          /* restore the char that was overwritten by the null */
+          *tpos = tempstore;
+          return(*status = BAD_C2D);
+        }
+
+        if (!decpt)  /* if no explicit decimal, use implied */
+           power = implipower;
+
+        dvalue = (sign * val / power) * pow(10., (double) (esign * exponent));
+
+        output[ii] = (float) (dvalue * scale + zero);   /* apply the scaling */
+
+      }
+      /* restore the char that was overwritten by the null */
+      *tpos = tempstore;
+    }
+    return(*status);
+}
diff --git a/src/plugins/cfitsio/getcoli.c b/src/plugins/cfitsio/getcoli.c
new file mode 100644
index 0000000..e3386e8
--- /dev/null
+++ b/src/plugins/cfitsio/getcoli.c
@@ -0,0 +1,1901 @@
+/*  This file, getcoli.c, contains routines that read data elements from   */
+/*  a FITS image or table, with short datatype.                            */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+
+#include <math.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <string.h>
+#include "fitsio2.h"
+
+/*--------------------------------------------------------------------------*/
+int ffgpvi( fitsfile *fptr,   /* I - FITS file pointer                       */
+            long  group,      /* I - group to read (1 = 1st group)           */
+            LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+            LONGLONG  nelem,      /* I - number of values to read                */
+            short nulval,     /* I - value for undefined pixels              */
+            short *array,     /* O - array of values that are returned       */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+  Undefined elements will be set equal to NULVAL, unless NULVAL=0
+  in which case no checking for undefined values will be performed.
+  ANYNUL is returned with a value of .true. if any pixels are undefined.
+*/
+{
+    long row;
+    char cdummy;
+    int nullcheck = 1;
+    short nullvalue;
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+         nullvalue = nulval;  /* set local variable */
+        fits_read_compressed_pixels(fptr, TSHORT, firstelem, nelem,
+            nullcheck, &nullvalue, array, NULL, anynul, status);
+        return(*status);
+    }
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffgcli(fptr, 2, row, firstelem, nelem, 1, 1, nulval,
+               array, &cdummy, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgpfi( fitsfile *fptr,   /* I - FITS file pointer                       */
+            long  group,      /* I - group to read (1 = 1st group)           */
+            LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+            LONGLONG  nelem,      /* I - number of values to read                */
+            short *array,     /* O - array of values that are returned       */
+            char *nularray,   /* O - array of null pixel flags               */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+  Any undefined pixels in the returned array will be set = 0 and the 
+  corresponding nularray value will be set = 1.
+  ANYNUL is returned with a value of .true. if any pixels are undefined.
+*/
+{
+    long row;
+    int nullcheck = 2;
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        fits_read_compressed_pixels(fptr, TSHORT, firstelem, nelem,
+            nullcheck, NULL, array, nularray, anynul, status);
+        return(*status);
+    }
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffgcli(fptr, 2, row, firstelem, nelem, 1, 2, 0,
+               array, nularray, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffg2di(fitsfile *fptr,  /* I - FITS file pointer                       */
+           long  group,     /* I - group to read (1 = 1st group)           */
+           short nulval,    /* set undefined pixels equal to this          */
+           LONGLONG  ncols,     /* I - number of pixels in each row of array   */
+           LONGLONG  naxis1,    /* I - FITS image NAXIS1 value                 */
+           LONGLONG  naxis2,    /* I - FITS image NAXIS2 value                 */
+           short *array,    /* O - array to be filled and returned         */
+           int  *anynul,    /* O - set to 1 if any values are null; else 0 */
+           int  *status)    /* IO - error status                           */
+/*
+  Read an entire 2-D array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being read).  Any null
+  values in the array will be set equal to the value of nulval, unless
+  nulval = 0 in which case no null checking will be performed.
+*/
+{
+    /* call the 3D reading routine, with the 3rd dimension = 1 */
+
+    ffg3di(fptr, group, nulval, ncols, naxis2, naxis1, naxis2, 1, array, 
+           anynul, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffg3di(fitsfile *fptr,  /* I - FITS file pointer                       */
+           long  group,     /* I - group to read (1 = 1st group)           */
+           short nulval,    /* set undefined pixels equal to this          */
+           LONGLONG  ncols,     /* I - number of pixels in each row of array   */
+           LONGLONG  nrows,     /* I - number of rows in each plane of array   */
+           LONGLONG  naxis1,    /* I - FITS image NAXIS1 value                 */
+           LONGLONG  naxis2,    /* I - FITS image NAXIS2 value                 */
+           LONGLONG  naxis3,    /* I - FITS image NAXIS3 value                 */
+           short *array,    /* O - array to be filled and returned         */
+           int  *anynul,    /* O - set to 1 if any values are null; else 0 */
+           int  *status)    /* IO - error status                           */
+/*
+  Read an entire 3-D array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being read).  Any null
+  values in the array will be set equal to the value of nulval, unless
+  nulval = 0 in which case no null checking will be performed.
+*/
+{
+    long tablerow, ii, jj;
+    LONGLONG nfits, narray;
+    char cdummy;
+    int nullcheck = 1;
+    long inc[] = {1,1,1};
+    LONGLONG fpixel[] = {1,1,1};
+    LONGLONG lpixel[3];
+    short nullvalue;
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        lpixel[0] = ncols;
+        lpixel[1] = nrows;
+        lpixel[2] = naxis3;
+        nullvalue = nulval;  /* set local variable */
+
+        fits_read_compressed_img(fptr, TSHORT, fpixel, lpixel, inc,
+            nullcheck, &nullvalue, array, NULL, anynul, status);
+        return(*status);
+    }
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+    tablerow=maxvalue(1,group);
+
+    if (ncols == naxis1 && nrows == naxis2)  /* arrays have same size? */
+    {
+       /* all the image pixels are contiguous, so read all at once */
+       ffgcli(fptr, 2, tablerow, 1, naxis1 * naxis2 * naxis3, 1, 1, nulval,
+               array, &cdummy, anynul, status);
+       return(*status);
+    }
+
+    if (ncols < naxis1 || nrows < naxis2)
+       return(*status = BAD_DIMEN);
+
+    nfits = 1;   /* next pixel in FITS image to read */
+    narray = 0;  /* next pixel in output array to be filled */
+
+    /* loop over naxis3 planes in the data cube */
+    for (jj = 0; jj < naxis3; jj++)
+    {
+      /* loop over the naxis2 rows in the FITS image, */
+      /* reading naxis1 pixels to each row            */
+
+      for (ii = 0; ii < naxis2; ii++)
+      {
+       if (ffgcli(fptr, 2, tablerow, nfits, naxis1, 1, 1, nulval,
+          &array[narray], &cdummy, anynul, status) > 0)
+          return(*status);
+
+       nfits += naxis1;
+       narray += ncols;
+      }
+      narray += (nrows - naxis2) * ncols;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgsvi(fitsfile *fptr, /* I - FITS file pointer                         */
+           int  colnum,    /* I - number of the column to read (1 = 1st)    */
+           int naxis,      /* I - number of dimensions in the FITS array    */
+           long  *naxes,   /* I - size of each dimension                    */
+           long  *blc,     /* I - 'bottom left corner' of the subsection    */
+           long  *trc,     /* I - 'top right corner' of the subsection      */
+           long  *inc,     /* I - increment to be applied in each dimension */
+           short nulval,   /* I - value to set undefined pixels             */
+           short *array,   /* O - array to be filled and returned           */
+           int  *anynul,   /* O - set to 1 if any values are null; else 0   */
+           int  *status)   /* IO - error status                             */
+/*
+  Read a subsection of data values from an image or a table column.
+  This routine is set up to handle a maximum of nine dimensions.
+*/
+{
+    long ii,i0, i1,i2,i3,i4,i5,i6,i7,i8,row,rstr,rstp,rinc;
+    long str[9],stp[9],incr[9],dir[9];
+    long nelem, nultyp, ninc, numcol;
+    LONGLONG felem, dsize[10], blcll[9], trcll[9];
+    int hdutype, anyf;
+    char ldummy, msg[FLEN_ERRMSG];
+    int nullcheck = 1;
+    short nullvalue;
+
+    if (naxis < 1 || naxis > 9)
+    {
+        sprintf(msg, "NAXIS = %d in call to ffgsvi is out of range", naxis);
+        ffpmsg(msg);
+        return(*status = BAD_DIMEN);
+    }
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        for (ii=0; ii < naxis; ii++) {
+	    blcll[ii] = blc[ii];
+	    trcll[ii] = trc[ii];
+	}
+
+        nullvalue = nulval;  /* set local variable */
+
+        fits_read_compressed_img(fptr, TSHORT, blcll, trcll, inc,
+            nullcheck, &nullvalue, array, NULL, anynul, status);
+
+        return(*status);
+    }
+
+/*
+    if this is a primary array, then the input COLNUM parameter should
+    be interpreted as the row number, and we will alway read the image
+    data from column 2 (any group parameters are in column 1).
+*/
+    if (ffghdt(fptr, &hdutype, status) > 0)
+        return(*status);
+
+    if (hdutype == IMAGE_HDU)
+    {
+        /* this is a primary array, or image extension */
+        if (colnum == 0)
+        {
+            rstr = 1;
+            rstp = 1;
+        }
+        else
+        {
+            rstr = colnum;
+            rstp = colnum;
+        }
+        rinc = 1;
+        numcol = 2;
+    }
+    else
+    {
+        /* this is a table, so the row info is in the (naxis+1) elements */
+        rstr = blc[naxis];
+        rstp = trc[naxis];
+        rinc = inc[naxis];
+        numcol = colnum;
+    }
+
+    nultyp = 1;
+    if (anynul)
+        *anynul = FALSE;
+
+    i0 = 0;
+    for (ii = 0; ii < 9; ii++)
+    {
+        str[ii] = 1;
+        stp[ii] = 1;
+        incr[ii] = 1;
+        dsize[ii] = 1;
+        dir[ii] = 1;
+    }
+
+    for (ii = 0; ii < naxis; ii++)
+    {
+      if (trc[ii] < blc[ii])
+      {
+        if (hdutype == IMAGE_HDU)
+        {
+           dir[ii] = -1;
+        }
+        else
+        {
+          sprintf(msg, "ffgsvi: illegal range specified for axis %ld", ii + 1);
+          ffpmsg(msg);
+          return(*status = BAD_PIX_NUM);
+        }
+      }
+
+      str[ii] = blc[ii];
+      stp[ii] = trc[ii];
+      incr[ii] = inc[ii];
+      dsize[ii + 1] = dsize[ii] * naxes[ii];
+      dsize[ii] = dsize[ii] * dir[ii];
+    }
+    dsize[naxis] = dsize[naxis] * dir[naxis];
+
+    if (naxis == 1 && naxes[0] == 1)
+    {
+      /* This is not a vector column, so read all the rows at once */
+      nelem = (rstp - rstr) / rinc + 1;
+      ninc = rinc;
+      rstp = rstr;
+    }
+    else
+    {
+      /* have to read each row individually, in all dimensions */
+      nelem = (stp[0]*dir[0] - str[0]*dir[0]) / inc[0] + 1;
+      ninc = incr[0] * dir[0];
+    }
+
+    for (row = rstr; row <= rstp; row += rinc)
+    {
+     for (i8 = str[8]*dir[8]; i8 <= stp[8]*dir[8]; i8 += incr[8])
+     {
+      for (i7 = str[7]*dir[7]; i7 <= stp[7]*dir[7]; i7 += incr[7])
+      {
+       for (i6 = str[6]*dir[6]; i6 <= stp[6]*dir[6]; i6 += incr[6])
+       {
+        for (i5 = str[5]*dir[5]; i5 <= stp[5]*dir[5]; i5 += incr[5])
+        {
+         for (i4 = str[4]*dir[4]; i4 <= stp[4]*dir[4]; i4 += incr[4])
+         {
+          for (i3 = str[3]*dir[3]; i3 <= stp[3]*dir[3]; i3 += incr[3])
+          {
+           for (i2 = str[2]*dir[2]; i2 <= stp[2]*dir[2]; i2 += incr[2])
+           {
+            for (i1 = str[1]*dir[1]; i1 <= stp[1]*dir[1]; i1 += incr[1])
+            {
+
+              felem=str[0] + (i1 - dir[1]) * dsize[1] + (i2 - dir[2]) * dsize[2] + 
+                             (i3 - dir[3]) * dsize[3] + (i4 - dir[4]) * dsize[4] +
+                             (i5 - dir[5]) * dsize[5] + (i6 - dir[6]) * dsize[6] +
+                             (i7 - dir[7]) * dsize[7] + (i8 - dir[8]) * dsize[8];
+
+              if ( ffgcli(fptr, numcol, row, felem, nelem, ninc, nultyp,
+                   nulval, &array[i0], &ldummy, &anyf, status) > 0)
+                   return(*status);
+
+              if (anyf && anynul)
+                  *anynul = TRUE;
+
+              i0 += nelem;
+            }
+           }
+          }
+         }
+        }
+       }
+      }
+     }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgsfi(fitsfile *fptr, /* I - FITS file pointer                         */
+           int  colnum,    /* I - number of the column to read (1 = 1st)    */
+           int naxis,      /* I - number of dimensions in the FITS array    */
+           long  *naxes,   /* I - size of each dimension                    */
+           long  *blc,     /* I - 'bottom left corner' of the subsection    */
+           long  *trc,     /* I - 'top right corner' of the subsection      */
+           long  *inc,     /* I - increment to be applied in each dimension */
+           short *array,   /* O - array to be filled and returned           */
+           char *flagval,  /* O - set to 1 if corresponding value is null   */
+           int  *anynul,   /* O - set to 1 if any values are null; else 0   */
+           int  *status)   /* IO - error status                             */
+/*
+  Read a subsection of data values from an image or a table column.
+  This routine is set up to handle a maximum of nine dimensions.
+*/
+{
+    long ii,i0, i1,i2,i3,i4,i5,i6,i7,i8,row,rstr,rstp,rinc;
+    long str[9],stp[9],incr[9],dsize[10];
+    LONGLONG blcll[9], trcll[9];
+    long felem, nelem, nultyp, ninc, numcol;
+    int hdutype, anyf;
+    short nulval = 0;
+    char msg[FLEN_ERRMSG];
+    int nullcheck = 2;
+
+    if (naxis < 1 || naxis > 9)
+    {
+        sprintf(msg, "NAXIS = %d in call to ffgsvi is out of range", naxis);
+        ffpmsg(msg);
+        return(*status = BAD_DIMEN);
+    }
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        for (ii=0; ii < naxis; ii++) {
+	    blcll[ii] = blc[ii];
+	    trcll[ii] = trc[ii];
+	}
+
+        fits_read_compressed_img(fptr, TSHORT, blcll, trcll, inc,
+            nullcheck, NULL, array, flagval, anynul, status);
+        return(*status);
+    }
+
+/*
+    if this is a primary array, then the input COLNUM parameter should
+    be interpreted as the row number, and we will alway read the image
+    data from column 2 (any group parameters are in column 1).
+*/
+    if (ffghdt(fptr, &hdutype, status) > 0)
+        return(*status);
+
+    if (hdutype == IMAGE_HDU)
+    {
+        /* this is a primary array, or image extension */
+        if (colnum == 0)
+        {
+            rstr = 1;
+            rstp = 1;
+        }
+        else
+        {
+            rstr = colnum;
+            rstp = colnum;
+        }
+        rinc = 1;
+        numcol = 2;
+    }
+    else
+    {
+        /* this is a table, so the row info is in the (naxis+1) elements */
+        rstr = blc[naxis];
+        rstp = trc[naxis];
+        rinc = inc[naxis];
+        numcol = colnum;
+    }
+
+    nultyp = 2;
+    if (anynul)
+        *anynul = FALSE;
+
+    i0 = 0;
+    for (ii = 0; ii < 9; ii++)
+    {
+        str[ii] = 1;
+        stp[ii] = 1;
+        incr[ii] = 1;
+        dsize[ii] = 1;
+    }
+
+    for (ii = 0; ii < naxis; ii++)
+    {
+      if (trc[ii] < blc[ii])
+      {
+        sprintf(msg, "ffgsvi: illegal range specified for axis %ld", ii + 1);
+        ffpmsg(msg);
+        return(*status = BAD_PIX_NUM);
+      }
+
+      str[ii] = blc[ii];
+      stp[ii] = trc[ii];
+      incr[ii] = inc[ii];
+      dsize[ii + 1] = dsize[ii] * naxes[ii];
+    }
+
+    if (naxis == 1 && naxes[0] == 1)
+    {
+      /* This is not a vector column, so read all the rows at once */
+      nelem = (rstp - rstr) / rinc + 1;
+      ninc = rinc;
+      rstp = rstr;
+    }
+    else
+    {
+      /* have to read each row individually, in all dimensions */
+      nelem = (stp[0] - str[0]) / inc[0] + 1;
+      ninc = incr[0];
+    }
+
+    for (row = rstr; row <= rstp; row += rinc)
+    {
+     for (i8 = str[8]; i8 <= stp[8]; i8 += incr[8])
+     {
+      for (i7 = str[7]; i7 <= stp[7]; i7 += incr[7])
+      {
+       for (i6 = str[6]; i6 <= stp[6]; i6 += incr[6])
+       {
+        for (i5 = str[5]; i5 <= stp[5]; i5 += incr[5])
+        {
+         for (i4 = str[4]; i4 <= stp[4]; i4 += incr[4])
+         {
+          for (i3 = str[3]; i3 <= stp[3]; i3 += incr[3])
+          {
+           for (i2 = str[2]; i2 <= stp[2]; i2 += incr[2])
+           {
+            for (i1 = str[1]; i1 <= stp[1]; i1 += incr[1])
+            {
+              felem=str[0] + (i1 - 1) * dsize[1] + (i2 - 1) * dsize[2] + 
+                             (i3 - 1) * dsize[3] + (i4 - 1) * dsize[4] +
+                             (i5 - 1) * dsize[5] + (i6 - 1) * dsize[6] +
+                             (i7 - 1) * dsize[7] + (i8 - 1) * dsize[8];
+
+              if ( ffgcli(fptr, numcol, row, felem, nelem, ninc, nultyp,
+                   nulval, &array[i0], &flagval[i0], &anyf, status) > 0)
+                   return(*status);
+
+              if (anyf && anynul)
+                  *anynul = TRUE;
+
+              i0 += nelem;
+            }
+           }
+          }
+         }
+        }
+       }
+      }
+     }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffggpi( fitsfile *fptr,   /* I - FITS file pointer                       */
+            long  group,      /* I - group to read (1 = 1st group)           */
+            long  firstelem,  /* I - first vector element to read (1 = 1st)  */
+            long  nelem,      /* I - number of values to read                */
+            short *array,     /* O - array of values that are returned       */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of group parameters from the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+*/
+{
+    long row;
+    int idummy;
+    char cdummy;
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffgcli(fptr, 1, row, firstelem, nelem, 1, 1, 0,
+               array, &cdummy, &idummy, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcvi(fitsfile *fptr,   /* I - FITS file pointer                       */
+           int  colnum,      /* I - number of column to read (1 = 1st col)  */
+           LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+           LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+           LONGLONG  nelem,      /* I - number of values to read                */
+           short nulval,     /* I - value for null pixels                   */
+           short *array,     /* O - array of values that are read           */
+           int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+           int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from a column in the current FITS HDU. Automatic
+  datatype conversion will be performed if the datatype of the column does not
+  match the datatype of the array parameter. The output values will be scaled 
+  by the FITS TSCALn and TZEROn values if these values have been defined.
+  Any undefined pixels will be set equal to the value of 'nulval' unless
+  nulval = 0 in which case no checks for undefined pixels will be made.
+*/
+{
+    char cdummy;
+
+    ffgcli(fptr, colnum, firstrow, firstelem, nelem, 1, 1, nulval,
+           array, &cdummy, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcfi(fitsfile *fptr,   /* I - FITS file pointer                       */
+           int  colnum,      /* I - number of column to read (1 = 1st col)  */
+           LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+           LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+           LONGLONG  nelem,      /* I - number of values to read                */
+           short *array,     /* O - array of values that are read           */
+           char *nularray,   /* O - array of flags: 1 if null pixel; else 0 */
+           int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+           int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from a column in the current FITS HDU. Automatic
+  datatype conversion will be performed if the datatype of the column does not
+  match the datatype of the array parameter. The output values will be scaled 
+  by the FITS TSCALn and TZEROn values if these values have been defined.
+  Nularray will be set = 1 if the corresponding array pixel is undefined, 
+  otherwise nularray will = 0.
+*/
+{
+    short dummy = 0;
+
+    ffgcli(fptr, colnum, firstrow, firstelem, nelem, 1, 2, dummy,
+           array, nularray, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcli( fitsfile *fptr,   /* I - FITS file pointer                       */
+            int  colnum,      /* I - number of column to read (1 = 1st col)  */
+            LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+            LONGLONG  firstelem, /* I - first vector element to read (1 = 1st)  */
+            LONGLONG  nelem,      /* I - number of values to read                */
+            long  elemincre,  /* I - pixel increment; e.g., 2 = every other  */
+            int   nultyp,     /* I - null value handling code:               */
+                              /*     1: set undefined pixels = nulval        */
+                              /*     2: set nularray=1 for undefined pixels  */
+            short nulval,     /* I - value for null pixels if nultyp = 1     */
+            short *array,     /* O - array of values that are read           */
+            char *nularray,   /* O - array of flags = 1 if nultyp = 2        */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from a column in the current FITS HDU.
+  The column number may refer to a real column in an ASCII or binary table, 
+  or it may refer be a virtual column in a 1 or more grouped FITS primary
+  array or image extension.  FITSIO treats a primary array as a binary table
+  with 2 vector columns: the first column contains the group parameters (often
+  with length = 0) and the second column contains the array of image pixels.
+  Each row of the table represents a group in the case of multigroup FITS
+  images.
+
+  The output array of values will be converted from the datatype of the column 
+  and will be scaled by the FITS TSCALn and TZEROn values if necessary.
+*/
+{
+    double scale, zero, power = 1., dtemp;
+    int tcode, maxelem, hdutype, xcode, decimals;
+    long twidth, incre;
+    long ii, xwidth, ntodo;
+    int convert, nulcheck, readcheck = 0;
+    LONGLONG repeat, startpos, elemnum, readptr, tnull;
+    LONGLONG rowlen, rownum, remain, next, rowincre;
+    char tform[20];
+    char message[81];
+    char snull[20];   /*  the FITS null value if reading from ASCII table  */
+
+    double cbuff[DBUFFSIZE / sizeof(double)]; /* align cbuff on word boundary */
+    void *buffer;
+
+    if (*status > 0 || nelem == 0)  /* inherit input status value if > 0 */
+        return(*status);
+
+    buffer = cbuff;
+
+    if (anynul)
+        *anynul = 0;
+
+    if (nultyp == 2)
+        memset(nularray, 0, (size_t) nelem);   /* initialize nullarray */
+
+    /*---------------------------------------------------*/
+    /*  Check input and get parameters about the column: */
+    /*---------------------------------------------------*/
+    if (elemincre < 0)
+        readcheck = -1;  /* don't do range checking in this case */
+
+    if ( ffgcprll( fptr, colnum, firstrow, firstelem, nelem, readcheck, &scale, &zero,
+         tform, &twidth, &tcode, &maxelem, &startpos, &elemnum, &incre,
+         &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0 )
+         return(*status);
+
+    incre *= elemincre;   /* multiply incre to just get every nth pixel */
+
+    if (tcode == TSTRING)    /* setup for ASCII tables */
+    {
+      /* get the number of implied decimal places if no explicit decmal point */
+      ffasfm(tform, &xcode, &xwidth, &decimals, status); 
+      for(ii = 0; ii < decimals; ii++)
+        power *= 10.;
+    }
+    /*------------------------------------------------------------------*/
+    /*  Decide whether to check for null values in the input FITS file: */
+    /*------------------------------------------------------------------*/
+    nulcheck = nultyp; /* by default check for null values in the FITS file */
+
+    if (nultyp == 1 && nulval == 0)
+       nulcheck = 0;    /* calling routine does not want to check for nulls */
+
+    else if (tcode%10 == 1 &&        /* if reading an integer column, and  */ 
+            tnull == NULL_UNDEFINED) /* if a null value is not defined,    */
+            nulcheck = 0;            /* then do not check for null values. */
+
+    else if (tcode == TSHORT && (tnull > SHRT_MAX || tnull < SHRT_MIN) )
+            nulcheck = 0;            /* Impossible null value */
+
+    else if (tcode == TBYTE && (tnull > 255 || tnull < 0) )
+            nulcheck = 0;            /* Impossible null value */
+
+    else if (tcode == TSTRING && snull[0] == ASCII_NULL_UNDEFINED)
+         nulcheck = 0;
+
+    /*----------------------------------------------------------------------*/
+    /*  If FITS column and output data array have same datatype, then we do */
+    /*  not need to use a temporary buffer to store intermediate datatype.  */
+    /*----------------------------------------------------------------------*/
+    convert = 1;
+    if (tcode == TSHORT) /* Special Case:                        */
+    {                             /* no type convertion required, so read */
+        maxelem = (int) nelem;          /* data directly into output buffer.    */
+
+        if (nulcheck == 0 && scale == 1. && zero == 0.)
+            convert = 0;  /* no need to scale data or find nulls */
+    }
+
+    /*---------------------------------------------------------------------*/
+    /*  Now read the pixels from the FITS column. If the column does not   */
+    /*  have the same datatype as the output array, then we have to read   */
+    /*  the raw values into a temporary buffer (of limited size).  In      */
+    /*  the case of a vector colum read only 1 vector of values at a time  */
+    /*  then skip to the next row if more values need to be read.          */
+    /*  After reading the raw values, then call the fffXXYY routine to (1) */
+    /*  test for undefined values, (2) convert the datatype if necessary,  */
+    /*  and (3) scale the values by the FITS TSCALn and TZEROn linear      */
+    /*  scaling parameters.                                                */
+    /*---------------------------------------------------------------------*/
+    remain = nelem;           /* remaining number of values to read */
+    next = 0;                 /* next element in array to be read   */
+    rownum = 0;               /* row number, relative to firstrow   */
+
+    while (remain)
+    {
+        /* limit the number of pixels to read at one time to the number that
+           will fit in the buffer or to the number of pixels that remain in
+           the current vector, which ever is smaller.
+        */
+        ntodo = (long) minvalue(remain, maxelem);
+        if (elemincre >= 0)
+        {
+          ntodo = (long) minvalue(ntodo, ((repeat - elemnum - 1)/elemincre +1));
+        }
+        else
+        {
+          ntodo = (long) minvalue(ntodo, (elemnum/(-elemincre) +1));
+        }
+
+        readptr = startpos + ((LONGLONG)rownum * rowlen) + (elemnum * (incre / elemincre));
+
+        switch (tcode) 
+        {
+            case (TSHORT):
+                ffgi2b(fptr, readptr, ntodo, incre, &array[next], status);
+                if (convert)
+                    fffi2i2(&array[next], ntodo, scale, zero, nulcheck, 
+                           (short) tnull, nulval, &nularray[next], anynul, 
+                           &array[next], status);
+                break;
+            case (TLONGLONG):
+
+                ffgi8b(fptr, readptr, ntodo, incre, (long *) buffer, status);
+                fffi8i2( (LONGLONG *) buffer, ntodo, scale, zero, 
+                           nulcheck, tnull, nulval, &nularray[next], 
+                            anynul, &array[next], status);
+                break;
+            case (TBYTE):
+                ffgi1b(fptr, readptr, ntodo, incre, (unsigned char *) buffer,
+                      status);
+                fffi1i2((unsigned char *) buffer, ntodo, scale, zero, nulcheck, 
+                    (unsigned char) tnull, nulval, &nularray[next], anynul, 
+                    &array[next], status);
+                break;
+            case (TLONG):
+                ffgi4b(fptr, readptr, ntodo, incre, (INT32BIT *) buffer,
+                       status);
+                fffi4i2((INT32BIT *) buffer, ntodo, scale, zero, nulcheck, 
+                       (INT32BIT) tnull, nulval, &nularray[next], anynul, 
+                       &array[next], status);
+                break;
+            case (TFLOAT):
+                ffgr4b(fptr, readptr, ntodo, incre, (float  *) buffer, status);
+                fffr4i2((float  *) buffer, ntodo, scale, zero, nulcheck, 
+                       nulval, &nularray[next], anynul, 
+                       &array[next], status);
+                break;
+            case (TDOUBLE):
+                ffgr8b(fptr, readptr, ntodo, incre, (double *) buffer, status);
+                fffr8i2((double *) buffer, ntodo, scale, zero, nulcheck, 
+                          nulval, &nularray[next], anynul, 
+                          &array[next], status);
+                break;
+            case (TSTRING):
+                ffmbyt(fptr, readptr, REPORT_EOF, status);
+       
+                if (incre == twidth)    /* contiguous bytes */
+                     ffgbyt(fptr, ntodo * twidth, buffer, status);
+                else
+                     ffgbytoff(fptr, twidth, ntodo, incre - twidth, buffer,
+                               status);
+
+                fffstri2((char *) buffer, ntodo, scale, zero, twidth, power,
+                     nulcheck, snull, nulval, &nularray[next], anynul,
+                     &array[next], status);
+                break;
+
+            default:  /*  error trap for invalid column format */
+                sprintf(message, 
+                   "Cannot read numbers from column %d which has format %s",
+                    colnum, tform);
+                ffpmsg(message);
+                if (hdutype == ASCII_TBL)
+                    return(*status = BAD_ATABLE_FORMAT);
+                else
+                    return(*status = BAD_BTABLE_FORMAT);
+
+        } /* End of switch block */
+
+        /*-------------------------*/
+        /*  Check for fatal error  */
+        /*-------------------------*/
+        if (*status > 0)  /* test for error during previous read operation */
+        {
+	  dtemp = (double) next;
+          if (hdutype > 0)
+            sprintf(message,
+            "Error reading elements %.0f thru %.0f from column %d (ffgcli).",
+              dtemp+1, dtemp+ntodo, colnum);
+          else
+            sprintf(message,
+            "Error reading elements %.0f thru %.0f from image (ffgcli).",
+              dtemp+1, dtemp+ntodo);
+
+          ffpmsg(message);
+          return(*status);
+        }
+
+        /*--------------------------------------------*/
+        /*  increment the counters for the next loop  */
+        /*--------------------------------------------*/
+        remain -= ntodo;
+        if (remain)
+        {
+            next += ntodo;
+            elemnum = elemnum + (ntodo * elemincre);
+
+            if (elemnum >= repeat)  /* completed a row; start on later row */
+            {
+                rowincre = elemnum / repeat;
+                rownum += rowincre;
+                elemnum = elemnum - (rowincre * repeat);
+            }
+            else if (elemnum < 0) /* completed a row; start on a previous row */
+            {
+                rowincre = (-elemnum - 1) / repeat + 1;
+                rownum -= rowincre;
+                elemnum = (rowincre * repeat) + elemnum;
+            }
+        }
+    }  /*  End of main while Loop  */
+
+
+    /*--------------------------------*/
+    /*  check for numerical overflow  */
+    /*--------------------------------*/
+    if (*status == OVERFLOW_ERR)
+    {
+        ffpmsg(
+        "Numerical overflow during type conversion while reading FITS data.");
+        *status = NUM_OVERFLOW;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi1i2(unsigned char *input, /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            unsigned char tnull,  /* I - value of FITS TNULLn keyword if any */
+            short nullval,        /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            short *output,        /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (short) input[ii];  /* copy input to output */
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DSHRT_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = SHRT_MIN;
+                }
+                else if (dvalue > DSHRT_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = SHRT_MAX;
+                }
+                else
+                    output[ii] = (short) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                    output[ii] = (short) input[ii];
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DSHRT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = SHRT_MIN;
+                    }
+                    else if (dvalue > DSHRT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = SHRT_MAX;
+                    }
+                    else
+                        output[ii] = (short) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi2i2(short *input,         /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            short tnull,          /* I - value of FITS TNULLn keyword if any */
+            short nullval,        /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            short *output,        /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            memcpy(output, input, ntodo * sizeof(short) );
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DSHRT_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = SHRT_MIN;
+                }
+                else if (dvalue > DSHRT_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = SHRT_MAX;
+                }
+                else
+                    output[ii] = (short) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                    output[ii] = input[ii];
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DSHRT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = SHRT_MIN;
+                    }
+                    else if (dvalue > DSHRT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = SHRT_MAX;
+                    }
+                    else
+                        output[ii] = (short) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi4i2(INT32BIT *input,      /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            INT32BIT tnull,       /* I - value of FITS TNULLn keyword if any */
+            short nullval,        /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            short *output,        /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] < SHRT_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = SHRT_MIN;
+                }
+                else if (input[ii] > SHRT_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = SHRT_MAX;
+                }
+                else
+                    output[ii] = (short) input[ii];
+            }
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DSHRT_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = SHRT_MIN;
+                }
+                else if (dvalue > DSHRT_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = SHRT_MAX;
+                }
+                else
+                    output[ii] = (short) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    if (input[ii] < SHRT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = SHRT_MIN;
+                    }
+                    else if (input[ii] > SHRT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = SHRT_MAX;
+                    }
+                    else
+                        output[ii] = (short) input[ii];
+                }
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DSHRT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = SHRT_MIN;
+                    }
+                    else if (dvalue > DSHRT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = SHRT_MAX;
+                    }
+                    else
+                        output[ii] = (short) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi8i2(LONGLONG *input,      /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            LONGLONG tnull,       /* I - value of FITS TNULLn keyword if any */
+            short nullval,        /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            short *output,        /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] < SHRT_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = SHRT_MIN;
+                }
+                else if (input[ii] > SHRT_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = SHRT_MAX;
+                }
+                else
+                    output[ii] = (short) input[ii];
+            }
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DSHRT_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = SHRT_MIN;
+                }
+                else if (dvalue > DSHRT_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = SHRT_MAX;
+                }
+                else
+                    output[ii] = (short) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    if (input[ii] < SHRT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = SHRT_MIN;
+                    }
+                    else if (input[ii] > SHRT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = SHRT_MAX;
+                    }
+                    else
+                        output[ii] = (short) input[ii];
+                }
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DSHRT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = SHRT_MIN;
+                    }
+                    else if (dvalue > DSHRT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = SHRT_MAX;
+                    }
+                    else
+                        output[ii] = (short) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffr4i2(float *input,         /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            short nullval,        /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            short *output,        /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to NaN.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+    short *sptr, iret;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] < DSHRT_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = SHRT_MIN;
+                }
+                else if (input[ii] > DSHRT_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = SHRT_MAX;
+                }
+                else
+                    output[ii] = (short) input[ii];
+            }
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DSHRT_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = SHRT_MIN;
+                }
+                else if (dvalue > DSHRT_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = SHRT_MAX;
+                }
+                else
+                    output[ii] = (short) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        sptr = (short *) input;
+
+#if BYTESWAPPED && MACHINE != VAXVMS && MACHINE != ALPHAVMS
+        sptr++;       /* point to MSBs */
+#endif
+
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++, sptr += 2)
+            {
+              if (0 != (iret = fnan(*sptr) ) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                  }
+                  else            /* it's an underflow */
+                     output[ii] = 0;
+              }
+              else
+                {
+                    if (input[ii] < DSHRT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = SHRT_MIN;
+                    }
+                    else if (input[ii] > DSHRT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = SHRT_MAX;
+                    }
+                    else
+                        output[ii] = (short) input[ii];
+                }
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++, sptr += 2)
+            {
+              if (0 != (iret = fnan(*sptr) ) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {  
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                  }
+                  else            /* it's an underflow */
+                  {
+                    if (zero < DSHRT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = SHRT_MIN;
+                    }
+                    else if (zero > DSHRT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = SHRT_MAX;
+                    }
+                    else
+                        output[ii] = (short) zero;
+                  }
+              }
+              else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DSHRT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = SHRT_MIN;
+                    }
+                    else if (dvalue > DSHRT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = SHRT_MAX;
+                    }
+                    else
+                        output[ii] = (short) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffr8i2(double *input,        /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            short nullval,        /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            short *output,        /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to NaN.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+    short *sptr, iret;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] < DSHRT_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = SHRT_MIN;
+                }
+                else if (input[ii] > DSHRT_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = SHRT_MAX;
+                }
+                else
+                    output[ii] = (short) input[ii];
+            }
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DSHRT_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = SHRT_MIN;
+                }
+                else if (dvalue > DSHRT_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = SHRT_MAX;
+                }
+                else
+                    output[ii] = (short) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        sptr = (short *) input;
+
+#if BYTESWAPPED && MACHINE != VAXVMS && MACHINE != ALPHAVMS
+        sptr += 3;       /* point to MSBs */
+#endif
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++, sptr += 4)
+            {
+              if (0 != (iret = dnan(*sptr)) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                  }
+                  else            /* it's an underflow */
+                     output[ii] = 0;
+              }
+              else
+                {
+                    if (input[ii] < DSHRT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = SHRT_MIN;
+                    }
+                    else if (input[ii] > DSHRT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = SHRT_MAX;
+                    }
+                    else
+                        output[ii] = (short) input[ii];
+                }
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++, sptr += 4)
+            {
+              if (0 != (iret = dnan(*sptr)) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {  
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                  }
+                  else            /* it's an underflow */
+                  {
+                    if (zero < DSHRT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = SHRT_MIN;
+                    }
+                    else if (zero > DSHRT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = SHRT_MAX;
+                    }
+                    else
+                        output[ii] = (short) zero;
+                  }
+              }
+              else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DSHRT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = SHRT_MIN;
+                    }
+                    else if (dvalue > DSHRT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = SHRT_MAX;
+                    }
+                    else
+                        output[ii] = (short) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffstri2(char *input,         /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            long twidth,          /* I - width of each substring of chars    */
+            double implipower,    /* I - power of 10 of implied decimal      */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            char  *snull,         /* I - value of FITS null string, if any   */
+            short nullval,        /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            short *output,        /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file. Check
+  for null values and do scaling if required. The nullcheck code value
+  determines how any null values in the input array are treated. A null
+  value is an input pixel that is equal to snull.  If nullcheck= 0, then
+  no special checking for nulls is performed.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    int nullen;
+    long ii;
+    double dvalue;
+    char *cstring, message[81];
+    char *cptr, *tpos;
+    char tempstore, chrzero = '0';
+    double val, power;
+    int exponent, sign, esign, decpt;
+
+    nullen = strlen(snull);
+    cptr = input;  /* pointer to start of input string */
+    for (ii = 0; ii < ntodo; ii++)
+    {
+      cstring = cptr;
+      /* temporarily insert a null terminator at end of the string */
+      tpos = cptr + twidth;
+      tempstore = *tpos;
+      *tpos = 0;
+
+      /* check if null value is defined, and if the    */
+      /* column string is identical to the null string */
+      if (snull[0] != ASCII_NULL_UNDEFINED && 
+         !strncmp(snull, cptr, nullen) )
+      {
+        if (nullcheck)  
+        {
+          *anynull = 1;    
+          if (nullcheck == 1)
+            output[ii] = nullval;
+          else
+            nullarray[ii] = 1;
+        }
+        cptr += twidth;
+      }
+      else
+      {
+        /* value is not the null value, so decode it */
+        /* remove any embedded blank characters from the string */
+
+        decpt = 0;
+        sign = 1;
+        val  = 0.;
+        power = 1.;
+        exponent = 0;
+        esign = 1;
+
+        while (*cptr == ' ')               /* skip leading blanks */
+           cptr++;
+
+        if (*cptr == '-' || *cptr == '+')  /* check for leading sign */
+        {
+          if (*cptr == '-')
+             sign = -1;
+
+          cptr++;
+
+          while (*cptr == ' ')         /* skip blanks between sign and value */
+            cptr++;
+        }
+
+        while (*cptr >= '0' && *cptr <= '9')
+        {
+          val = val * 10. + *cptr - chrzero;  /* accumulate the value */
+          cptr++;
+
+          while (*cptr == ' ')         /* skip embedded blanks in the value */
+            cptr++;
+        }
+
+        if (*cptr == '.' || *cptr == ',')       /* check for decimal point */
+        {
+          decpt = 1;       /* set flag to show there was a decimal point */
+          cptr++;
+          while (*cptr == ' ')         /* skip any blanks */
+            cptr++;
+
+          while (*cptr >= '0' && *cptr <= '9')
+          {
+            val = val * 10. + *cptr - chrzero;  /* accumulate the value */
+            power = power * 10.;
+            cptr++;
+
+            while (*cptr == ' ')         /* skip embedded blanks in the value */
+              cptr++;
+          }
+        }
+
+        if (*cptr == 'E' || *cptr == 'D')  /* check for exponent */
+        {
+          cptr++;
+          while (*cptr == ' ')         /* skip blanks */
+              cptr++;
+  
+          if (*cptr == '-' || *cptr == '+')  /* check for exponent sign */
+          {
+            if (*cptr == '-')
+               esign = -1;
+
+            cptr++;
+
+            while (*cptr == ' ')        /* skip blanks between sign and exp */
+              cptr++;
+          }
+
+          while (*cptr >= '0' && *cptr <= '9')
+          {
+            exponent = exponent * 10 + *cptr - chrzero;  /* accumulate exp */
+            cptr++;
+
+            while (*cptr == ' ')         /* skip embedded blanks */
+              cptr++;
+          }
+        }
+
+        if (*cptr  != 0)  /* should end up at the null terminator */
+        {
+          sprintf(message, "Cannot read number from ASCII table");
+          ffpmsg(message);
+          sprintf(message, "Column field = %s.", cstring);
+          ffpmsg(message);
+          /* restore the char that was overwritten by the null */
+          *tpos = tempstore;
+          return(*status = BAD_C2D);
+        }
+
+        if (!decpt)  /* if no explicit decimal, use implied */
+           power = implipower;
+
+        dvalue = (sign * val / power) * pow(10., (double) (esign * exponent));
+
+        dvalue = dvalue * scale + zero;   /* apply the scaling */
+
+        if (dvalue < DSHRT_MIN)
+        {
+            *status = OVERFLOW_ERR;
+            output[ii] = SHRT_MIN;
+        }
+        else if (dvalue > DSHRT_MAX)
+        {
+            *status = OVERFLOW_ERR;
+            output[ii] = SHRT_MAX;
+        }
+        else
+            output[ii] = (short) dvalue;
+      }
+      /* restore the char that was overwritten by the null */
+      *tpos = tempstore;
+    }
+    return(*status);
+}
diff --git a/src/plugins/cfitsio/getcolj.c b/src/plugins/cfitsio/getcolj.c
new file mode 100644
index 0000000..2d90913
--- /dev/null
+++ b/src/plugins/cfitsio/getcolj.c
@@ -0,0 +1,3726 @@
+/*  This file, getcolj.c, contains routines that read data elements from   */
+/*  a FITS image or table, with long data type.                            */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+
+#include <math.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <string.h>
+#include "fitsio2.h"
+
+/*--------------------------------------------------------------------------*/
+int ffgpvj( fitsfile *fptr,   /* I - FITS file pointer                       */
+            long  group,      /* I - group to read (1 = 1st group)           */
+            LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+            LONGLONG  nelem,      /* I - number of values to read                */
+            long  nulval,     /* I - value for undefined pixels              */
+            long  *array,     /* O - array of values that are returned       */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+  Undefined elements will be set equal to NULVAL, unless NULVAL=0
+  in which case no checking for undefined values will be performed.
+  ANYNUL is returned with a value of .true. if any pixels are undefined.
+*/
+{
+    long row;
+    char cdummy;
+    int nullcheck = 1;
+    long nullvalue;
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+         nullvalue = nulval;  /* set local variable */
+
+        fits_read_compressed_pixels(fptr, TLONG, firstelem, nelem,
+            nullcheck, &nullvalue, array, NULL, anynul, status);
+        return(*status);
+    }
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffgclj(fptr, 2, row, firstelem, nelem, 1, 1, nulval,
+               array, &cdummy, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgpfj( fitsfile *fptr,   /* I - FITS file pointer                       */
+            long  group,      /* I - group to read (1 = 1st group)           */
+            LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+            LONGLONG  nelem,      /* I - number of values to read                */
+            long  *array,     /* O - array of values that are returned       */
+            char *nularray,   /* O - array of null pixel flags               */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+  Any undefined pixels in the returned array will be set = 0 and the 
+  corresponding nularray value will be set = 1.
+  ANYNUL is returned with a value of .true. if any pixels are undefined.
+*/
+{
+    long row;
+    int nullcheck = 2;
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        fits_read_compressed_pixels(fptr, TLONG, firstelem, nelem,
+            nullcheck, NULL, array, nularray, anynul, status);
+        return(*status);
+    }
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffgclj(fptr, 2, row, firstelem, nelem, 1, 2, 0L,
+               array, nularray, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffg2dj(fitsfile *fptr,  /* I - FITS file pointer                       */
+           long  group,     /* I - group to read (1 = 1st group)           */
+           long  nulval,    /* set undefined pixels equal to this          */
+           LONGLONG  ncols,     /* I - number of pixels in each row of array   */
+           LONGLONG  naxis1,    /* I - FITS image NAXIS1 value                 */
+           LONGLONG  naxis2,    /* I - FITS image NAXIS2 value                 */
+           long  *array,    /* O - array to be filled and returned         */
+           int  *anynul,    /* O - set to 1 if any values are null; else 0 */
+           int  *status)    /* IO - error status                           */
+/*
+  Read an entire 2-D array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being read).  Any null
+  values in the array will be set equal to the value of nulval, unless
+  nulval = 0 in which case no null checking will be performed.
+*/
+{
+    /* call the 3D reading routine, with the 3rd dimension = 1 */
+
+    ffg3dj(fptr, group, nulval, ncols, naxis2, naxis1, naxis2, 1, array, 
+           anynul, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffg3dj(fitsfile *fptr,  /* I - FITS file pointer                       */
+           long  group,     /* I - group to read (1 = 1st group)           */
+           long  nulval,    /* set undefined pixels equal to this          */
+           LONGLONG  ncols,     /* I - number of pixels in each row of array   */
+           LONGLONG  nrows,     /* I - number of rows in each plane of array   */
+           LONGLONG  naxis1,    /* I - FITS image NAXIS1 value                 */
+           LONGLONG  naxis2,    /* I - FITS image NAXIS2 value                 */
+           LONGLONG  naxis3,    /* I - FITS image NAXIS3 value                 */
+           long  *array,    /* O - array to be filled and returned         */
+           int  *anynul,    /* O - set to 1 if any values are null; else 0 */
+           int  *status)    /* IO - error status                           */
+/*
+  Read an entire 3-D array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being read).  Any null
+  values in the array will be set equal to the value of nulval, unless
+  nulval = 0 in which case no null checking will be performed.
+*/
+{
+    long tablerow, ii, jj;
+    char cdummy;
+    int nullcheck = 1;
+    long inc[] = {1,1,1};
+    LONGLONG fpixel[] = {1,1,1}, nfits, narray;
+    LONGLONG lpixel[3], nullvalue;
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        lpixel[0] = ncols;
+        lpixel[1] = nrows;
+        lpixel[2] = naxis3;
+        nullvalue = nulval;  /* set local variable */
+
+        fits_read_compressed_img(fptr, TLONG, fpixel, lpixel, inc,
+            nullcheck, &nullvalue, array, NULL, anynul, status);
+        return(*status);
+    }
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+    tablerow=maxvalue(1,group);
+
+    if (ncols == naxis1 && nrows == naxis2)  /* arrays have same size? */
+    {
+       /* all the image pixels are contiguous, so read all at once */
+       ffgclj(fptr, 2, tablerow, 1, naxis1 * naxis2 * naxis3, 1, 1, nulval,
+               array, &cdummy, anynul, status);
+       return(*status);
+    }
+
+    if (ncols < naxis1 || nrows < naxis2)
+       return(*status = BAD_DIMEN);
+
+    nfits = 1;   /* next pixel in FITS image to read */
+    narray = 0;  /* next pixel in output array to be filled */
+
+    /* loop over naxis3 planes in the data cube */
+    for (jj = 0; jj < naxis3; jj++)
+    {
+      /* loop over the naxis2 rows in the FITS image, */
+      /* reading naxis1 pixels to each row            */
+
+      for (ii = 0; ii < naxis2; ii++)
+      {
+       if (ffgclj(fptr, 2, tablerow, nfits, naxis1, 1, 1, nulval,
+          &array[narray], &cdummy, anynul, status) > 0)
+          return(*status);
+
+       nfits += naxis1;
+       narray += ncols;
+      }
+      narray += (nrows - naxis2) * ncols;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgsvj(fitsfile *fptr, /* I - FITS file pointer                         */
+           int  colnum,    /* I - number of the column to read (1 = 1st)    */
+           int naxis,      /* I - number of dimensions in the FITS array    */
+           long  *naxes,   /* I - size of each dimension                    */
+           long  *blc,     /* I - 'bottom left corner' of the subsection    */
+           long  *trc,     /* I - 'top right corner' of the subsection      */
+           long  *inc,     /* I - increment to be applied in each dimension */
+           long nulval,    /* I - value to set undefined pixels             */
+           long *array,    /* O - array to be filled and returned           */
+           int  *anynul,   /* O - set to 1 if any values are null; else 0   */
+           int  *status)   /* IO - error status                             */
+/*
+  Read a subsection of data values from an image or a table column.
+  This routine is set up to handle a maximum of nine dimensions.
+*/
+{
+    long ii,i0, i1,i2,i3,i4,i5,i6,i7,i8,row,rstr,rstp,rinc;
+    long str[9],stp[9],incr[9],dir[9];
+    long nelem, nultyp, ninc, numcol;
+    LONGLONG felem, dsize[10], blcll[9], trcll[9];
+    int hdutype, anyf;
+    char ldummy, msg[FLEN_ERRMSG];
+    int nullcheck = 1;
+    long nullvalue;
+
+    if (naxis < 1 || naxis > 9)
+    {
+        sprintf(msg, "NAXIS = %d in call to ffgsvj is out of range", naxis);
+        ffpmsg(msg);
+        return(*status = BAD_DIMEN);
+    }
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        for (ii=0; ii < naxis; ii++) {
+	    blcll[ii] = blc[ii];
+	    trcll[ii] = trc[ii];
+	}
+
+        nullvalue = nulval;  /* set local variable */
+
+        fits_read_compressed_img(fptr, TLONG, blcll, trcll, inc,
+            nullcheck, &nullvalue, array, NULL, anynul, status);
+        return(*status);
+    }
+
+/*
+    if this is a primary array, then the input COLNUM parameter should
+    be interpreted as the row number, and we will alway read the image
+    data from column 2 (any group parameters are in column 1).
+*/
+    if (ffghdt(fptr, &hdutype, status) > 0)
+        return(*status);
+
+    if (hdutype == IMAGE_HDU)
+    {
+        /* this is a primary array, or image extension */
+        if (colnum == 0)
+        {
+            rstr = 1;
+            rstp = 1;
+        }
+        else
+        {
+            rstr = colnum;
+            rstp = colnum;
+        }
+        rinc = 1;
+        numcol = 2;
+    }
+    else
+    {
+        /* this is a table, so the row info is in the (naxis+1) elements */
+        rstr = blc[naxis];
+        rstp = trc[naxis];
+        rinc = inc[naxis];
+        numcol = colnum;
+    }
+
+    nultyp = 1;
+    if (anynul)
+        *anynul = FALSE;
+
+    i0 = 0;
+    for (ii = 0; ii < 9; ii++)
+    {
+        str[ii] = 1;
+        stp[ii] = 1;
+        incr[ii] = 1;
+        dsize[ii] = 1;
+        dir[ii] = 1;
+    }
+
+    for (ii = 0; ii < naxis; ii++)
+    {
+      if (trc[ii] < blc[ii])
+      {
+        if (hdutype == IMAGE_HDU)
+        {
+           dir[ii] = -1;
+        }
+        else
+        {
+          sprintf(msg, "ffgsvj: illegal range specified for axis %ld", ii + 1);
+          ffpmsg(msg);
+          return(*status = BAD_PIX_NUM);
+        }
+      }
+
+      str[ii] = blc[ii];
+      stp[ii] = trc[ii];
+      incr[ii] = inc[ii];
+      dsize[ii + 1] = dsize[ii] * naxes[ii];
+      dsize[ii] = dsize[ii] * dir[ii];
+    }
+    dsize[naxis] = dsize[naxis] * dir[naxis];
+
+    if (naxis == 1 && naxes[0] == 1)
+    {
+      /* This is not a vector column, so read all the rows at once */
+      nelem = (rstp - rstr) / rinc + 1;
+      ninc = rinc;
+      rstp = rstr;
+    }
+    else
+    {
+      /* have to read each row individually, in all dimensions */
+      nelem = (stp[0]*dir[0] - str[0]*dir[0]) / inc[0] + 1;
+      ninc = incr[0] * dir[0];
+    }
+
+    for (row = rstr; row <= rstp; row += rinc)
+    {
+     for (i8 = str[8]*dir[8]; i8 <= stp[8]*dir[8]; i8 += incr[8])
+     {
+      for (i7 = str[7]*dir[7]; i7 <= stp[7]*dir[7]; i7 += incr[7])
+      {
+       for (i6 = str[6]*dir[6]; i6 <= stp[6]*dir[6]; i6 += incr[6])
+       {
+        for (i5 = str[5]*dir[5]; i5 <= stp[5]*dir[5]; i5 += incr[5])
+        {
+         for (i4 = str[4]*dir[4]; i4 <= stp[4]*dir[4]; i4 += incr[4])
+         {
+          for (i3 = str[3]*dir[3]; i3 <= stp[3]*dir[3]; i3 += incr[3])
+          {
+           for (i2 = str[2]*dir[2]; i2 <= stp[2]*dir[2]; i2 += incr[2])
+           {
+            for (i1 = str[1]*dir[1]; i1 <= stp[1]*dir[1]; i1 += incr[1])
+            {
+
+              felem=str[0] + (i1 - dir[1]) * dsize[1] + (i2 - dir[2]) * dsize[2] + 
+                             (i3 - dir[3]) * dsize[3] + (i4 - dir[4]) * dsize[4] +
+                             (i5 - dir[5]) * dsize[5] + (i6 - dir[6]) * dsize[6] +
+                             (i7 - dir[7]) * dsize[7] + (i8 - dir[8]) * dsize[8];
+
+              if ( ffgclj(fptr, numcol, row, felem, nelem, ninc, nultyp,
+                   nulval, &array[i0], &ldummy, &anyf, status) > 0)
+                   return(*status);
+
+              if (anyf && anynul)
+                  *anynul = TRUE;
+
+              i0 += nelem;
+            }
+           }
+          }
+         }
+        }
+       }
+      }
+     }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgsfj(fitsfile *fptr, /* I - FITS file pointer                         */
+           int  colnum,    /* I - number of the column to read (1 = 1st)    */
+           int naxis,      /* I - number of dimensions in the FITS array    */
+           long  *naxes,   /* I - size of each dimension                    */
+           long  *blc,     /* I - 'bottom left corner' of the subsection    */
+           long  *trc,     /* I - 'top right corner' of the subsection      */
+           long  *inc,     /* I - increment to be applied in each dimension */
+           long *array,    /* O - array to be filled and returned           */
+           char *flagval,  /* O - set to 1 if corresponding value is null   */
+           int  *anynul,   /* O - set to 1 if any values are null; else 0   */
+           int  *status)   /* IO - error status                             */
+/*
+  Read a subsection of data values from an image or a table column.
+  This routine is set up to handle a maximum of nine dimensions.
+*/
+{
+    long ii,i0, i1,i2,i3,i4,i5,i6,i7,i8,row,rstr,rstp,rinc;
+    long str[9],stp[9],incr[9],dsize[10];
+    LONGLONG blcll[9], trcll[9];
+    long felem, nelem, nultyp, ninc, numcol;
+    long nulval = 0;
+    int hdutype, anyf;
+    char msg[FLEN_ERRMSG];
+    int nullcheck = 2;
+
+    if (naxis < 1 || naxis > 9)
+    {
+        sprintf(msg, "NAXIS = %d in call to ffgsvj is out of range", naxis);
+        ffpmsg(msg);
+        return(*status = BAD_DIMEN);
+    }
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        for (ii=0; ii < naxis; ii++) {
+	    blcll[ii] = blc[ii];
+	    trcll[ii] = trc[ii];
+	}
+
+        fits_read_compressed_img(fptr, TLONG, blcll, trcll, inc,
+            nullcheck, NULL, array, flagval, anynul, status);
+        return(*status);
+    }
+
+/*
+    if this is a primary array, then the input COLNUM parameter should
+    be interpreted as the row number, and we will alway read the image
+    data from column 2 (any group parameters are in column 1).
+*/
+    if (ffghdt(fptr, &hdutype, status) > 0)
+        return(*status);
+
+    if (hdutype == IMAGE_HDU)
+    {
+        /* this is a primary array, or image extension */
+        if (colnum == 0)
+        {
+            rstr = 1;
+            rstp = 1;
+        }
+        else
+        {
+            rstr = colnum;
+            rstp = colnum;
+        }
+        rinc = 1;
+        numcol = 2;
+    }
+    else
+    {
+        /* this is a table, so the row info is in the (naxis+1) elements */
+        rstr = blc[naxis];
+        rstp = trc[naxis];
+        rinc = inc[naxis];
+        numcol = colnum;
+    }
+
+    nultyp = 2;
+    if (anynul)
+        *anynul = FALSE;
+
+    i0 = 0;
+    for (ii = 0; ii < 9; ii++)
+    {
+        str[ii] = 1;
+        stp[ii] = 1;
+        incr[ii] = 1;
+        dsize[ii] = 1;
+    }
+
+    for (ii = 0; ii < naxis; ii++)
+    {
+      if (trc[ii] < blc[ii])
+      {
+        sprintf(msg, "ffgsvj: illegal range specified for axis %ld", ii + 1);
+        ffpmsg(msg);
+        return(*status = BAD_PIX_NUM);
+      }
+
+      str[ii] = blc[ii];
+      stp[ii] = trc[ii];
+      incr[ii] = inc[ii];
+      dsize[ii + 1] = dsize[ii] * naxes[ii];
+    }
+
+    if (naxis == 1 && naxes[0] == 1)
+    {
+      /* This is not a vector column, so read all the rows at once */
+      nelem = (rstp - rstr) / rinc + 1;
+      ninc = rinc;
+      rstp = rstr;
+    }
+    else
+    {
+      /* have to read each row individually, in all dimensions */
+      nelem = (stp[0] - str[0]) / inc[0] + 1;
+      ninc = incr[0];
+    }
+
+    for (row = rstr; row <= rstp; row += rinc)
+    {
+     for (i8 = str[8]; i8 <= stp[8]; i8 += incr[8])
+     {
+      for (i7 = str[7]; i7 <= stp[7]; i7 += incr[7])
+      {
+       for (i6 = str[6]; i6 <= stp[6]; i6 += incr[6])
+       {
+        for (i5 = str[5]; i5 <= stp[5]; i5 += incr[5])
+        {
+         for (i4 = str[4]; i4 <= stp[4]; i4 += incr[4])
+         {
+          for (i3 = str[3]; i3 <= stp[3]; i3 += incr[3])
+          {
+           for (i2 = str[2]; i2 <= stp[2]; i2 += incr[2])
+           {
+            for (i1 = str[1]; i1 <= stp[1]; i1 += incr[1])
+            {
+              felem=str[0] + (i1 - 1) * dsize[1] + (i2 - 1) * dsize[2] + 
+                             (i3 - 1) * dsize[3] + (i4 - 1) * dsize[4] +
+                             (i5 - 1) * dsize[5] + (i6 - 1) * dsize[6] +
+                             (i7 - 1) * dsize[7] + (i8 - 1) * dsize[8];
+
+              if ( ffgclj(fptr, numcol, row, felem, nelem, ninc, nultyp,
+                   nulval, &array[i0], &flagval[i0], &anyf, status) > 0)
+                   return(*status);
+
+              if (anyf && anynul)
+                  *anynul = TRUE;
+
+              i0 += nelem;
+            }
+           }
+          }
+         }
+        }
+       }
+      }
+     }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffggpj( fitsfile *fptr,   /* I - FITS file pointer                       */
+            long  group,      /* I - group to read (1 = 1st group)           */
+            long  firstelem,  /* I - first vector element to read (1 = 1st)  */
+            long  nelem,      /* I - number of values to read                */
+            long  *array,     /* O - array of values that are returned       */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of group parameters from the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+*/
+{
+    long row;
+    int idummy;
+    char cdummy;
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffgclj(fptr, 1, row, firstelem, nelem, 1, 1, 0L,
+               array, &cdummy, &idummy, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcvj(fitsfile *fptr,   /* I - FITS file pointer                       */
+           int  colnum,      /* I - number of column to read (1 = 1st col)  */
+           LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+           LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+           LONGLONG  nelem,      /* I - number of values to read                */
+           long  nulval,     /* I - value for null pixels                   */
+           long *array,      /* O - array of values that are read           */
+           int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+           int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from a column in the current FITS HDU. Automatic
+  datatype conversion will be performed if the datatype of the column does not
+  match the datatype of the array parameter. The output values will be scaled 
+  by the FITS TSCALn and TZEROn values if these values have been defined.
+  Any undefined pixels will be set equal to the value of 'nulval' unless
+  nulval = 0 in which case no checks for undefined pixels will be made.
+*/
+{
+    char cdummy;
+
+    ffgclj(fptr, colnum, firstrow, firstelem, nelem, 1, 1, nulval,
+           array, &cdummy, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcfj(fitsfile *fptr,   /* I - FITS file pointer                       */
+           int  colnum,      /* I - number of column to read (1 = 1st col)  */
+           LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+           LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+           LONGLONG  nelem,      /* I - number of values to read                */
+           long  *array,     /* O - array of values that are read           */
+           char *nularray,   /* O - array of flags: 1 if null pixel; else 0 */
+           int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+           int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from a column in the current FITS HDU. Automatic
+  datatype conversion will be performed if the datatype of the column does not
+  match the datatype of the array parameter. The output values will be scaled 
+  by the FITS TSCALn and TZEROn values if these values have been defined.
+  Nularray will be set = 1 if the corresponding array pixel is undefined, 
+  otherwise nularray will = 0.
+*/
+{
+    long dummy = 0;
+
+    ffgclj(fptr, colnum, firstrow, firstelem, nelem, 1, 2, dummy,
+           array, nularray, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgclj( fitsfile *fptr,   /* I - FITS file pointer                       */
+            int  colnum,      /* I - number of column to read (1 = 1st col)  */
+            LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+            LONGLONG firstelem,  /* I - first vector element to read (1 = 1st)  */
+            LONGLONG  nelem,      /* I - number of values to read                */
+            long  elemincre,  /* I - pixel increment; e.g., 2 = every other  */
+            int   nultyp,     /* I - null value handling code:               */
+                              /*     1: set undefined pixels = nulval        */
+                              /*     2: set nularray=1 for undefined pixels  */
+            long  nulval,     /* I - value for null pixels if nultyp = 1     */
+            long  *array,     /* O - array of values that are read           */
+            char *nularray,   /* O - array of flags = 1 if nultyp = 2        */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from a column in the current FITS HDU.
+  The column number may refer to a real column in an ASCII or binary table, 
+  or it may refer be a virtual column in a 1 or more grouped FITS primary
+  array or image extension.  FITSIO treats a primary array as a binary table
+  with 2 vector columns: the first column contains the group parameters (often
+  with length = 0) and the second column contains the array of image pixels.
+  Each row of the table represents a group in the case of multigroup FITS
+  images.
+
+  The output array of values will be converted from the datatype of the column 
+  and will be scaled by the FITS TSCALn and TZEROn values if necessary.
+*/
+{
+    double scale, zero, power = 1., dtemp;
+    int tcode, maxelem, hdutype, xcode, decimals;
+    long twidth, incre;
+    long ii, xwidth, ntodo;
+    int convert, nulcheck, readcheck = 0;
+    LONGLONG repeat, startpos, elemnum, readptr, tnull;
+    LONGLONG rowlen, rownum, remain, next, rowincre;
+    char tform[20];
+    char message[81];
+    char snull[20];   /*  the FITS null value if reading from ASCII table  */
+
+    double cbuff[DBUFFSIZE / sizeof(double)]; /* align cbuff on word boundary */
+    void *buffer;
+
+    if (*status > 0 || nelem == 0)  /* inherit input status value if > 0 */
+        return(*status);
+
+    buffer = cbuff;
+
+    if (anynul)
+        *anynul = 0;
+
+    if (nultyp == 2)
+        memset(nularray, 0, (size_t) nelem);   /* initialize nullarray */
+
+    /*---------------------------------------------------*/
+    /*  Check input and get parameters about the column: */
+    /*---------------------------------------------------*/
+    if (elemincre < 0)
+        readcheck = -1;  /* don't do range checking in this case */
+
+    if (ffgcprll(fptr, colnum, firstrow, firstelem, nelem, readcheck, &scale, &zero,
+         tform, &twidth, &tcode, &maxelem, &startpos, &elemnum, &incre,
+         &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0 )
+         return(*status);
+
+    incre *= elemincre;   /* multiply incre to just get every nth pixel */
+
+    if (tcode == TSTRING)    /* setup for ASCII tables */
+    {
+      /* get the number of implied decimal places if no explicit decmal point */
+      ffasfm(tform, &xcode, &xwidth, &decimals, status); 
+      for(ii = 0; ii < decimals; ii++)
+        power *= 10.;
+    }
+    /*------------------------------------------------------------------*/
+    /*  Decide whether to check for null values in the input FITS file: */
+    /*------------------------------------------------------------------*/
+    nulcheck = nultyp; /* by default check for null values in the FITS file */
+
+    if (nultyp == 1 && nulval == 0)
+       nulcheck = 0;    /* calling routine does not want to check for nulls */
+
+    else if (tcode%10 == 1 &&        /* if reading an integer column, and  */ 
+            tnull == NULL_UNDEFINED) /* if a null value is not defined,    */
+            nulcheck = 0;            /* then do not check for null values. */
+
+    else if (tcode == TSHORT && (tnull > SHRT_MAX || tnull < SHRT_MIN) )
+            nulcheck = 0;            /* Impossible null value */
+
+    else if (tcode == TBYTE && (tnull > 255 || tnull < 0) )
+            nulcheck = 0;            /* Impossible null value */
+
+    else if (tcode == TSTRING && snull[0] == ASCII_NULL_UNDEFINED)
+         nulcheck = 0;
+
+    /*----------------------------------------------------------------------*/
+    /*  If FITS column and output data array have same datatype, then we do */
+    /*  not need to use a temporary buffer to store intermediate datatype.  */
+    /*----------------------------------------------------------------------*/
+    convert = 1;
+    if (tcode == TLONG)  /* Special Case:                        */
+    {                             /* no type convertion required, so read */
+        maxelem = (int) nelem;          /* data directly into output buffer.    */
+
+        if (nulcheck == 0 && scale == 1. && zero == 0. && LONGSIZE == 32)
+            convert = 0;  /* no need to scale data or find nulls */
+    }
+
+    /*---------------------------------------------------------------------*/
+    /*  Now read the pixels from the FITS column. If the column does not   */
+    /*  have the same datatype as the output array, then we have to read   */
+    /*  the raw values into a temporary buffer (of limited size).  In      */
+    /*  the case of a vector colum read only 1 vector of values at a time  */
+    /*  then skip to the next row if more values need to be read.          */
+    /*  After reading the raw values, then call the fffXXYY routine to (1) */
+    /*  test for undefined values, (2) convert the datatype if necessary,  */
+    /*  and (3) scale the values by the FITS TSCALn and TZEROn linear      */
+    /*  scaling parameters.                                                */
+    /*---------------------------------------------------------------------*/
+    remain = nelem;           /* remaining number of values to read */
+    next = 0;                 /* next element in array to be read   */
+    rownum = 0;               /* row number, relative to firstrow   */
+
+    while (remain)
+    {
+        /* limit the number of pixels to read at one time to the number that
+           will fit in the buffer or to the number of pixels that remain in
+           the current vector, which ever is smaller.
+        */
+        ntodo = (long) minvalue(remain, maxelem);
+        if (elemincre >= 0)
+        {
+          ntodo = (long) minvalue(ntodo, ((repeat - elemnum - 1)/elemincre +1));
+        }
+        else
+        {
+          ntodo = (long) minvalue(ntodo, (elemnum/(-elemincre) +1));
+        }
+
+        readptr = startpos + ((LONGLONG)rownum * rowlen) + (elemnum * (incre / elemincre));
+
+        switch (tcode) 
+        {
+            case (TLONG):
+                ffgi4b(fptr, readptr, ntodo, incre, (INT32BIT *) &array[next],
+                       status);
+                if (convert)
+                    fffi4i4((INT32BIT *) &array[next], ntodo, scale, zero, 
+                           nulcheck, (INT32BIT) tnull, nulval, &nularray[next], 
+                            anynul, &array[next], status);
+                break;
+            case (TLONGLONG):
+                ffgi8b(fptr, readptr, ntodo, incre, (long *) buffer, status);
+                fffi8i4((LONGLONG *) buffer, ntodo, scale, zero, 
+                           nulcheck, tnull, nulval, &nularray[next], 
+                            anynul, &array[next], status);
+                break;
+            case (TBYTE):
+                ffgi1b(fptr, readptr, ntodo, incre, (unsigned char *) buffer,
+                       status);
+                fffi1i4((unsigned char *) buffer, ntodo, scale, zero, nulcheck, 
+                     (unsigned char) tnull, nulval, &nularray[next], anynul, 
+                     &array[next], status);
+                break;
+            case (TSHORT):
+                ffgi2b(fptr, readptr, ntodo, incre, (short  *) buffer, status);
+                fffi2i4((short  *) buffer, ntodo, scale, zero, nulcheck, 
+                      (short) tnull, nulval, &nularray[next], anynul, 
+                      &array[next], status);
+                break;
+            case (TFLOAT):
+                ffgr4b(fptr, readptr, ntodo, incre, (float  *) buffer, status);
+                fffr4i4((float  *) buffer, ntodo, scale, zero, nulcheck, 
+                       nulval, &nularray[next], anynul, 
+                       &array[next], status);
+                break;
+            case (TDOUBLE):
+                ffgr8b(fptr, readptr, ntodo, incre, (double *) buffer, status);
+                fffr8i4((double *) buffer, ntodo, scale, zero, nulcheck, 
+                          nulval, &nularray[next], anynul, 
+                          &array[next], status);
+                break;
+            case (TSTRING):
+                ffmbyt(fptr, readptr, REPORT_EOF, status);
+       
+                if (incre == twidth)    /* contiguous bytes */
+                     ffgbyt(fptr, ntodo * twidth, buffer, status);
+                else
+                     ffgbytoff(fptr, twidth, ntodo, incre - twidth, buffer,
+                               status);
+
+                fffstri4((char *) buffer, ntodo, scale, zero, twidth, power,
+                     nulcheck, snull, nulval, &nularray[next], anynul,
+                     &array[next], status);
+                break;
+
+            default:  /*  error trap for invalid column format */
+                sprintf(message, 
+                   "Cannot read numbers from column %d which has format %s",
+                    colnum, tform);
+                ffpmsg(message);
+                if (hdutype == ASCII_TBL)
+                    return(*status = BAD_ATABLE_FORMAT);
+                else
+                    return(*status = BAD_BTABLE_FORMAT);
+
+        } /* End of switch block */
+
+        /*-------------------------*/
+        /*  Check for fatal error  */
+        /*-------------------------*/
+        if (*status > 0)  /* test for error during previous read operation */
+        {
+	  dtemp = (double) next;
+          if (hdutype > 0)
+            sprintf(message,
+            "Error reading elements %.0f thru %.0f from column %d (ffgclj).",
+              dtemp+1., dtemp+ntodo, colnum);
+          else
+            sprintf(message,
+            "Error reading elements %.0f thru %.0f from image (ffgclj).",
+              dtemp+1., dtemp+ntodo);
+
+          ffpmsg(message);
+          return(*status);
+        }
+
+        /*--------------------------------------------*/
+        /*  increment the counters for the next loop  */
+        /*--------------------------------------------*/
+        remain -= ntodo;
+        if (remain)
+        {
+            next += ntodo;
+            elemnum = elemnum + (ntodo * elemincre);
+
+            if (elemnum >= repeat)  /* completed a row; start on later row */
+            {
+                rowincre = elemnum / repeat;
+                rownum += rowincre;
+                elemnum = elemnum - (rowincre * repeat);
+            }
+            else if (elemnum < 0)  /* completed a row; start on a previous row */
+            {
+                rowincre = (-elemnum - 1) / repeat + 1;
+                rownum -= rowincre;
+                elemnum = (rowincre * repeat) + elemnum;
+            }
+        }
+    }  /*  End of main while Loop  */
+
+
+    /*--------------------------------*/
+    /*  check for numerical overflow  */
+    /*--------------------------------*/
+    if (*status == OVERFLOW_ERR)
+    {
+        ffpmsg(
+        "Numerical overflow during type conversion while reading FITS data.");
+        *status = NUM_OVERFLOW;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi1i4(unsigned char *input, /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            unsigned char tnull,  /* I - value of FITS TNULLn keyword if any */
+            long nullval,         /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            long *output,         /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (long) input[ii];  /* copy input to output */
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DLONG_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = LONG_MIN;
+                }
+                else if (dvalue > DLONG_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = LONG_MAX;
+                }
+                else
+                    output[ii] = (long) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                    output[ii] = (long) input[ii];
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DLONG_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONG_MIN;
+                    }
+                    else if (dvalue > DLONG_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONG_MAX;
+                    }
+                    else
+                        output[ii] = (long) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi2i4(short *input,         /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            short tnull,          /* I - value of FITS TNULLn keyword if any */
+            long nullval,         /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            long *output,         /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (long) input[ii];   /* copy input to output */
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DLONG_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = LONG_MIN;
+                }
+                else if (dvalue > DLONG_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = LONG_MAX;
+                }
+                else
+                    output[ii] = (long) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                    output[ii] = (long) input[ii];
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DLONG_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONG_MIN;
+                    }
+                    else if (dvalue > DLONG_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONG_MAX;
+                    }
+                    else
+                        output[ii] = (long) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi4i4(INT32BIT *input,      /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            INT32BIT tnull,       /* I - value of FITS TNULLn keyword if any */
+            long nullval,         /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            long *output,         /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+
+  Process the array of data in reverse order, to handle the case where
+  the input data is 4-bytes and the output is  8-bytes and the conversion
+  is being done in place in the same array.
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = ntodo - 1; ii >= 0; ii--)
+                output[ii] = (long) input[ii];   /* copy input to output */
+        }
+        else             /* must scale the data */
+        {
+            for (ii = ntodo - 1; ii >= 0; ii--)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DLONG_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = LONG_MIN;
+                }
+                else if (dvalue > DLONG_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = LONG_MAX;
+                }
+                else
+                    output[ii] = (long) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = ntodo - 1; ii >= 0; ii--)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                    output[ii] = input[ii];
+
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = ntodo - 1; ii >= 0; ii--)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DLONG_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONG_MIN;
+                    }
+                    else if (dvalue > DLONG_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONG_MAX;
+                    }
+                    else
+                        output[ii] = (long) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi8i4(LONGLONG *input,      /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            LONGLONG tnull,       /* I - value of FITS TNULLn keyword if any */
+            long nullval,         /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            long *output,         /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] < LONG_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = LONG_MIN;
+                }
+                else if (input[ii] > LONG_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = LONG_MAX;
+                }
+                else
+                    output[ii] = (long) input[ii];
+            }
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DLONG_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = LONG_MIN;
+                }
+                else if (dvalue > DLONG_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = LONG_MAX;
+                }
+                else
+                    output[ii] = (long) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    if (input[ii] < LONG_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONG_MIN;
+                    }
+                    else if (input[ii] > LONG_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONG_MAX;
+                    }
+                    else
+                        output[ii] = (long) input[ii];
+                }
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DLONG_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONG_MIN;
+                    }
+                    else if (dvalue > DLONG_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONG_MAX;
+                    }
+                    else
+                        output[ii] = (long) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffr4i4(float *input,         /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            long nullval,         /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            long *output,         /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to NaN.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+    short *sptr, iret;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] < DLONG_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = LONG_MIN;
+                }
+                else if (input[ii] > DLONG_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = LONG_MAX;
+                }
+                else
+                    output[ii] = (long) input[ii];
+            }
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DLONG_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = LONG_MIN;
+                }
+                else if (dvalue > DLONG_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = LONG_MAX;
+                }
+                else
+                    output[ii] = (long) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        sptr = (short *) input;
+
+#if BYTESWAPPED && MACHINE != VAXVMS && MACHINE != ALPHAVMS
+        sptr++;       /* point to MSBs */
+#endif
+
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++, sptr += 2)
+            {
+              if (0 != (iret = fnan(*sptr) ) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                  }
+                  else            /* it's an underflow */
+                     output[ii] = 0;
+              }
+              else
+                {
+                    if (input[ii] < DLONG_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONG_MIN;
+                    }
+                    else if (input[ii] > DLONG_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONG_MAX;
+                    }
+                    else
+                        output[ii] = (long) input[ii];
+                }
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++, sptr += 2)
+            {
+              if (0 != (iret = fnan(*sptr) ) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {  
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                  }
+                  else            /* it's an underflow */
+                  {
+                    if (zero < DLONG_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONG_MIN;
+                    }
+                    else if (zero > DLONG_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONG_MAX;
+                    }
+                    else
+                        output[ii] = (long) zero;
+                  }
+              }
+              else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DLONG_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONG_MIN;
+                    }
+                    else if (dvalue > DLONG_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONG_MAX;
+                    }
+                    else
+                        output[ii] = (long) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffr8i4(double *input,        /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            long nullval,         /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            long *output,         /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to NaN.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+    short *sptr, iret;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] < DLONG_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = LONG_MIN;
+                }
+                else if (input[ii] > DLONG_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = LONG_MAX;
+                }
+                else
+                    output[ii] = (long) input[ii];
+            }
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DLONG_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = LONG_MIN;
+                }
+                else if (dvalue > DLONG_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = LONG_MAX;
+                }
+                else
+                    output[ii] = (long) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        sptr = (short *) input;
+
+#if BYTESWAPPED && MACHINE != VAXVMS && MACHINE != ALPHAVMS
+        sptr += 3;       /* point to MSBs */
+#endif
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++, sptr += 4)
+            {
+              if (0 != (iret = dnan(*sptr)) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                  }
+                  else            /* it's an underflow */
+                     output[ii] = 0;
+              }
+              else
+                {
+                    if (input[ii] < DLONG_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONG_MIN;
+                    }
+                    else if (input[ii] > DLONG_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONG_MAX;
+                    }
+                    else
+                        output[ii] = (long) input[ii];
+                }
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++, sptr += 4)
+            {
+              if (0 != (iret = dnan(*sptr)) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {  
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                  }
+                  else            /* it's an underflow */
+                  {
+                    if (zero < DLONG_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONG_MIN;
+                    }
+                    else if (zero > DLONG_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONG_MAX;
+                    }
+                    else
+                        output[ii] = (long) zero;
+                  }
+              }
+              else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DLONG_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONG_MIN;
+                    }
+                    else if (dvalue > DLONG_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONG_MAX;
+                    }
+                    else
+                        output[ii] = (long) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffstri4(char *input,         /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            long twidth,          /* I - width of each substring of chars    */
+            double implipower,    /* I - power of 10 of implied decimal      */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            char  *snull,         /* I - value of FITS null string, if any   */
+            long nullval,         /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            long *output,         /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file. Check
+  for null values and do scaling if required. The nullcheck code value
+  determines how any null values in the input array are treated. A null
+  value is an input pixel that is equal to snull.  If nullcheck= 0, then
+  no special checking for nulls is performed.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    int nullen;
+    long ii;
+    double dvalue;
+    char *cstring, message[81];
+    char *cptr, *tpos;
+    char tempstore, chrzero = '0';
+    double val, power;
+    int exponent, sign, esign, decpt;
+
+    nullen = strlen(snull);
+    cptr = input;  /* pointer to start of input string */
+    for (ii = 0; ii < ntodo; ii++)
+    {
+      cstring = cptr;
+      /* temporarily insert a null terminator at end of the string */
+      tpos = cptr + twidth;
+      tempstore = *tpos;
+      *tpos = 0;
+
+      /* check if null value is defined, and if the    */
+      /* column string is identical to the null string */
+      if (snull[0] != ASCII_NULL_UNDEFINED && 
+         !strncmp(snull, cptr, nullen) )
+      {
+        if (nullcheck)  
+        {
+          *anynull = 1;    
+          if (nullcheck == 1)
+            output[ii] = nullval;
+          else
+            nullarray[ii] = 1;
+        }
+        cptr += twidth;
+      }
+      else
+      {
+        /* value is not the null value, so decode it */
+        /* remove any embedded blank characters from the string */
+
+        decpt = 0;
+        sign = 1;
+        val  = 0.;
+        power = 1.;
+        exponent = 0;
+        esign = 1;
+
+        while (*cptr == ' ')               /* skip leading blanks */
+           cptr++;
+
+        if (*cptr == '-' || *cptr == '+')  /* check for leading sign */
+        {
+          if (*cptr == '-')
+             sign = -1;
+
+          cptr++;
+
+          while (*cptr == ' ')         /* skip blanks between sign and value */
+            cptr++;
+        }
+
+        while (*cptr >= '0' && *cptr <= '9')
+        {
+          val = val * 10. + *cptr - chrzero;  /* accumulate the value */
+          cptr++;
+
+          while (*cptr == ' ')         /* skip embedded blanks in the value */
+            cptr++;
+        }
+
+        if (*cptr == '.' || *cptr == ',')    /* check for decimal point */
+        {
+          decpt = 1;       /* set flag to show there was a decimal point */
+          cptr++;
+          while (*cptr == ' ')         /* skip any blanks */
+            cptr++;
+
+          while (*cptr >= '0' && *cptr <= '9')
+          {
+            val = val * 10. + *cptr - chrzero;  /* accumulate the value */
+            power = power * 10.;
+            cptr++;
+
+            while (*cptr == ' ')         /* skip embedded blanks in the value */
+              cptr++;
+          }
+        }
+
+        if (*cptr == 'E' || *cptr == 'D')  /* check for exponent */
+        {
+          cptr++;
+          while (*cptr == ' ')         /* skip blanks */
+              cptr++;
+  
+          if (*cptr == '-' || *cptr == '+')  /* check for exponent sign */
+          {
+            if (*cptr == '-')
+               esign = -1;
+
+            cptr++;
+
+            while (*cptr == ' ')        /* skip blanks between sign and exp */
+              cptr++;
+          }
+
+          while (*cptr >= '0' && *cptr <= '9')
+          {
+            exponent = exponent * 10 + *cptr - chrzero;  /* accumulate exp */
+            cptr++;
+
+            while (*cptr == ' ')         /* skip embedded blanks */
+              cptr++;
+          }
+        }
+
+        if (*cptr  != 0)  /* should end up at the null terminator */
+        {
+          sprintf(message, "Cannot read number from ASCII table");
+          ffpmsg(message);
+          sprintf(message, "Column field = %s.", cstring);
+          ffpmsg(message);
+          /* restore the char that was overwritten by the null */
+          *tpos = tempstore;
+          return(*status = BAD_C2D);
+        }
+
+        if (!decpt)  /* if no explicit decimal, use implied */
+           power = implipower;
+
+        dvalue = (sign * val / power) * pow(10., (double) (esign * exponent));
+
+        dvalue = dvalue * scale + zero;   /* apply the scaling */
+
+        if (dvalue < DLONG_MIN)
+        {
+            *status = OVERFLOW_ERR;
+            output[ii] = LONG_MIN;
+        }
+        else if (dvalue > DLONG_MAX)
+        {
+            *status = OVERFLOW_ERR;
+            output[ii] = LONG_MAX;
+        }
+        else
+            output[ii] = (long) dvalue;
+      }
+      /* restore the char that was overwritten by the null */
+      *tpos = tempstore;
+    }
+    return(*status);
+}
+
+/* ======================================================================== */
+/*      the following routines support the 'long long' data type            */
+/* ======================================================================== */
+
+/*--------------------------------------------------------------------------*/
+int ffgpvjj(fitsfile *fptr,   /* I - FITS file pointer                       */
+            long  group,      /* I - group to read (1 = 1st group)           */
+            LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+            LONGLONG  nelem,      /* I - number of values to read                */
+            LONGLONG  nulval, /* I - value for undefined pixels              */
+            LONGLONG  *array, /* O - array of values that are returned       */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+  Undefined elements will be set equal to NULVAL, unless NULVAL=0
+  in which case no checking for undefined values will be performed.
+  ANYNUL is returned with a value of .true. if any pixels are undefined.
+*/
+{
+    long row;
+    char cdummy;
+    int nullcheck = 1;
+    LONGLONG nullvalue;
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+         nullvalue = nulval;  /* set local variable */
+
+        fits_read_compressed_pixels(fptr, TLONGLONG, firstelem, nelem,
+            nullcheck, &nullvalue, array, NULL, anynul, status);
+        return(*status);
+    }
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffgcljj(fptr, 2, row, firstelem, nelem, 1, 1, nulval,
+               array, &cdummy, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgpfjj(fitsfile *fptr,   /* I - FITS file pointer                       */
+            long  group,      /* I - group to read (1 = 1st group)           */
+            LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+            LONGLONG  nelem,      /* I - number of values to read                */
+            LONGLONG  *array, /* O - array of values that are returned       */
+            char *nularray,   /* O - array of null pixel flags               */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+  Any undefined pixels in the returned array will be set = 0 and the 
+  corresponding nularray value will be set = 1.
+  ANYNUL is returned with a value of .true. if any pixels are undefined.
+*/
+{
+    long row;
+    int nullcheck = 2;
+    LONGLONG dummy = 0;
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        fits_read_compressed_pixels(fptr, TLONGLONG, firstelem, nelem,
+            nullcheck, NULL, array, nularray, anynul, status);
+        return(*status);
+    }
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffgcljj(fptr, 2, row, firstelem, nelem, 1, 2, dummy,
+               array, nularray, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffg2djj(fitsfile *fptr, /* I - FITS file pointer                       */
+           long  group,     /* I - group to read (1 = 1st group)           */
+           LONGLONG nulval ,/* set undefined pixels equal to this          */
+           LONGLONG  ncols,     /* I - number of pixels in each row of array   */
+           LONGLONG  naxis1,    /* I - FITS image NAXIS1 value                 */
+           LONGLONG  naxis2,    /* I - FITS image NAXIS2 value                 */
+           LONGLONG  *array,/* O - array to be filled and returned         */
+           int  *anynul,    /* O - set to 1 if any values are null; else 0 */
+           int  *status)    /* IO - error status                           */
+/*
+  Read an entire 2-D array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being read).  Any null
+  values in the array will be set equal to the value of nulval, unless
+  nulval = 0 in which case no null checking will be performed.
+*/
+{
+    /* call the 3D reading routine, with the 3rd dimension = 1 */
+
+    ffg3djj(fptr, group, nulval, ncols, naxis2, naxis1, naxis2, 1, array, 
+           anynul, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffg3djj(fitsfile *fptr, /* I - FITS file pointer                       */
+           long  group,     /* I - group to read (1 = 1st group)           */
+           LONGLONG nulval, /* set undefined pixels equal to this          */
+           LONGLONG  ncols,     /* I - number of pixels in each row of array   */
+           LONGLONG  nrows,     /* I - number of rows in each plane of array   */
+           LONGLONG  naxis1,    /* I - FITS image NAXIS1 value                 */
+           LONGLONG  naxis2,    /* I - FITS image NAXIS2 value                 */
+           LONGLONG  naxis3,    /* I - FITS image NAXIS3 value                 */
+           LONGLONG  *array,/* O - array to be filled and returned         */
+           int  *anynul,    /* O - set to 1 if any values are null; else 0 */
+           int  *status)    /* IO - error status                           */
+/*
+  Read an entire 3-D array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being read).  Any null
+  values in the array will be set equal to the value of nulval, unless
+  nulval = 0 in which case no null checking will be performed.
+*/
+{
+    long tablerow, ii, jj;
+    char cdummy;
+    int nullcheck = 1;
+    long inc[] = {1,1,1};
+    LONGLONG fpixel[] = {1,1,1}, nfits, narray;
+    LONGLONG lpixel[3];
+    LONGLONG nullvalue;
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        lpixel[0] = ncols;
+        lpixel[1] = nrows;
+        lpixel[2] = naxis3;
+        nullvalue = nulval;  /* set local variable */
+
+        fits_read_compressed_img(fptr, TLONGLONG, fpixel, lpixel, inc,
+            nullcheck, &nullvalue, array, NULL, anynul, status);
+        return(*status);
+    }
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+    tablerow=maxvalue(1,group);
+
+    if (ncols == naxis1 && nrows == naxis2)  /* arrays have same size? */
+    {
+       /* all the image pixels are contiguous, so read all at once */
+       ffgcljj(fptr, 2, tablerow, 1, naxis1 * naxis2 * naxis3, 1, 1, nulval,
+               array, &cdummy, anynul, status);
+       return(*status);
+    }
+
+    if (ncols < naxis1 || nrows < naxis2)
+       return(*status = BAD_DIMEN);
+
+    nfits = 1;   /* next pixel in FITS image to read */
+    narray = 0;  /* next pixel in output array to be filled */
+
+    /* loop over naxis3 planes in the data cube */
+    for (jj = 0; jj < naxis3; jj++)
+    {
+      /* loop over the naxis2 rows in the FITS image, */
+      /* reading naxis1 pixels to each row            */
+
+      for (ii = 0; ii < naxis2; ii++)
+      {
+       if (ffgcljj(fptr, 2, tablerow, nfits, naxis1, 1, 1, nulval,
+          &array[narray], &cdummy, anynul, status) > 0)
+          return(*status);
+
+       nfits += naxis1;
+       narray += ncols;
+      }
+      narray += (nrows - naxis2) * ncols;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgsvjj(fitsfile *fptr, /* I - FITS file pointer                         */
+           int  colnum,    /* I - number of the column to read (1 = 1st)    */
+           int naxis,      /* I - number of dimensions in the FITS array    */
+           long  *naxes,   /* I - size of each dimension                    */
+           long  *blc,     /* I - 'bottom left corner' of the subsection    */
+           long  *trc,     /* I - 'top right corner' of the subsection      */
+           long  *inc,     /* I - increment to be applied in each dimension */
+           LONGLONG nulval,/* I - value to set undefined pixels             */
+           LONGLONG *array,/* O - array to be filled and returned           */
+           int  *anynul,   /* O - set to 1 if any values are null; else 0   */
+           int  *status)   /* IO - error status                             */
+/*
+  Read a subsection of data values from an image or a table column.
+  This routine is set up to handle a maximum of nine dimensions.
+*/
+{
+    long ii,i0, i1,i2,i3,i4,i5,i6,i7,i8,row,rstr,rstp,rinc;
+    long str[9],stp[9],incr[9],dir[9];
+    long nelem, nultyp, ninc, numcol;
+    LONGLONG felem, dsize[10], blcll[9], trcll[9];
+    int hdutype, anyf;
+    char ldummy, msg[FLEN_ERRMSG];
+    int nullcheck = 1;
+    LONGLONG nullvalue;
+
+    if (naxis < 1 || naxis > 9)
+    {
+        sprintf(msg, "NAXIS = %d in call to ffgsvj is out of range", naxis);
+        ffpmsg(msg);
+        return(*status = BAD_DIMEN);
+    }
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        for (ii=0; ii < naxis; ii++) {
+	    blcll[ii] = blc[ii];
+	    trcll[ii] = trc[ii];
+	}
+
+        nullvalue = nulval;  /* set local variable */
+
+        fits_read_compressed_img(fptr, TLONGLONG, blcll, trcll, inc,
+            nullcheck, &nullvalue, array, NULL, anynul, status);
+        return(*status);
+    }
+
+/*
+    if this is a primary array, then the input COLNUM parameter should
+    be interpreted as the row number, and we will alway read the image
+    data from column 2 (any group parameters are in column 1).
+*/
+    if (ffghdt(fptr, &hdutype, status) > 0)
+        return(*status);
+
+    if (hdutype == IMAGE_HDU)
+    {
+        /* this is a primary array, or image extension */
+        if (colnum == 0)
+        {
+            rstr = 1;
+            rstp = 1;
+        }
+        else
+        {
+            rstr = colnum;
+            rstp = colnum;
+        }
+        rinc = 1;
+        numcol = 2;
+    }
+    else
+    {
+        /* this is a table, so the row info is in the (naxis+1) elements */
+        rstr = blc[naxis];
+        rstp = trc[naxis];
+        rinc = inc[naxis];
+        numcol = colnum;
+    }
+
+    nultyp = 1;
+    if (anynul)
+        *anynul = FALSE;
+
+    i0 = 0;
+    for (ii = 0; ii < 9; ii++)
+    {
+        str[ii] = 1;
+        stp[ii] = 1;
+        incr[ii] = 1;
+        dsize[ii] = 1;
+        dir[ii] = 1;
+    }
+
+    for (ii = 0; ii < naxis; ii++)
+    {
+      if (trc[ii] < blc[ii])
+      {
+        if (hdutype == IMAGE_HDU)
+        {
+           dir[ii] = -1;
+        }
+        else
+        {
+          sprintf(msg, "ffgsvj: illegal range specified for axis %ld", ii + 1);
+          ffpmsg(msg);
+          return(*status = BAD_PIX_NUM);
+        }
+      }
+
+      str[ii] = blc[ii];
+      stp[ii] = trc[ii];
+      incr[ii] = inc[ii];
+      dsize[ii + 1] = dsize[ii] * naxes[ii];
+      dsize[ii] = dsize[ii] * dir[ii];
+    }
+    dsize[naxis] = dsize[naxis] * dir[naxis];
+
+    if (naxis == 1 && naxes[0] == 1)
+    {
+      /* This is not a vector column, so read all the rows at once */
+      nelem = (rstp - rstr) / rinc + 1;
+      ninc = rinc;
+      rstp = rstr;
+    }
+    else
+    {
+      /* have to read each row individually, in all dimensions */
+      nelem = (stp[0]*dir[0] - str[0]*dir[0]) / inc[0] + 1;
+      ninc = incr[0] * dir[0];
+    }
+
+    for (row = rstr; row <= rstp; row += rinc)
+    {
+     for (i8 = str[8]*dir[8]; i8 <= stp[8]*dir[8]; i8 += incr[8])
+     {
+      for (i7 = str[7]*dir[7]; i7 <= stp[7]*dir[7]; i7 += incr[7])
+      {
+       for (i6 = str[6]*dir[6]; i6 <= stp[6]*dir[6]; i6 += incr[6])
+       {
+        for (i5 = str[5]*dir[5]; i5 <= stp[5]*dir[5]; i5 += incr[5])
+        {
+         for (i4 = str[4]*dir[4]; i4 <= stp[4]*dir[4]; i4 += incr[4])
+         {
+          for (i3 = str[3]*dir[3]; i3 <= stp[3]*dir[3]; i3 += incr[3])
+          {
+           for (i2 = str[2]*dir[2]; i2 <= stp[2]*dir[2]; i2 += incr[2])
+           {
+            for (i1 = str[1]*dir[1]; i1 <= stp[1]*dir[1]; i1 += incr[1])
+            {
+
+              felem=str[0] + (i1 - dir[1]) * dsize[1] + (i2 - dir[2]) * dsize[2] + 
+                             (i3 - dir[3]) * dsize[3] + (i4 - dir[4]) * dsize[4] +
+                             (i5 - dir[5]) * dsize[5] + (i6 - dir[6]) * dsize[6] +
+                             (i7 - dir[7]) * dsize[7] + (i8 - dir[8]) * dsize[8];
+
+              if ( ffgcljj(fptr, numcol, row, felem, nelem, ninc, nultyp,
+                   nulval, &array[i0], &ldummy, &anyf, status) > 0)
+                   return(*status);
+
+              if (anyf && anynul)
+                  *anynul = TRUE;
+
+              i0 += nelem;
+            }
+           }
+          }
+         }
+        }
+       }
+      }
+     }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgsfjj(fitsfile *fptr, /* I - FITS file pointer                         */
+           int  colnum,    /* I - number of the column to read (1 = 1st)    */
+           int naxis,      /* I - number of dimensions in the FITS array    */
+           long  *naxes,   /* I - size of each dimension                    */
+           long  *blc,     /* I - 'bottom left corner' of the subsection    */
+           long  *trc,     /* I - 'top right corner' of the subsection      */
+           long  *inc,     /* I - increment to be applied in each dimension */
+           LONGLONG *array,/* O - array to be filled and returned           */
+           char *flagval,  /* O - set to 1 if corresponding value is null   */
+           int  *anynul,   /* O - set to 1 if any values are null; else 0   */
+           int  *status)   /* IO - error status                             */
+/*
+  Read a subsection of data values from an image or a table column.
+  This routine is set up to handle a maximum of nine dimensions.
+*/
+{
+    long ii,i0, i1,i2,i3,i4,i5,i6,i7,i8,row,rstr,rstp,rinc;
+    long str[9],stp[9],incr[9],dsize[10];
+    LONGLONG blcll[9], trcll[9];
+    long felem, nelem, nultyp, ninc, numcol;
+    LONGLONG nulval = 0;
+    int hdutype, anyf;
+    char msg[FLEN_ERRMSG];
+    int nullcheck = 2;
+
+    if (naxis < 1 || naxis > 9)
+    {
+        sprintf(msg, "NAXIS = %d in call to ffgsvj is out of range", naxis);
+        ffpmsg(msg);
+        return(*status = BAD_DIMEN);
+    }
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        for (ii=0; ii < naxis; ii++) {
+	    blcll[ii] = blc[ii];
+	    trcll[ii] = trc[ii];
+	}
+
+         fits_read_compressed_img(fptr, TLONGLONG, blcll, trcll, inc,
+            nullcheck, NULL, array, flagval, anynul, status);
+        return(*status);
+    }
+
+/*
+    if this is a primary array, then the input COLNUM parameter should
+    be interpreted as the row number, and we will alway read the image
+    data from column 2 (any group parameters are in column 1).
+*/
+    if (ffghdt(fptr, &hdutype, status) > 0)
+        return(*status);
+
+    if (hdutype == IMAGE_HDU)
+    {
+        /* this is a primary array, or image extension */
+        if (colnum == 0)
+        {
+            rstr = 1;
+            rstp = 1;
+        }
+        else
+        {
+            rstr = colnum;
+            rstp = colnum;
+        }
+        rinc = 1;
+        numcol = 2;
+    }
+    else
+    {
+        /* this is a table, so the row info is in the (naxis+1) elements */
+        rstr = blc[naxis];
+        rstp = trc[naxis];
+        rinc = inc[naxis];
+        numcol = colnum;
+    }
+
+    nultyp = 2;
+    if (anynul)
+        *anynul = FALSE;
+
+    i0 = 0;
+    for (ii = 0; ii < 9; ii++)
+    {
+        str[ii] = 1;
+        stp[ii] = 1;
+        incr[ii] = 1;
+        dsize[ii] = 1;
+    }
+
+    for (ii = 0; ii < naxis; ii++)
+    {
+      if (trc[ii] < blc[ii])
+      {
+        sprintf(msg, "ffgsvj: illegal range specified for axis %ld", ii + 1);
+        ffpmsg(msg);
+        return(*status = BAD_PIX_NUM);
+      }
+
+      str[ii] = blc[ii];
+      stp[ii] = trc[ii];
+      incr[ii] = inc[ii];
+      dsize[ii + 1] = dsize[ii] * naxes[ii];
+    }
+
+    if (naxis == 1 && naxes[0] == 1)
+    {
+      /* This is not a vector column, so read all the rows at once */
+      nelem = (rstp - rstr) / rinc + 1;
+      ninc = rinc;
+      rstp = rstr;
+    }
+    else
+    {
+      /* have to read each row individually, in all dimensions */
+      nelem = (stp[0] - str[0]) / inc[0] + 1;
+      ninc = incr[0];
+    }
+
+    for (row = rstr; row <= rstp; row += rinc)
+    {
+     for (i8 = str[8]; i8 <= stp[8]; i8 += incr[8])
+     {
+      for (i7 = str[7]; i7 <= stp[7]; i7 += incr[7])
+      {
+       for (i6 = str[6]; i6 <= stp[6]; i6 += incr[6])
+       {
+        for (i5 = str[5]; i5 <= stp[5]; i5 += incr[5])
+        {
+         for (i4 = str[4]; i4 <= stp[4]; i4 += incr[4])
+         {
+          for (i3 = str[3]; i3 <= stp[3]; i3 += incr[3])
+          {
+           for (i2 = str[2]; i2 <= stp[2]; i2 += incr[2])
+           {
+            for (i1 = str[1]; i1 <= stp[1]; i1 += incr[1])
+            {
+              felem=str[0] + (i1 - 1) * dsize[1] + (i2 - 1) * dsize[2] + 
+                             (i3 - 1) * dsize[3] + (i4 - 1) * dsize[4] +
+                             (i5 - 1) * dsize[5] + (i6 - 1) * dsize[6] +
+                             (i7 - 1) * dsize[7] + (i8 - 1) * dsize[8];
+
+              if ( ffgcljj(fptr, numcol, row, felem, nelem, ninc, nultyp,
+                   nulval, &array[i0], &flagval[i0], &anyf, status) > 0)
+                   return(*status);
+
+              if (anyf && anynul)
+                  *anynul = TRUE;
+
+              i0 += nelem;
+            }
+           }
+          }
+         }
+        }
+       }
+      }
+     }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffggpjj(fitsfile *fptr,   /* I - FITS file pointer                       */
+            long  group,      /* I - group to read (1 = 1st group)           */
+            long  firstelem,  /* I - first vector element to read (1 = 1st)  */
+            long  nelem,      /* I - number of values to read                */
+            LONGLONG  *array, /* O - array of values that are returned       */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of group parameters from the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+*/
+{
+    long row;
+    int idummy;
+    char cdummy;
+    LONGLONG dummy = 0;
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffgcljj(fptr, 1, row, firstelem, nelem, 1, 1, dummy,
+               array, &cdummy, &idummy, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcvjj(fitsfile *fptr,  /* I - FITS file pointer                       */
+           int  colnum,      /* I - number of column to read (1 = 1st col)  */
+           LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+           LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+           LONGLONG  nelem,      /* I - number of values to read                */
+           LONGLONG  nulval, /* I - value for null pixels                   */
+           LONGLONG *array,  /* O - array of values that are read           */
+           int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+           int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from a column in the current FITS HDU. Automatic
+  datatype conversion will be performed if the datatype of the column does not
+  match the datatype of the array parameter. The output values will be scaled 
+  by the FITS TSCALn and TZEROn values if these values have been defined.
+  Any undefined pixels will be set equal to the value of 'nulval' unless
+  nulval = 0 in which case no checks for undefined pixels will be made.
+*/
+{
+    char cdummy;
+
+    ffgcljj(fptr, colnum, firstrow, firstelem, nelem, 1, 1, nulval,
+           array, &cdummy, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcfjj(fitsfile *fptr,  /* I - FITS file pointer                       */
+           int  colnum,      /* I - number of column to read (1 = 1st col)  */
+           LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+           LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+           LONGLONG  nelem,      /* I - number of values to read                */
+           LONGLONG  *array, /* O - array of values that are read           */
+           char *nularray,   /* O - array of flags: 1 if null pixel; else 0 */
+           int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+           int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from a column in the current FITS HDU. Automatic
+  datatype conversion will be performed if the datatype of the column does not
+  match the datatype of the array parameter. The output values will be scaled 
+  by the FITS TSCALn and TZEROn values if these values have been defined.
+  Nularray will be set = 1 if the corresponding array pixel is undefined, 
+  otherwise nularray will = 0.
+*/
+{
+    LONGLONG dummy = 0;
+
+    ffgcljj(fptr, colnum, firstrow, firstelem, nelem, 1, 2, dummy,
+           array, nularray, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcljj( fitsfile *fptr,   /* I - FITS file pointer                       */
+            int  colnum,      /* I - number of column to read (1 = 1st col)  */
+            LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+            LONGLONG firstelem,  /* I - first vector element to read (1 = 1st)  */
+            LONGLONG  nelem,      /* I - number of values to read                */
+            long  elemincre,  /* I - pixel increment; e.g., 2 = every other  */
+            int   nultyp,     /* I - null value handling code:               */
+                              /*     1: set undefined pixels = nulval        */
+                              /*     2: set nularray=1 for undefined pixels  */
+            LONGLONG  nulval, /* I - value for null pixels if nultyp = 1     */
+            LONGLONG  *array, /* O - array of values that are read           */
+            char *nularray,   /* O - array of flags = 1 if nultyp = 2        */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from a column in the current FITS HDU.
+  The column number may refer to a real column in an ASCII or binary table, 
+  or it may refer be a virtual column in a 1 or more grouped FITS primary
+  array or image extension.  FITSIO treats a primary array as a binary table
+  with 2 vector columns: the first column contains the group parameters (often
+  with length = 0) and the second column contains the array of image pixels.
+  Each row of the table represents a group in the case of multigroup FITS
+  images.
+
+  The output array of values will be converted from the datatype of the column 
+  and will be scaled by the FITS TSCALn and TZEROn values if necessary.
+*/
+{
+    double scale, zero, power = 1., dtemp;
+    int tcode, maxelem, hdutype, xcode, decimals;
+    long twidth, incre;
+    long ii, xwidth, ntodo;
+    int convert, nulcheck, readcheck = 0;
+    LONGLONG repeat, startpos, elemnum, readptr, tnull;
+    LONGLONG rowlen, rownum, remain, next, rowincre;
+    char tform[20];
+    char message[81];
+    char snull[20];   /*  the FITS null value if reading from ASCII table  */
+
+    double cbuff[DBUFFSIZE / sizeof(double)]; /* align cbuff on word boundary */
+    void *buffer;
+
+    if (*status > 0 || nelem == 0)  /* inherit input status value if > 0 */
+        return(*status);
+
+    buffer = cbuff;
+
+    if (anynul)
+        *anynul = 0;
+
+    if (nultyp == 2)
+        memset(nularray, 0, (size_t) nelem);   /* initialize nullarray */
+
+    /*---------------------------------------------------*/
+    /*  Check input and get parameters about the column: */
+    /*---------------------------------------------------*/
+    if (elemincre < 0)
+        readcheck = -1;  /* don't do range checking in this case */
+
+    if (ffgcprll(fptr, colnum, firstrow, firstelem, nelem, readcheck, &scale, &zero,
+         tform, &twidth, &tcode, &maxelem, &startpos, &elemnum, &incre,
+         &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0 )
+         return(*status);
+
+    incre *= elemincre;   /* multiply incre to just get every nth pixel */
+
+    if (tcode == TSTRING)    /* setup for ASCII tables */
+    {
+      /* get the number of implied decimal places if no explicit decmal point */
+      ffasfm(tform, &xcode, &xwidth, &decimals, status); 
+      for(ii = 0; ii < decimals; ii++)
+        power *= 10.;
+    }
+    /*------------------------------------------------------------------*/
+    /*  Decide whether to check for null values in the input FITS file: */
+    /*------------------------------------------------------------------*/
+    nulcheck = nultyp; /* by default check for null values in the FITS file */
+
+    if (nultyp == 1 && nulval == 0)
+       nulcheck = 0;    /* calling routine does not want to check for nulls */
+
+    else if (tcode%10 == 1 &&        /* if reading an integer column, and  */ 
+            tnull == NULL_UNDEFINED) /* if a null value is not defined,    */
+            nulcheck = 0;            /* then do not check for null values. */
+
+    else if (tcode == TSHORT && (tnull > SHRT_MAX || tnull < SHRT_MIN) )
+            nulcheck = 0;            /* Impossible null value */
+
+    else if (tcode == TBYTE && (tnull > 255 || tnull < 0) )
+            nulcheck = 0;            /* Impossible null value */
+
+    else if (tcode == TSTRING && snull[0] == ASCII_NULL_UNDEFINED)
+         nulcheck = 0;
+
+    /*----------------------------------------------------------------------*/
+    /*  If FITS column and output data array have same datatype, then we do */
+    /*  not need to use a temporary buffer to store intermediate datatype.  */
+    /*----------------------------------------------------------------------*/
+    convert = 1;
+    if (tcode == TLONGLONG)  /* Special Case:                        */
+    {                             /* no type convertion required, so read */
+        maxelem = (int) nelem;          /* data directly into output buffer.    */
+
+        if (nulcheck == 0 && scale == 1. && zero == 0.)
+            convert = 0;  /* no need to scale data or find nulls */
+    }
+
+    /*---------------------------------------------------------------------*/
+    /*  Now read the pixels from the FITS column. If the column does not   */
+    /*  have the same datatype as the output array, then we have to read   */
+    /*  the raw values into a temporary buffer (of limited size).  In      */
+    /*  the case of a vector colum read only 1 vector of values at a time  */
+    /*  then skip to the next row if more values need to be read.          */
+    /*  After reading the raw values, then call the fffXXYY routine to (1) */
+    /*  test for undefined values, (2) convert the datatype if necessary,  */
+    /*  and (3) scale the values by the FITS TSCALn and TZEROn linear      */
+    /*  scaling parameters.                                                */
+    /*---------------------------------------------------------------------*/
+    remain = nelem;           /* remaining number of values to read */
+    next = 0;                 /* next element in array to be read   */
+    rownum = 0;               /* row number, relative to firstrow   */
+
+    while (remain)
+    {
+        /* limit the number of pixels to read at one time to the number that
+           will fit in the buffer or to the number of pixels that remain in
+           the current vector, which ever is smaller.
+        */
+        ntodo = (long) minvalue(remain, maxelem);
+        if (elemincre >= 0)
+        {
+          ntodo = (long) minvalue(ntodo, ((repeat - elemnum - 1)/elemincre +1));
+        }
+        else
+        {
+          ntodo = (long) minvalue(ntodo, (elemnum/(-elemincre) +1));
+        }
+
+        readptr = startpos + ((LONGLONG)rownum * rowlen) + (elemnum * (incre / elemincre));
+
+        switch (tcode) 
+        {
+            case (TLONGLONG):
+                ffgi8b(fptr, readptr, ntodo, incre, (long *) &array[next],
+                       status);
+                if (convert)
+                    fffi8i8((LONGLONG *) &array[next], ntodo, scale, zero, 
+                           nulcheck, tnull, nulval, &nularray[next], 
+                           anynul, &array[next], status);
+                break;
+            case (TLONG):
+                ffgi4b(fptr, readptr, ntodo, incre, (INT32BIT *) buffer,
+                       status);
+                fffi4i8((INT32BIT *) buffer, ntodo, scale, zero, 
+                        nulcheck, (INT32BIT) tnull, nulval, &nularray[next], 
+                        anynul, &array[next], status);
+                break;
+            case (TBYTE):
+                ffgi1b(fptr, readptr, ntodo, incre, (unsigned char *) buffer,
+                       status);
+                fffi1i8((unsigned char *) buffer, ntodo, scale, zero, nulcheck, 
+                     (unsigned char) tnull, nulval, &nularray[next], anynul, 
+                     &array[next], status);
+                break;
+            case (TSHORT):
+                ffgi2b(fptr, readptr, ntodo, incre, (short  *) buffer, status);
+                fffi2i8((short  *) buffer, ntodo, scale, zero, nulcheck, 
+                      (short) tnull, nulval, &nularray[next], anynul, 
+                      &array[next], status);
+                break;
+            case (TFLOAT):
+                ffgr4b(fptr, readptr, ntodo, incre, (float  *) buffer, status);
+                fffr4i8((float  *) buffer, ntodo, scale, zero, nulcheck, 
+                       nulval, &nularray[next], anynul, 
+                       &array[next], status);
+                break;
+            case (TDOUBLE):
+                ffgr8b(fptr, readptr, ntodo, incre, (double *) buffer, status);
+                fffr8i8((double *) buffer, ntodo, scale, zero, nulcheck, 
+                          nulval, &nularray[next], anynul, 
+                          &array[next], status);
+                break;
+            case (TSTRING):
+                ffmbyt(fptr, readptr, REPORT_EOF, status);
+       
+                if (incre == twidth)    /* contiguous bytes */
+                     ffgbyt(fptr, ntodo * twidth, buffer, status);
+                else
+                     ffgbytoff(fptr, twidth, ntodo, incre - twidth, buffer,
+                               status);
+
+                fffstri8((char *) buffer, ntodo, scale, zero, twidth, power,
+                     nulcheck, snull, nulval, &nularray[next], anynul,
+                     &array[next], status);
+                break;
+
+            default:  /*  error trap for invalid column format */
+                sprintf(message, 
+                   "Cannot read numbers from column %d which has format %s",
+                    colnum, tform);
+                ffpmsg(message);
+                if (hdutype == ASCII_TBL)
+                    return(*status = BAD_ATABLE_FORMAT);
+                else
+                    return(*status = BAD_BTABLE_FORMAT);
+
+        } /* End of switch block */
+
+        /*-------------------------*/
+        /*  Check for fatal error  */
+        /*-------------------------*/
+        if (*status > 0)  /* test for error during previous read operation */
+        {
+	  dtemp = (double) next;
+          if (hdutype > 0)
+            sprintf(message,
+            "Error reading elements %.0f thru %.0f from column %d (ffgclj).",
+              dtemp+1., dtemp+ntodo, colnum);
+          else
+            sprintf(message,
+            "Error reading elements %.0f thru %.0f from image (ffgclj).",
+              dtemp+1., dtemp+ntodo);
+
+          ffpmsg(message);
+          return(*status);
+        }
+
+        /*--------------------------------------------*/
+        /*  increment the counters for the next loop  */
+        /*--------------------------------------------*/
+        remain -= ntodo;
+        if (remain)
+        {
+            next += ntodo;
+            elemnum = elemnum + (ntodo * elemincre);
+
+            if (elemnum >= repeat)  /* completed a row; start on later row */
+            {
+                rowincre = elemnum / repeat;
+                rownum += rowincre;
+                elemnum = elemnum - (rowincre * repeat);
+            }
+            else if (elemnum < 0)  /* completed a row; start on a previous row */
+            {
+                rowincre = (-elemnum - 1) / repeat + 1;
+                rownum -= rowincre;
+                elemnum = (rowincre * repeat) + elemnum;
+            }
+        }
+    }  /*  End of main while Loop  */
+
+
+    /*--------------------------------*/
+    /*  check for numerical overflow  */
+    /*--------------------------------*/
+    if (*status == OVERFLOW_ERR)
+    {
+        ffpmsg(
+        "Numerical overflow during type conversion while reading FITS data.");
+        *status = NUM_OVERFLOW;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi1i8(unsigned char *input, /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            unsigned char tnull,  /* I - value of FITS TNULLn keyword if any */
+            LONGLONG nullval,     /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            LONGLONG *output,     /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (LONGLONG) input[ii];  /* copy input to output */
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DLONGLONG_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = LONGLONG_MIN;
+                }
+                else if (dvalue > DLONGLONG_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = LONGLONG_MAX;
+                }
+                else
+                    output[ii] = (LONGLONG) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                    output[ii] = (LONGLONG) input[ii];
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DLONGLONG_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONGLONG_MIN;
+                    }
+                    else if (dvalue > DLONGLONG_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONGLONG_MAX;
+                    }
+                    else
+                        output[ii] = (LONGLONG) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi2i8(short *input,         /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            short tnull,          /* I - value of FITS TNULLn keyword if any */
+            LONGLONG nullval,     /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            LONGLONG *output,     /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (LONGLONG) input[ii];   /* copy input to output */
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DLONGLONG_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = LONGLONG_MIN;
+                }
+                else if (dvalue > DLONGLONG_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = LONGLONG_MAX;
+                }
+                else
+                    output[ii] = (LONGLONG) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                    output[ii] = (LONGLONG) input[ii];
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DLONGLONG_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONGLONG_MIN;
+                    }
+                    else if (dvalue > DLONGLONG_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONGLONG_MAX;
+                    }
+                    else
+                        output[ii] = (LONGLONG) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi4i8(INT32BIT *input,      /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            INT32BIT tnull,       /* I - value of FITS TNULLn keyword if any */
+            LONGLONG nullval,     /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            LONGLONG *output,     /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (LONGLONG) input[ii];   /* copy input to output */
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DLONGLONG_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = LONGLONG_MIN;
+                }
+                else if (dvalue > DLONGLONG_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = LONGLONG_MAX;
+                }
+                else
+                    output[ii] = (LONGLONG) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                    output[ii] = (LONGLONG) input[ii];
+
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DLONGLONG_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONGLONG_MIN;
+                    }
+                    else if (dvalue > DLONGLONG_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONGLONG_MAX;
+                    }
+                    else
+                        output[ii] = (LONGLONG) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi8i8(LONGLONG *input,      /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            LONGLONG tnull,       /* I - value of FITS TNULLn keyword if any */
+            LONGLONG nullval,     /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            LONGLONG *output,     /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+                output[ii] =  input[ii];   /* copy input to output */
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DLONGLONG_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = LONGLONG_MIN;
+                }
+                else if (dvalue > DLONGLONG_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = LONGLONG_MAX;
+                }
+                else
+                    output[ii] = (LONGLONG) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                    output[ii] = input[ii];
+
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DLONGLONG_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONGLONG_MIN;
+                    }
+                    else if (dvalue > DLONGLONG_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONGLONG_MAX;
+                    }
+                    else
+                        output[ii] = (LONGLONG) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffr4i8(float *input,         /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            LONGLONG nullval,     /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            LONGLONG *output,     /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to NaN.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+    short *sptr, iret;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] < DLONGLONG_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = LONGLONG_MIN;
+                }
+                else if (input[ii] > DLONGLONG_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = LONGLONG_MAX;
+                }
+                else
+                    output[ii] = (LONGLONG) input[ii];
+            }
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DLONGLONG_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = LONGLONG_MIN;
+                }
+                else if (dvalue > DLONGLONG_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = LONGLONG_MAX;
+                }
+                else
+                    output[ii] = (LONGLONG) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        sptr = (short *) input;
+
+#if BYTESWAPPED && MACHINE != VAXVMS && MACHINE != ALPHAVMS
+        sptr++;       /* point to MSBs */
+#endif
+
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++, sptr += 2)
+            {
+              if (0 != (iret = fnan(*sptr) ) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                  }
+                  else            /* it's an underflow */
+                     output[ii] = 0;
+              }
+              else
+                {
+                    if (input[ii] < DLONGLONG_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONGLONG_MIN;
+                    }
+                    else if (input[ii] > DLONGLONG_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONGLONG_MAX;
+                    }
+                    else
+                        output[ii] = (LONGLONG) input[ii];
+                }
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++, sptr += 2)
+            {
+              if (0 != (iret = fnan(*sptr) ) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {  
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                  }
+                  else            /* it's an underflow */
+                  {
+                    if (zero < DLONGLONG_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONGLONG_MIN;
+                    }
+                    else if (zero > DLONGLONG_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONGLONG_MAX;
+                    }
+                    else
+                        output[ii] = (LONGLONG) zero;
+                  }
+              }
+              else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DLONGLONG_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONGLONG_MIN;
+                    }
+                    else if (dvalue > DLONGLONG_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONGLONG_MAX;
+                    }
+                    else
+                        output[ii] = (LONGLONG) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffr8i8(double *input,        /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            LONGLONG nullval,     /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            LONGLONG *output,     /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to NaN.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+    short *sptr, iret;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] < DLONGLONG_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = LONGLONG_MIN;
+                }
+                else if (input[ii] > DLONGLONG_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = LONGLONG_MAX;
+                }
+                else
+                    output[ii] = (LONGLONG) input[ii];
+            }
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DLONGLONG_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = LONGLONG_MIN;
+                }
+                else if (dvalue > DLONGLONG_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = LONGLONG_MAX;
+                }
+                else
+                    output[ii] = (LONGLONG) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        sptr = (short *) input;
+
+#if BYTESWAPPED && MACHINE != VAXVMS && MACHINE != ALPHAVMS
+        sptr += 3;       /* point to MSBs */
+#endif
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++, sptr += 4)
+            {
+              if (0 != (iret = dnan(*sptr)) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                  }
+                  else            /* it's an underflow */
+                     output[ii] = 0;
+              }
+              else
+                {
+                    if (input[ii] < DLONGLONG_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONGLONG_MIN;
+                    }
+                    else if (input[ii] > DLONGLONG_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONGLONG_MAX;
+                    }
+                    else
+                        output[ii] = (LONGLONG) input[ii];
+                }
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++, sptr += 4)
+            {
+              if (0 != (iret = dnan(*sptr)) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {  
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                  }
+                  else            /* it's an underflow */
+                  {
+                    if (zero < DLONGLONG_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONGLONG_MIN;
+                    }
+                    else if (zero > DLONGLONG_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONGLONG_MAX;
+                    }
+                    else
+                        output[ii] = (LONGLONG) zero;
+                  }
+              }
+              else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DLONGLONG_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONGLONG_MIN;
+                    }
+                    else if (dvalue > DLONGLONG_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = LONGLONG_MAX;
+                    }
+                    else
+                        output[ii] = (LONGLONG) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffstri8(char *input,         /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            long twidth,          /* I - width of each substring of chars    */
+            double implipower,    /* I - power of 10 of implied decimal      */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            char  *snull,         /* I - value of FITS null string, if any   */
+            LONGLONG nullval,     /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            LONGLONG *output,     /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file. Check
+  for null values and do scaling if required. The nullcheck code value
+  determines how any null values in the input array are treated. A null
+  value is an input pixel that is equal to snull.  If nullcheck= 0, then
+  no special checking for nulls is performed.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    int nullen;
+    long ii;
+    double dvalue;
+    char *cstring, message[81];
+    char *cptr, *tpos;
+    char tempstore, chrzero = '0';
+    double val, power;
+    int exponent, sign, esign, decpt;
+
+    nullen = strlen(snull);
+    cptr = input;  /* pointer to start of input string */
+    for (ii = 0; ii < ntodo; ii++)
+    {
+      cstring = cptr;
+      /* temporarily insert a null terminator at end of the string */
+      tpos = cptr + twidth;
+      tempstore = *tpos;
+      *tpos = 0;
+
+      /* check if null value is defined, and if the    */
+      /* column string is identical to the null string */
+      if (snull[0] != ASCII_NULL_UNDEFINED && 
+         !strncmp(snull, cptr, nullen) )
+      {
+        if (nullcheck)  
+        {
+          *anynull = 1;    
+          if (nullcheck == 1)
+            output[ii] = nullval;
+          else
+            nullarray[ii] = 1;
+        }
+        cptr += twidth;
+      }
+      else
+      {
+        /* value is not the null value, so decode it */
+        /* remove any embedded blank characters from the string */
+
+        decpt = 0;
+        sign = 1;
+        val  = 0.;
+        power = 1.;
+        exponent = 0;
+        esign = 1;
+
+        while (*cptr == ' ')               /* skip leading blanks */
+           cptr++;
+
+        if (*cptr == '-' || *cptr == '+')  /* check for leading sign */
+        {
+          if (*cptr == '-')
+             sign = -1;
+
+          cptr++;
+
+          while (*cptr == ' ')         /* skip blanks between sign and value */
+            cptr++;
+        }
+
+        while (*cptr >= '0' && *cptr <= '9')
+        {
+          val = val * 10. + *cptr - chrzero;  /* accumulate the value */
+          cptr++;
+
+          while (*cptr == ' ')         /* skip embedded blanks in the value */
+            cptr++;
+        }
+
+        if (*cptr == '.' || *cptr == ',')    /* check for decimal point */
+        {
+          decpt = 1;       /* set flag to show there was a decimal point */
+          cptr++;
+          while (*cptr == ' ')         /* skip any blanks */
+            cptr++;
+
+          while (*cptr >= '0' && *cptr <= '9')
+          {
+            val = val * 10. + *cptr - chrzero;  /* accumulate the value */
+            power = power * 10.;
+            cptr++;
+
+            while (*cptr == ' ')         /* skip embedded blanks in the value */
+              cptr++;
+          }
+        }
+
+        if (*cptr == 'E' || *cptr == 'D')  /* check for exponent */
+        {
+          cptr++;
+          while (*cptr == ' ')         /* skip blanks */
+              cptr++;
+  
+          if (*cptr == '-' || *cptr == '+')  /* check for exponent sign */
+          {
+            if (*cptr == '-')
+               esign = -1;
+
+            cptr++;
+
+            while (*cptr == ' ')        /* skip blanks between sign and exp */
+              cptr++;
+          }
+
+          while (*cptr >= '0' && *cptr <= '9')
+          {
+            exponent = exponent * 10 + *cptr - chrzero;  /* accumulate exp */
+            cptr++;
+
+            while (*cptr == ' ')         /* skip embedded blanks */
+              cptr++;
+          }
+        }
+
+        if (*cptr  != 0)  /* should end up at the null terminator */
+        {
+          sprintf(message, "Cannot read number from ASCII table");
+          ffpmsg(message);
+          sprintf(message, "Column field = %s.", cstring);
+          ffpmsg(message);
+          /* restore the char that was overwritten by the null */
+          *tpos = tempstore;
+          return(*status = BAD_C2D);
+        }
+
+        if (!decpt)  /* if no explicit decimal, use implied */
+           power = implipower;
+
+        dvalue = (sign * val / power) * pow(10., (double) (esign * exponent));
+
+        dvalue = dvalue * scale + zero;   /* apply the scaling */
+
+        if (dvalue < DLONGLONG_MIN)
+        {
+            *status = OVERFLOW_ERR;
+            output[ii] = LONGLONG_MIN;
+        }
+        else if (dvalue > DLONGLONG_MAX)
+        {
+            *status = OVERFLOW_ERR;
+            output[ii] = LONGLONG_MAX;
+        }
+        else
+            output[ii] = (LONGLONG) dvalue;
+      }
+      /* restore the char that was overwritten by the null */
+      *tpos = tempstore;
+    }
+    return(*status);
+}
diff --git a/src/plugins/cfitsio/getcolk.c b/src/plugins/cfitsio/getcolk.c
new file mode 100644
index 0000000..4b4447e
--- /dev/null
+++ b/src/plugins/cfitsio/getcolk.c
@@ -0,0 +1,1894 @@
+/*  This file, getcolk.c, contains routines that read data elements from   */
+/*  a FITS image or table, with 'int' data type.                           */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+
+#include <math.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <string.h>
+#include "fitsio2.h"
+
+/*--------------------------------------------------------------------------*/
+int ffgpvk( fitsfile *fptr,   /* I - FITS file pointer                       */
+            long  group,      /* I - group to read (1 = 1st group)           */
+            LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+            LONGLONG  nelem,      /* I - number of values to read                */
+            int   nulval,     /* I - value for undefined pixels              */
+            int   *array,     /* O - array of values that are returned       */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+  Undefined elements will be set equal to NULVAL, unless NULVAL=0
+  in which case no checking for undefined values will be performed.
+  ANYNUL is returned with a value of .true. if any pixels are undefined.
+*/
+{
+    long row;
+    char cdummy;
+    int nullcheck = 1;
+    int nullvalue;
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+         nullvalue = nulval;  /* set local variable */
+
+        fits_read_compressed_pixels(fptr, TINT, firstelem, nelem,
+            nullcheck, &nullvalue, array, NULL, anynul, status);
+        return(*status);
+    }
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffgclk(fptr, 2, row, firstelem, nelem, 1, 1, nulval,
+               array, &cdummy, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgpfk( fitsfile *fptr,   /* I - FITS file pointer                       */
+            long  group,      /* I - group to read (1 = 1st group)           */
+            LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+            LONGLONG  nelem,      /* I - number of values to read                */
+            int   *array,     /* O - array of values that are returned       */
+            char *nularray,   /* O - array of null pixel flags               */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+  Any undefined pixels in the returned array will be set = 0 and the 
+  corresponding nularray value will be set = 1.
+  ANYNUL is returned with a value of .true. if any pixels are undefined.
+*/
+{
+    long row;
+    int nullcheck = 2;
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        fits_read_compressed_pixels(fptr, TINT, firstelem, nelem,
+            nullcheck, NULL, array, nularray, anynul, status);
+        return(*status);
+    }
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffgclk(fptr, 2, row, firstelem, nelem, 1, 2, 0L,
+               array, nularray, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffg2dk(fitsfile *fptr,  /* I - FITS file pointer                       */
+           long  group,     /* I - group to read (1 = 1st group)           */
+           int  nulval,    /* set undefined pixels equal to this          */
+           LONGLONG  ncols,     /* I - number of pixels in each row of array   */
+           LONGLONG  naxis1,    /* I - FITS image NAXIS1 value                 */
+           LONGLONG  naxis2,    /* I - FITS image NAXIS2 value                 */
+           int  *array,    /* O - array to be filled and returned         */
+           int  *anynul,    /* O - set to 1 if any values are null; else 0 */
+           int  *status)    /* IO - error status                           */
+/*
+  Read an entire 2-D array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being read).  Any null
+  values in the array will be set equal to the value of nulval, unless
+  nulval = 0 in which case no null checking will be performed.
+*/
+{
+    /* call the 3D reading routine, with the 3rd dimension = 1 */
+
+    ffg3dk(fptr, group, nulval, ncols, naxis2, naxis1, naxis2, 1, array, 
+           anynul, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffg3dk(fitsfile *fptr,  /* I - FITS file pointer                       */
+           long  group,     /* I - group to read (1 = 1st group)           */
+           int   nulval,    /* set undefined pixels equal to this          */
+           LONGLONG  ncols,     /* I - number of pixels in each row of array   */
+           LONGLONG  nrows,     /* I - number of rows in each plane of array   */
+           LONGLONG  naxis1,    /* I - FITS image NAXIS1 value                 */
+           LONGLONG  naxis2,    /* I - FITS image NAXIS2 value                 */
+           LONGLONG  naxis3,    /* I - FITS image NAXIS3 value                 */
+           int   *array,    /* O - array to be filled and returned         */
+           int  *anynul,    /* O - set to 1 if any values are null; else 0 */
+           int  *status)    /* IO - error status                           */
+/*
+  Read an entire 3-D array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being read).  Any null
+  values in the array will be set equal to the value of nulval, unless
+  nulval = 0 in which case no null checking will be performed.
+*/
+{
+    long tablerow, ii, jj;
+    char cdummy;
+    int nullcheck = 1;
+    long inc[] = {1,1,1};
+    LONGLONG fpixel[] = {1,1,1}, nfits, narray;
+    LONGLONG lpixel[3];
+    int nullvalue;
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        lpixel[0] = ncols;
+        lpixel[1] = nrows;
+        lpixel[2] = naxis3;
+        nullvalue = nulval;  /* set local variable */
+
+        fits_read_compressed_img(fptr, TINT, fpixel, lpixel, inc,
+            nullcheck, &nullvalue, array, NULL, anynul, status);
+        return(*status);
+    }
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+    tablerow=maxvalue(1,group);
+
+    if (ncols == naxis1 && nrows == naxis2)  /* arrays have same size? */
+    {
+       /* all the image pixels are contiguous, so read all at once */
+       ffgclk(fptr, 2, tablerow, 1, naxis1 * naxis2 * naxis3, 1, 1, nulval,
+               array, &cdummy, anynul, status);
+       return(*status);
+    }
+
+    if (ncols < naxis1 || nrows < naxis2)
+       return(*status = BAD_DIMEN);
+
+    nfits = 1;   /* next pixel in FITS image to read */
+    narray = 0;  /* next pixel in output array to be filled */
+
+    /* loop over naxis3 planes in the data cube */
+    for (jj = 0; jj < naxis3; jj++)
+    {
+      /* loop over the naxis2 rows in the FITS image, */
+      /* reading naxis1 pixels to each row            */
+
+      for (ii = 0; ii < naxis2; ii++)
+      {
+       if (ffgclk(fptr, 2, tablerow, nfits, naxis1, 1, 1, nulval,
+          &array[narray], &cdummy, anynul, status) > 0)
+          return(*status);
+
+       nfits += naxis1;
+       narray += ncols;
+      }
+      narray += (nrows - naxis2) * ncols;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgsvk(fitsfile *fptr, /* I - FITS file pointer                         */
+           int  colnum,    /* I - number of the column to read (1 = 1st)    */
+           int naxis,      /* I - number of dimensions in the FITS array    */
+           long  *naxes,   /* I - size of each dimension                    */
+           long  *blc,     /* I - 'bottom left corner' of the subsection    */
+           long  *trc,     /* I - 'top right corner' of the subsection      */
+           long  *inc,     /* I - increment to be applied in each dimension */
+           int  nulval,    /* I - value to set undefined pixels             */
+           int  *array,    /* O - array to be filled and returned           */
+           int  *anynul,   /* O - set to 1 if any values are null; else 0   */
+           int  *status)   /* IO - error status                             */
+/*
+  Read a subsection of data values from an image or a table column.
+  This routine is set up to handle a maximum of nine dimensions.
+*/
+{
+    long ii,i0, i1,i2,i3,i4,i5,i6,i7,i8,row,rstr,rstp,rinc;
+    long str[9],stp[9],incr[9],dir[9];
+    long nelem, nultyp, ninc, numcol;
+    LONGLONG felem, dsize[10], blcll[9], trcll[9];
+    int hdutype, anyf;
+    char ldummy, msg[FLEN_ERRMSG];
+    int nullcheck = 1;
+    int nullvalue;
+
+    if (naxis < 1 || naxis > 9)
+    {
+        sprintf(msg, "NAXIS = %d in call to ffgsvj is out of range", naxis);
+        ffpmsg(msg);
+        return(*status = BAD_DIMEN);
+    }
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        for (ii=0; ii < naxis; ii++) {
+	    blcll[ii] = blc[ii];
+	    trcll[ii] = trc[ii];
+	}
+
+        nullvalue = nulval;  /* set local variable */
+
+        fits_read_compressed_img(fptr, TINT, blcll, trcll, inc,
+            nullcheck, &nullvalue, array, NULL, anynul, status);
+        return(*status);
+    }
+
+/*
+    if this is a primary array, then the input COLNUM parameter should
+    be interpreted as the row number, and we will alway read the image
+    data from column 2 (any group parameters are in column 1).
+*/
+    if (ffghdt(fptr, &hdutype, status) > 0)
+        return(*status);
+
+    if (hdutype == IMAGE_HDU)
+    {
+        /* this is a primary array, or image extension */
+        if (colnum == 0)
+        {
+            rstr = 1;
+            rstp = 1;
+        }
+        else
+        {
+            rstr = colnum;
+            rstp = colnum;
+        }
+        rinc = 1;
+        numcol = 2;
+    }
+    else
+    {
+        /* this is a table, so the row info is in the (naxis+1) elements */
+        rstr = blc[naxis];
+        rstp = trc[naxis];
+        rinc = inc[naxis];
+        numcol = colnum;
+    }
+
+    nultyp = 1;
+    if (anynul)
+        *anynul = FALSE;
+
+    i0 = 0;
+    for (ii = 0; ii < 9; ii++)
+    {
+        str[ii] = 1;
+        stp[ii] = 1;
+        incr[ii] = 1;
+        dsize[ii] = 1;
+        dir[ii] = 1;
+    }
+
+    for (ii = 0; ii < naxis; ii++)
+    {
+      if (trc[ii] < blc[ii])
+      {
+        if (hdutype == IMAGE_HDU)
+        {
+           dir[ii] = -1;
+        }
+        else
+        {
+          sprintf(msg, "ffgsvk: illegal range specified for axis %ld", ii + 1);
+          ffpmsg(msg);
+          return(*status = BAD_PIX_NUM);
+        }
+      }
+
+      str[ii] = blc[ii];
+      stp[ii] = trc[ii];
+      incr[ii] = inc[ii];
+      dsize[ii + 1] = dsize[ii] * naxes[ii];
+      dsize[ii] = dsize[ii] * dir[ii];
+    }
+    dsize[naxis] = dsize[naxis] * dir[naxis];
+
+    if (naxis == 1 && naxes[0] == 1)
+    {
+      /* This is not a vector column, so read all the rows at once */
+      nelem = (rstp - rstr) / rinc + 1;
+      ninc = rinc;
+      rstp = rstr;
+    }
+    else
+    {
+      /* have to read each row individually, in all dimensions */
+      nelem = (stp[0]*dir[0] - str[0]*dir[0]) / inc[0] + 1;
+      ninc = incr[0] * dir[0];
+    }
+
+    for (row = rstr; row <= rstp; row += rinc)
+    {
+     for (i8 = str[8]*dir[8]; i8 <= stp[8]*dir[8]; i8 += incr[8])
+     {
+      for (i7 = str[7]*dir[7]; i7 <= stp[7]*dir[7]; i7 += incr[7])
+      {
+       for (i6 = str[6]*dir[6]; i6 <= stp[6]*dir[6]; i6 += incr[6])
+       {
+        for (i5 = str[5]*dir[5]; i5 <= stp[5]*dir[5]; i5 += incr[5])
+        {
+         for (i4 = str[4]*dir[4]; i4 <= stp[4]*dir[4]; i4 += incr[4])
+         {
+          for (i3 = str[3]*dir[3]; i3 <= stp[3]*dir[3]; i3 += incr[3])
+          {
+           for (i2 = str[2]*dir[2]; i2 <= stp[2]*dir[2]; i2 += incr[2])
+           {
+            for (i1 = str[1]*dir[1]; i1 <= stp[1]*dir[1]; i1 += incr[1])
+            {
+
+              felem=str[0] + (i1 - dir[1]) * dsize[1] + (i2 - dir[2]) * dsize[2] + 
+                             (i3 - dir[3]) * dsize[3] + (i4 - dir[4]) * dsize[4] +
+                             (i5 - dir[5]) * dsize[5] + (i6 - dir[6]) * dsize[6] +
+                             (i7 - dir[7]) * dsize[7] + (i8 - dir[8]) * dsize[8];
+
+              if ( ffgclk(fptr, numcol, row, felem, nelem, ninc, nultyp,
+                   nulval, &array[i0], &ldummy, &anyf, status) > 0)
+                   return(*status);
+
+              if (anyf && anynul)
+                  *anynul = TRUE;
+
+              i0 += nelem;
+            }
+           }
+          }
+         }
+        }
+       }
+      }
+     }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgsfk(fitsfile *fptr, /* I - FITS file pointer                         */
+           int  colnum,    /* I - number of the column to read (1 = 1st)    */
+           int naxis,      /* I - number of dimensions in the FITS array    */
+           long  *naxes,   /* I - size of each dimension                    */
+           long  *blc,     /* I - 'bottom left corner' of the subsection    */
+           long  *trc,     /* I - 'top right corner' of the subsection      */
+           long  *inc,     /* I - increment to be applied in each dimension */
+           int  *array,    /* O - array to be filled and returned           */
+           char *flagval,  /* O - set to 1 if corresponding value is null   */
+           int  *anynul,   /* O - set to 1 if any values are null; else 0   */
+           int  *status)   /* IO - error status                             */
+/*
+  Read a subsection of data values from an image or a table column.
+  This routine is set up to handle a maximum of nine dimensions.
+*/
+{
+    long ii,i0, i1,i2,i3,i4,i5,i6,i7,i8,row,rstr,rstp,rinc;
+    long str[9],stp[9],incr[9],dsize[10];
+    LONGLONG blcll[9], trcll[9];
+    long felem, nelem, nultyp, ninc, numcol;
+    long nulval = 0;
+    int hdutype, anyf;
+    char msg[FLEN_ERRMSG];
+    int nullcheck = 2;
+
+    if (naxis < 1 || naxis > 9)
+    {
+        sprintf(msg, "NAXIS = %d in call to ffgsvj is out of range", naxis);
+        ffpmsg(msg);
+        return(*status = BAD_DIMEN);
+    }
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        for (ii=0; ii < naxis; ii++) {
+	    blcll[ii] = blc[ii];
+	    trcll[ii] = trc[ii];
+	}
+
+        fits_read_compressed_img(fptr, TINT, blcll, trcll, inc,
+            nullcheck, NULL, array, flagval, anynul, status);
+        return(*status);
+    }
+
+/*
+    if this is a primary array, then the input COLNUM parameter should
+    be interpreted as the row number, and we will alway read the image
+    data from column 2 (any group parameters are in column 1).
+*/
+    if (ffghdt(fptr, &hdutype, status) > 0)
+        return(*status);
+
+    if (hdutype == IMAGE_HDU)
+    {
+        /* this is a primary array, or image extension */
+        if (colnum == 0)
+        {
+            rstr = 1;
+            rstp = 1;
+        }
+        else
+        {
+            rstr = colnum;
+            rstp = colnum;
+        }
+        rinc = 1;
+        numcol = 2;
+    }
+    else
+    {
+        /* this is a table, so the row info is in the (naxis+1) elements */
+        rstr = blc[naxis];
+        rstp = trc[naxis];
+        rinc = inc[naxis];
+        numcol = colnum;
+    }
+
+    nultyp = 2;
+    if (anynul)
+        *anynul = FALSE;
+
+    i0 = 0;
+    for (ii = 0; ii < 9; ii++)
+    {
+        str[ii] = 1;
+        stp[ii] = 1;
+        incr[ii] = 1;
+        dsize[ii] = 1;
+    }
+
+    for (ii = 0; ii < naxis; ii++)
+    {
+      if (trc[ii] < blc[ii])
+      {
+        sprintf(msg, "ffgsvj: illegal range specified for axis %ld", ii + 1);
+        ffpmsg(msg);
+        return(*status = BAD_PIX_NUM);
+      }
+
+      str[ii] = blc[ii];
+      stp[ii] = trc[ii];
+      incr[ii] = inc[ii];
+      dsize[ii + 1] = dsize[ii] * naxes[ii];
+    }
+
+    if (naxis == 1 && naxes[0] == 1)
+    {
+      /* This is not a vector column, so read all the rows at once */
+      nelem = (rstp - rstr) / rinc + 1;
+      ninc = rinc;
+      rstp = rstr;
+    }
+    else
+    {
+      /* have to read each row individually, in all dimensions */
+      nelem = (stp[0] - str[0]) / inc[0] + 1;
+      ninc = incr[0];
+    }
+
+    for (row = rstr; row <= rstp; row += rinc)
+    {
+     for (i8 = str[8]; i8 <= stp[8]; i8 += incr[8])
+     {
+      for (i7 = str[7]; i7 <= stp[7]; i7 += incr[7])
+      {
+       for (i6 = str[6]; i6 <= stp[6]; i6 += incr[6])
+       {
+        for (i5 = str[5]; i5 <= stp[5]; i5 += incr[5])
+        {
+         for (i4 = str[4]; i4 <= stp[4]; i4 += incr[4])
+         {
+          for (i3 = str[3]; i3 <= stp[3]; i3 += incr[3])
+          {
+           for (i2 = str[2]; i2 <= stp[2]; i2 += incr[2])
+           {
+            for (i1 = str[1]; i1 <= stp[1]; i1 += incr[1])
+            {
+              felem=str[0] + (i1 - 1) * dsize[1] + (i2 - 1) * dsize[2] + 
+                             (i3 - 1) * dsize[3] + (i4 - 1) * dsize[4] +
+                             (i5 - 1) * dsize[5] + (i6 - 1) * dsize[6] +
+                             (i7 - 1) * dsize[7] + (i8 - 1) * dsize[8];
+
+              if ( ffgclk(fptr, numcol, row, felem, nelem, ninc, nultyp,
+                   nulval, &array[i0], &flagval[i0], &anyf, status) > 0)
+                   return(*status);
+
+              if (anyf && anynul)
+                  *anynul = TRUE;
+
+              i0 += nelem;
+            }
+           }
+          }
+         }
+        }
+       }
+      }
+     }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffggpk( fitsfile *fptr,   /* I - FITS file pointer                       */
+            long  group,      /* I - group to read (1 = 1st group)           */
+            long  firstelem,  /* I - first vector element to read (1 = 1st)  */
+            long  nelem,      /* I - number of values to read                */
+            int  *array,     /* O - array of values that are returned       */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of group parameters from the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+*/
+{
+    long row;
+    int idummy;
+    char cdummy;
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffgclk(fptr, 1, row, firstelem, nelem, 1, 1, 0L,
+               array, &cdummy, &idummy, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcvk(fitsfile *fptr,   /* I - FITS file pointer                       */
+           int  colnum,      /* I - number of column to read (1 = 1st col)  */
+           LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+           LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+           LONGLONG  nelem,      /* I - number of values to read                */
+           int   nulval,     /* I - value for null pixels                   */
+           int  *array,      /* O - array of values that are read           */
+           int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+           int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from a column in the current FITS HDU. Automatic
+  datatype conversion will be performed if the datatype of the column does not
+  match the datatype of the array parameter. The output values will be scaled 
+  by the FITS TSCALn and TZEROn values if these values have been defined.
+  Any undefined pixels will be set equal to the value of 'nulval' unless
+  nulval = 0 in which case no checks for undefined pixels will be made.
+*/
+{
+    char cdummy;
+
+    ffgclk(fptr, colnum, firstrow, firstelem, nelem, 1, 1, nulval,
+           array, &cdummy, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcfk(fitsfile *fptr,   /* I - FITS file pointer                       */
+           int  colnum,      /* I - number of column to read (1 = 1st col)  */
+           LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+           LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+           LONGLONG  nelem,      /* I - number of values to read                */
+           int   *array,     /* O - array of values that are read           */
+           char *nularray,   /* O - array of flags: 1 if null pixel; else 0 */
+           int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+           int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from a column in the current FITS HDU. Automatic
+  datatype conversion will be performed if the datatype of the column does not
+  match the datatype of the array parameter. The output values will be scaled 
+  by the FITS TSCALn and TZEROn values if these values have been defined.
+  Nularray will be set = 1 if the corresponding array pixel is undefined, 
+  otherwise nularray will = 0.
+*/
+{
+    int dummy = 0;
+
+    ffgclk(fptr, colnum, firstrow, firstelem, nelem, 1, 2, dummy,
+           array, nularray, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgclk( fitsfile *fptr,   /* I - FITS file pointer                       */
+            int  colnum,      /* I - number of column to read (1 = 1st col)  */
+            LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+            LONGLONG firstelem,  /* I - first vector element to read (1 = 1st)  */
+            LONGLONG  nelem,      /* I - number of values to read                */
+            long  elemincre,  /* I - pixel increment; e.g., 2 = every other  */
+            int   nultyp,     /* I - null value handling code:               */
+                              /*     1: set undefined pixels = nulval        */
+                              /*     2: set nularray=1 for undefined pixels  */
+            int   nulval,     /* I - value for null pixels if nultyp = 1     */
+            int  *array,      /* O - array of values that are read           */
+            char *nularray,   /* O - array of flags = 1 if nultyp = 2        */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from a column in the current FITS HDU.
+  The column number may refer to a real column in an ASCII or binary table, 
+  or it may refer be a virtual column in a 1 or more grouped FITS primary
+  array or image extension.  FITSIO treats a primary array as a binary table
+  with 2 vector columns: the first column contains the group parameters (often
+  with length = 0) and the second column contains the array of image pixels.
+  Each row of the table represents a group in the case of multigroup FITS
+  images.
+
+  The output array of values will be converted from the datatype of the column 
+  and will be scaled by the FITS TSCALn and TZEROn values if necessary.
+*/
+{
+    double scale, zero, power, dtemp;
+    int tcode, maxelem, hdutype, xcode, decimals;
+    long twidth, incre;
+    long ii, xwidth, ntodo;
+    int convert, nulcheck, readcheck = 0;
+    LONGLONG repeat, startpos, elemnum, readptr, tnull;
+    LONGLONG rowlen, rownum, remain, next, rowincre;
+    char tform[20];
+    char message[81];
+    char snull[20];   /*  the FITS null value if reading from ASCII table  */
+
+    double cbuff[DBUFFSIZE / sizeof(double)]; /* align cbuff on word boundary */
+    void *buffer;
+
+    if (*status > 0 || nelem == 0)  /* inherit input status value if > 0 */
+        return(*status);
+
+    /* call the 'short' or 'long' version of this routine, if possible */
+    if (sizeof(int) == sizeof(short))
+        ffgcli(fptr, colnum, firstrow, firstelem, nelem, elemincre, nultyp,
+              (short) nulval, (short *) array, nularray, anynul, status);
+    else if (sizeof(int) == sizeof(long))
+        ffgclj(fptr, colnum, firstrow, firstelem, nelem, elemincre, nultyp,
+              (long) nulval, (long *) array, nularray, anynul, status);
+    else
+    {
+    /*
+      This is a special case: sizeof(int) is not equal to sizeof(short) or
+      sizeof(long).  This occurs on Alpha OSF systems where short = 2 bytes,
+      int = 4 bytes, and long = 8 bytes.
+    */
+
+    buffer = cbuff;
+    power = 1.;
+
+    if (anynul)
+        *anynul = 0;
+
+    if (nultyp == 2)
+        memset(nularray, 0, (size_t) nelem);   /* initialize nullarray */
+
+    /*---------------------------------------------------*/
+    /*  Check input and get parameters about the column: */
+    /*---------------------------------------------------*/
+    if (elemincre < 0)
+        readcheck = -1;  /* don't do range checking in this case */
+
+    if ( ffgcprll( fptr, colnum, firstrow, firstelem, nelem, readcheck, &scale, &zero,
+         tform, &twidth, &tcode, &maxelem, &startpos, &elemnum, &incre,
+         &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0 )
+         return(*status);
+
+    incre *= elemincre;   /* multiply incre to just get every nth pixel */
+
+    if (tcode == TSTRING)    /* setup for ASCII tables */
+    {
+      /* get the number of implied decimal places if no explicit decmal point */
+      ffasfm(tform, &xcode, &xwidth, &decimals, status); 
+      for(ii = 0; ii < decimals; ii++)
+        power *= 10.;
+    }
+    /*------------------------------------------------------------------*/
+    /*  Decide whether to check for null values in the input FITS file: */
+    /*------------------------------------------------------------------*/
+    nulcheck = nultyp; /* by default check for null values in the FITS file */
+
+    if (nultyp == 1 && nulval == 0)
+       nulcheck = 0;    /* calling routine does not want to check for nulls */
+
+    else if (tcode%10 == 1 &&        /* if reading an integer column, and  */ 
+            tnull == NULL_UNDEFINED) /* if a null value is not defined,    */
+            nulcheck = 0;            /* then do not check for null values. */
+
+    else if (tcode == TSHORT && (tnull > SHRT_MAX || tnull < SHRT_MIN) )
+            nulcheck = 0;            /* Impossible null value */
+
+    else if (tcode == TBYTE && (tnull > 255 || tnull < 0) )
+            nulcheck = 0;            /* Impossible null value */
+
+    else if (tcode == TSTRING && snull[0] == ASCII_NULL_UNDEFINED)
+         nulcheck = 0;
+
+    /*----------------------------------------------------------------------*/
+    /*  If FITS column and output data array have same datatype, then we do */
+    /*  not need to use a temporary buffer to store intermediate datatype.  */
+    /*----------------------------------------------------------------------*/
+    convert = 1;
+    if (tcode == TLONG)           /* Special Case:                        */
+    {                             /* no type convertion required, so read */
+        maxelem = (int) nelem;          /* data directly into output buffer.    */
+
+        if (nulcheck == 0 && scale == 1. && zero == 0.)
+            convert = 0;  /* no need to scale data or find nulls */
+    }
+
+    /*---------------------------------------------------------------------*/
+    /*  Now read the pixels from the FITS column. If the column does not   */
+    /*  have the same datatype as the output array, then we have to read   */
+    /*  the raw values into a temporary buffer (of limited size).  In      */
+    /*  the case of a vector colum read only 1 vector of values at a time  */
+    /*  then skip to the next row if more values need to be read.          */
+    /*  After reading the raw values, then call the fffXXYY routine to (1) */
+    /*  test for undefined values, (2) convert the datatype if necessary,  */
+    /*  and (3) scale the values by the FITS TSCALn and TZEROn linear      */
+    /*  scaling parameters.                                                */
+    /*---------------------------------------------------------------------*/
+    remain = nelem;           /* remaining number of values to read */
+    next = 0;                 /* next element in array to be read   */
+    rownum = 0;               /* row number, relative to firstrow   */
+
+    while (remain)
+    {
+        /* limit the number of pixels to read at one time to the number that
+           will fit in the buffer or to the number of pixels that remain in
+           the current vector, which ever is smaller.
+        */
+        ntodo = (long) minvalue(remain, maxelem);
+        if (elemincre >= 0)
+        {
+          ntodo = (long) minvalue(ntodo, ((repeat - elemnum - 1)/elemincre +1));
+        }
+        else
+        {
+          ntodo = (long) minvalue(ntodo, (elemnum/(-elemincre) +1));
+        }
+
+        readptr = startpos + ((LONGLONG)rownum * rowlen) + (elemnum * (incre / elemincre));
+
+        switch (tcode) 
+        {
+            case (TLONG):
+                ffgi4b(fptr, readptr, ntodo, incre, (INT32BIT *) &array[next],
+                       status);
+                if (convert)
+                    fffi4int((INT32BIT *) &array[next], ntodo, scale, zero, 
+                             nulcheck, (INT32BIT) tnull, nulval,
+                             &nularray[next], anynul, &array[next], status);
+                break;
+            case (TLONGLONG):
+
+                ffgi8b(fptr, readptr, ntodo, incre, (long *) buffer, status);
+                fffi8int( (LONGLONG *) buffer, ntodo, scale, zero, 
+                           nulcheck, tnull, nulval, &nularray[next], 
+                            anynul, &array[next], status);
+                break;
+            case (TBYTE):
+                ffgi1b(fptr, readptr, ntodo, incre, (unsigned char *) buffer,
+                       status);
+                fffi1int((unsigned char *) buffer, ntodo, scale, zero, nulcheck,
+                     (unsigned char) tnull, nulval, &nularray[next], anynul, 
+                     &array[next], status);
+                break;
+            case (TSHORT):
+                ffgi2b(fptr, readptr, ntodo, incre, (short  *) buffer, status);
+                fffi2int((short  *) buffer, ntodo, scale, zero, nulcheck, 
+                      (short) tnull, nulval, &nularray[next], anynul, 
+                      &array[next], status);
+                break;
+            case (TFLOAT):
+                ffgr4b(fptr, readptr, ntodo, incre, (float  *) buffer, status);
+                fffr4int((float  *) buffer, ntodo, scale, zero, nulcheck, 
+                       nulval, &nularray[next], anynul, 
+                       &array[next], status);
+                break;
+            case (TDOUBLE):
+                ffgr8b(fptr, readptr, ntodo, incre, (double *) buffer, status);
+                fffr8int((double *) buffer, ntodo, scale, zero, nulcheck, 
+                          nulval, &nularray[next], anynul, 
+                          &array[next], status);
+                break;
+            case (TSTRING):
+                ffmbyt(fptr, readptr, REPORT_EOF, status);
+       
+                if (incre == twidth)    /* contiguous bytes */
+                     ffgbyt(fptr, ntodo * twidth, buffer, status);
+                else
+                     ffgbytoff(fptr, twidth, ntodo, incre - twidth, buffer,
+                               status);
+
+                fffstrint((char *) buffer, ntodo, scale, zero, twidth, power,
+                     nulcheck, snull, nulval, &nularray[next], anynul,
+                     &array[next], status);
+                break;
+
+            default:  /*  error trap for invalid column format */
+                sprintf(message, 
+                   "Cannot read numbers from column %d which has format %s",
+                    colnum, tform);
+                ffpmsg(message);
+                if (hdutype == ASCII_TBL)
+                    return(*status = BAD_ATABLE_FORMAT);
+                else
+                    return(*status = BAD_BTABLE_FORMAT);
+
+        } /* End of switch block */
+
+        /*-------------------------*/
+        /*  Check for fatal error  */
+        /*-------------------------*/
+        if (*status > 0)  /* test for error during previous read operation */
+        {
+	  dtemp = (double) next;
+          if (hdutype > 0)
+            sprintf(message,
+            "Error reading elements %.0f thru %.0f from column %d (ffgclk).",
+              dtemp+1., dtemp+ntodo, colnum);
+          else
+            sprintf(message,
+            "Error reading elements %.0f thru %.0f from image (ffgclk).",
+              dtemp+1., dtemp+ntodo);
+
+          ffpmsg(message);
+          return(*status);
+        }
+
+        /*--------------------------------------------*/
+        /*  increment the counters for the next loop  */
+        /*--------------------------------------------*/
+        remain -= ntodo;
+        if (remain)
+        {
+            next += ntodo;
+            elemnum = elemnum + (ntodo * elemincre);
+
+            if (elemnum >= repeat)  /* completed a row; start on later row */
+            {
+                rowincre = elemnum / repeat;
+                rownum += rowincre;
+                elemnum = elemnum - (rowincre * repeat);
+            }
+            else if (elemnum < 0)  /* completed a row; start on a previous row */
+            {
+                rowincre = (-elemnum - 1) / repeat + 1;
+                rownum -= rowincre;
+                elemnum = (rowincre * repeat) + elemnum;
+            }
+        }
+    }  /*  End of main while Loop  */
+
+
+    /*--------------------------------*/
+    /*  check for numerical overflow  */
+    /*--------------------------------*/
+    if (*status == OVERFLOW_ERR)
+    {
+        ffpmsg(
+        "Numerical overflow during type conversion while reading FITS data.");
+        *status = NUM_OVERFLOW;
+    }
+
+    }  /* end of DEC Alpha special case */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi1int(unsigned char *input,/* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            unsigned char tnull,  /* I - value of FITS TNULLn keyword if any */
+            int  nullval,         /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            int  *output,         /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (int) input[ii];  /* copy input to output */
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DINT_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = INT_MIN;
+                }
+                else if (dvalue > DINT_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = INT_MAX;
+                }
+                else
+                    output[ii] = (int) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                    output[ii] = (int) input[ii];
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DINT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = INT_MIN;
+                    }
+                    else if (dvalue > DINT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = INT_MAX;
+                    }
+                    else
+                        output[ii] = (int) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi2int(short *input,        /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            short tnull,          /* I - value of FITS TNULLn keyword if any */
+            int  nullval,         /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            int  *output,         /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (int) input[ii];   /* copy input to output */
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DINT_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = INT_MIN;
+                }
+                else if (dvalue > DINT_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = INT_MAX;
+                }
+                else
+                    output[ii] = (int) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                    output[ii] = (int) input[ii];
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DINT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = INT_MIN;
+                    }
+                    else if (dvalue > DINT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = INT_MAX;
+                    }
+                    else
+                        output[ii] = (int) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi4int(INT32BIT *input,     /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            INT32BIT tnull,       /* I - value of FITS TNULLn keyword if any */
+            int  nullval,         /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            int  *output,         /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (int) input[ii];   /* copy input to output */
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DINT_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = INT_MIN;
+                }
+                else if (dvalue > DINT_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = INT_MAX;
+                }
+                else
+                    output[ii] = (int) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                    output[ii] = (int) input[ii];
+
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DINT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = INT_MIN;
+                    }
+                    else if (dvalue > DINT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = INT_MAX;
+                    }
+                    else
+                        output[ii] = (int) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi8int(LONGLONG *input,     /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            LONGLONG tnull,       /* I - value of FITS TNULLn keyword if any */
+            int  nullval,         /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            int  *output,         /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] < INT_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = INT_MIN;
+                }
+                else if (input[ii] > INT_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = INT_MAX;
+                }
+                else
+                    output[ii] = (int) input[ii];
+            }
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DINT_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = INT_MIN;
+                }
+                else if (dvalue > DINT_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = INT_MAX;
+                }
+                else
+                    output[ii] = (int) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    if (input[ii] < INT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = INT_MIN;
+                    }
+                    else if (input[ii] > INT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = INT_MAX;
+                    }
+                    else
+                        output[ii] = (int) input[ii];
+                }
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DINT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = INT_MIN;
+                    }
+                    else if (dvalue > DINT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = INT_MAX;
+                    }
+                    else
+                        output[ii] = (int) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffr4int(float *input,        /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            int  nullval,         /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            int  *output,         /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to NaN.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+    short *sptr, iret;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] < DINT_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = INT_MIN;
+                }
+                else if (input[ii] > DINT_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = INT_MAX;
+                }
+                else
+                    output[ii] = (int) input[ii];
+            }
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DINT_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = INT_MIN;
+                }
+                else if (dvalue > DINT_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = INT_MAX;
+                }
+                else
+                    output[ii] = (int) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        sptr = (short *) input;
+
+#if BYTESWAPPED && MACHINE != VAXVMS && MACHINE != ALPHAVMS
+        sptr++;       /* point to MSBs */
+#endif
+
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++, sptr += 2)
+            {
+              if (0 != (iret = fnan(*sptr) ) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                  }
+                  else            /* it's an underflow */
+                     output[ii] = 0;
+              }
+              else
+                {
+                    if (input[ii] < DINT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = INT_MIN;
+                    }
+                    else if (input[ii] > DINT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = INT_MAX;
+                    }
+                    else
+                        output[ii] = (int) input[ii];
+                }
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++, sptr += 2)
+            {
+              if (0 != (iret = fnan(*sptr) ) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {  
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                  }
+                  else            /* it's an underflow */
+                  { 
+                    if (zero < DINT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = INT_MIN;
+                    }
+                    else if (zero > DINT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = INT_MAX;
+                    }
+                    else
+                      output[ii] = (int) zero;
+                  }
+              }
+              else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DINT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = INT_MIN;
+                    }
+                    else if (dvalue > DINT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = INT_MAX;
+                    }
+                    else
+                        output[ii] = (int) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffr8int(double *input,       /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            int  nullval,         /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            int  *output,         /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to NaN.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+    short *sptr, iret;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] < DINT_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = INT_MIN;
+                }
+                else if (input[ii] > DINT_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = INT_MAX;
+                }
+                else
+                    output[ii] = (int) input[ii];
+            }
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DINT_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = INT_MIN;
+                }
+                else if (dvalue > DINT_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = INT_MAX;
+                }
+                else
+                    output[ii] = (int) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        sptr = (short *) input;
+
+#if BYTESWAPPED && MACHINE != VAXVMS && MACHINE != ALPHAVMS
+        sptr += 3;       /* point to MSBs */
+#endif
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++, sptr += 4)
+            {
+              if (0 != (iret = dnan(*sptr)) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                  }
+                  else            /* it's an underflow */
+                     output[ii] = 0;
+              }
+              else
+                {
+                    if (input[ii] < DINT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = INT_MIN;
+                    }
+                    else if (input[ii] > DINT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = INT_MAX;
+                    }
+                    else
+                        output[ii] = (int) input[ii];
+                }
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++, sptr += 4)
+            {
+              if (0 != (iret = dnan(*sptr)) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {  
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                  }
+                  else            /* it's an underflow */
+                  { 
+                    if (zero < DINT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = INT_MIN;
+                    }
+                    else if (zero > DINT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = INT_MAX;
+                    }
+                    else
+                      output[ii] = (int) zero;
+                  }
+              }
+              else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DINT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = INT_MIN;
+                    }
+                    else if (dvalue > DINT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = INT_MAX;
+                    }
+                    else
+                        output[ii] = (int) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffstrint(char *input,        /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            long twidth,          /* I - width of each substring of chars    */
+            double implipower,    /* I - power of 10 of implied decimal      */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            char  *snull,         /* I - value of FITS null string, if any   */
+            int nullval,          /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            int *output,          /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file. Check
+  for null values and do scaling if required. The nullcheck code value
+  determines how any null values in the input array are treated. A null
+  value is an input pixel that is equal to snull.  If nullcheck= 0, then
+  no special checking for nulls is performed.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    int nullen;
+    long ii;
+    double dvalue;
+    char *cstring, message[81];
+    char *cptr, *tpos;
+    char tempstore, chrzero = '0';
+    double val, power;
+    int exponent, sign, esign, decpt;
+
+    nullen = strlen(snull);
+    cptr = input;  /* pointer to start of input string */
+    for (ii = 0; ii < ntodo; ii++)
+    {
+      cstring = cptr;
+      /* temporarily insert a null terminator at end of the string */
+      tpos = cptr + twidth;
+      tempstore = *tpos;
+      *tpos = 0;
+
+      /* check if null value is defined, and if the    */
+      /* column string is identical to the null string */
+      if (snull[0] != ASCII_NULL_UNDEFINED && 
+         !strncmp(snull, cptr, nullen) )
+      {
+        if (nullcheck)  
+        {
+          *anynull = 1;    
+          if (nullcheck == 1)
+            output[ii] = nullval;
+          else
+            nullarray[ii] = 1;
+        }
+        cptr += twidth;
+      }
+      else
+      {
+        /* value is not the null value, so decode it */
+        /* remove any embedded blank characters from the string */
+
+        decpt = 0;
+        sign = 1;
+        val  = 0.;
+        power = 1.;
+        exponent = 0;
+        esign = 1;
+
+        while (*cptr == ' ')               /* skip leading blanks */
+           cptr++;
+
+        if (*cptr == '-' || *cptr == '+')  /* check for leading sign */
+        {
+          if (*cptr == '-')
+             sign = -1;
+
+          cptr++;
+
+          while (*cptr == ' ')         /* skip blanks between sign and value */
+            cptr++;
+        }
+
+        while (*cptr >= '0' && *cptr <= '9')
+        {
+          val = val * 10. + *cptr - chrzero;  /* accumulate the value */
+          cptr++;
+
+          while (*cptr == ' ')         /* skip embedded blanks in the value */
+            cptr++;
+        }
+
+        if (*cptr == '.' || *cptr == ',')       /* check for decimal point */
+        {
+          decpt = 1;       /* set flag to show there was a decimal point */
+          cptr++;
+          while (*cptr == ' ')         /* skip any blanks */
+            cptr++;
+
+          while (*cptr >= '0' && *cptr <= '9')
+          {
+            val = val * 10. + *cptr - chrzero;  /* accumulate the value */
+            power = power * 10.;
+            cptr++;
+
+            while (*cptr == ' ')         /* skip embedded blanks in the value */
+              cptr++;
+          }
+        }
+
+        if (*cptr == 'E' || *cptr == 'D')  /* check for exponent */
+        {
+          cptr++;
+          while (*cptr == ' ')         /* skip blanks */
+              cptr++;
+  
+          if (*cptr == '-' || *cptr == '+')  /* check for exponent sign */
+          {
+            if (*cptr == '-')
+               esign = -1;
+
+            cptr++;
+
+            while (*cptr == ' ')        /* skip blanks between sign and exp */
+              cptr++;
+          }
+
+          while (*cptr >= '0' && *cptr <= '9')
+          {
+            exponent = exponent * 10 + *cptr - chrzero;  /* accumulate exp */
+            cptr++;
+
+            while (*cptr == ' ')         /* skip embedded blanks */
+              cptr++;
+          }
+        }
+
+        if (*cptr  != 0)  /* should end up at the null terminator */
+        {
+          sprintf(message, "Cannot read number from ASCII table");
+          ffpmsg(message);
+          sprintf(message, "Column field = %s.", cstring);
+          ffpmsg(message);
+          /* restore the char that was overwritten by the null */
+          *tpos = tempstore;
+          return(*status = BAD_C2D);
+        }
+
+        if (!decpt)  /* if no explicit decimal, use implied */
+           power = implipower;
+
+        dvalue = (sign * val / power) * pow(10., (double) (esign * exponent));
+
+        dvalue = dvalue * scale + zero;   /* apply the scaling */
+
+        if (dvalue < DINT_MIN)
+        {
+            *status = OVERFLOW_ERR;
+            output[ii] = INT_MIN;
+        }
+        else if (dvalue > DINT_MAX)
+        {
+            *status = OVERFLOW_ERR;
+            output[ii] = INT_MAX;
+        }
+        else
+            output[ii] = (long) dvalue;
+      }
+      /* restore the char that was overwritten by the null */
+      *tpos = tempstore;
+    }
+    return(*status);
+}
diff --git a/src/plugins/cfitsio/getcoll.c b/src/plugins/cfitsio/getcoll.c
new file mode 100644
index 0000000..427247d
--- /dev/null
+++ b/src/plugins/cfitsio/getcoll.c
@@ -0,0 +1,614 @@
+/*  This file, getcoll.c, contains routines that read data elements from   */
+/*  a FITS image or table, with logical datatype.                          */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+
+#include <stdlib.h>
+#include <string.h>
+#include "fitsio2.h"
+/*--------------------------------------------------------------------------*/
+int ffgcvl( fitsfile *fptr,   /* I - FITS file pointer                       */
+            int  colnum,      /* I - number of column to read (1 = 1st col)  */
+            LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+            LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+            LONGLONG  nelem,      /* I - number of values to read                */
+            char  nulval,     /* I - value for null pixels                   */
+            char *array,      /* O - array of values                         */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of logical values from a column in the current FITS HDU.
+  Any undefined pixels will be set equal to the value of 'nulval' unless
+  nulval = 0 in which case no checks for undefined pixels will be made.
+*/
+{
+    char cdummy;
+
+    ffgcll( fptr, colnum, firstrow, firstelem, nelem, 1, nulval, array,
+            &cdummy, anynul, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcl(  fitsfile *fptr,   /* I - FITS file pointer                       */
+            int  colnum,      /* I - number of column to read (1 = 1st col)  */
+            LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+            LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+            LONGLONG  nelem,      /* I - number of values to read                */
+            char *array,      /* O - array of values                         */
+            int  *status)     /* IO - error status                           */
+/*
+  !!!! THIS ROUTINE IS DEPRECATED AND SHOULD NOT BE USED !!!!!!
+                  !!!! USE ffgcvl INSTEAD  !!!!!!
+  Read an array of logical values from a column in the current FITS HDU.
+  No checking for null values will be performed.
+*/
+{
+    char nulval = 0;
+    int anynul;
+
+    ffgcvl( fptr, colnum, firstrow, firstelem, nelem, nulval, array,
+            &anynul, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcfl( fitsfile *fptr,   /* I - FITS file pointer                       */
+            int  colnum,      /* I - number of column to read (1 = 1st col)  */
+            LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+            LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+            LONGLONG  nelem,      /* I - number of values to read                */
+            char *array,      /* O - array of values                         */
+            char *nularray,   /* O - array of flags = 1 if nultyp = 2        */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of logical values from a column in the current FITS HDU.
+*/
+{
+    char nulval = 0;
+
+    ffgcll( fptr, colnum, firstrow, firstelem, nelem, 2, nulval, array,
+            nularray, anynul, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcll( fitsfile *fptr,   /* I - FITS file pointer                       */
+            int  colnum,      /* I - number of column to read (1 = 1st col)  */
+            LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+            LONGLONG  firstelem, /* I - first vector element to read (1 = 1st)  */
+            LONGLONG  nelem,      /* I - number of values to read                */
+            int   nultyp,     /* I - null value handling code:               */
+                              /*     1: set undefined pixels = nulval        */
+                              /*     2: set nularray=1 for undefined pixels  */
+            char nulval,      /* I - value for null pixels if nultyp = 1     */
+            char *array,      /* O - array of values                         */
+            char *nularray,   /* O - array of flags = 1 if nultyp = 2        */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of logical values from a column in the current FITS HDU.
+*/
+{
+    double dtemp;
+    int tcode, maxelem, hdutype, ii, nulcheck;
+    long twidth, incre;
+    long ntodo;
+    LONGLONG repeat, startpos, elemnum, readptr, tnull, rowlen, rownum, remain, next;
+    double scale, zero;
+    char tform[20];
+    char message[FLEN_ERRMSG];
+    char snull[20];   /*  the FITS null value  */
+    unsigned char buffer[DBUFFSIZE], *buffptr;
+
+    if (*status > 0 || nelem == 0)  /* inherit input status value if > 0 */
+        return(*status);
+
+    if (anynul)
+       *anynul = 0;
+
+    if (nultyp == 2)      
+       memset(nularray, 0, (size_t) nelem);   /* initialize nullarray */
+
+    /*---------------------------------------------------*/
+    /*  Check input and get parameters about the column: */
+    /*---------------------------------------------------*/
+    if (ffgcprll( fptr, colnum, firstrow, firstelem, nelem, 0, &scale, &zero,
+        tform, &twidth, &tcode, &maxelem, &startpos,  &elemnum, &incre,
+        &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0)
+        return(*status);
+
+    if (tcode != TLOGICAL)   
+        return(*status = NOT_LOGICAL_COL);
+ 
+    /*------------------------------------------------------------------*/
+    /*  Decide whether to check for null values in the input FITS file: */
+    /*------------------------------------------------------------------*/
+    nulcheck = nultyp; /* by default, check for null values in the FITS file */
+
+    if (nultyp == 1 && nulval == 0)
+       nulcheck = 0;    /* calling routine does not want to check for nulls */
+
+    /*---------------------------------------------------------------------*/
+    /*  Now read the logical values from the FITS column.                  */
+    /*---------------------------------------------------------------------*/
+
+    remain = nelem;           /* remaining number of values to read */
+    next = 0;                 /* next element in array to be read   */
+    rownum = 0;               /* row number, relative to firstrow   */
+    ntodo = (long) remain;           /* max number of elements to read at one time */
+
+    while (ntodo)
+    {
+      /*
+         limit the number of pixels to read at one time to the number that
+         remain in the current vector.    
+      */
+      ntodo = (long) minvalue(ntodo, maxelem);      
+      ntodo = (long) minvalue(ntodo, (repeat - elemnum));
+
+      readptr = startpos + (rowlen * rownum) + (elemnum * incre);
+
+      ffgi1b(fptr, readptr, ntodo, incre, buffer, status);
+
+      /* convert from T or F to 1 or 0 */
+      buffptr = buffer;
+      for (ii = 0; ii < ntodo; ii++, next++, buffptr++)
+      {
+        if (*buffptr == 'T')
+          array[next] = 1;
+        else if (*buffptr =='F') 
+          array[next] = 0;
+        else if (*buffptr == 0)
+        {
+          array[next] = nulval;  /* set null values to input nulval */
+          if (anynul)
+              *anynul = 1;
+
+          if (nulcheck == 2)
+          {
+            nularray[next] = 1;  /* set null flags */
+          }
+        }
+        else  /* some other illegal character; return the char value */
+        {
+          array[next] = (char) *buffptr;
+        }
+      }
+
+      if (*status > 0)  /* test for error during previous read operation */
+      {
+	dtemp = (double) next;
+        sprintf(message,
+          "Error reading elements %.0f thruough %.0f of logical array (ffgcl).",
+           dtemp+1., dtemp + ntodo);
+        ffpmsg(message);
+        return(*status);
+      }
+
+      /*--------------------------------------------*/
+      /*  increment the counters for the next loop  */
+      /*--------------------------------------------*/
+      remain -= ntodo;
+      if (remain)
+      {
+        elemnum += ntodo;
+
+        if (elemnum == repeat)  /* completed a row; start on later row */
+          {
+            elemnum = 0;
+            rownum++;
+          }
+      }
+      ntodo = (long) remain;  /* this is the maximum number to do in next loop */
+
+    }  /*  End of main while Loop  */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcx(  fitsfile *fptr,  /* I - FITS file pointer                       */
+            int   colnum,    /* I - number of column to write (1 = 1st col) */
+            LONGLONG  frow,      /* I - first row to write (1 = 1st row)        */
+            LONGLONG  fbit,      /* I - first bit to write (1 = 1st)            */
+            LONGLONG  nbit,      /* I - number of bits to write                 */
+            char *larray,    /* O - array of logicals corresponding to bits */
+            int  *status)    /* IO - error status                           */
+/*
+  read an array of logical values from a specified bit or byte
+  column of the binary table.    larray is set = TRUE, if the corresponding
+  bit = 1, otherwise it is set to FALSE.
+  The binary table column being read from must have datatype 'B' or 'X'. 
+*/
+{
+    LONGLONG bstart;
+    long offset, ndone, ii, repeat, bitloc, fbyte;
+    LONGLONG  rstart, estart;
+    int tcode, descrp;
+    unsigned char cbuff;
+    static unsigned char onbit[8] = {128,  64,  32,  16,   8,   4,   2,   1};
+    tcolumn *colptr;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    /*  check input parameters */
+    if (nbit < 1)
+        return(*status);
+    else if (frow < 1)
+        return(*status = BAD_ROW_NUM);
+    else if (fbit < 1)
+        return(*status = BAD_ELEM_NUM);
+
+    /* position to the correct HDU */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    /* rescan header if data structure is undefined */
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+        if ( ffrdef(fptr, status) > 0)               
+            return(*status);
+
+    fbyte = (long) ((fbit + 7) / 8);
+    bitloc = (long) (fbit - 1 - ((fbit - 1) / 8 * 8));
+    ndone = 0;
+    rstart = frow - 1;
+    estart = fbyte - 1;
+
+    colptr  = (fptr->Fptr)->tableptr;   /* point to first column */
+    colptr += (colnum - 1);     /* offset to correct column structure */
+
+    tcode = colptr->tdatatype;
+
+    if (abs(tcode) > TBYTE)
+        return(*status = NOT_LOGICAL_COL); /* not correct datatype column */
+
+    if (tcode > 0)
+    {
+        descrp = FALSE;  /* not a variable length descriptor column */
+        /* N.B: REPEAT is the number of bytes, not number of bits */
+        repeat = (long) colptr->trepeat;
+
+        if (tcode == TBIT)
+            repeat = (repeat + 7) / 8;  /* convert from bits to bytes */
+
+        if (fbyte > repeat)
+            return(*status = BAD_ELEM_NUM);
+
+        /* calc the i/o pointer location to start of sequence of pixels */
+        bstart = (fptr->Fptr)->datastart + ((fptr->Fptr)->rowlength * rstart) +
+               colptr->tbcol + estart;
+    }
+    else
+    {
+        descrp = TRUE;  /* a variable length descriptor column */
+        /* only bit arrays (tform = 'X') are supported for variable */
+        /* length arrays.  REPEAT is the number of BITS in the array. */
+
+        ffgdes(fptr, colnum, frow, &repeat, &offset, status);
+
+        if (tcode == -TBIT)
+            repeat = (repeat + 7) / 8;
+
+        if ((fbit + nbit + 6) / 8 > repeat)
+            return(*status = BAD_ELEM_NUM);
+
+        /* calc the i/o pointer location to start of sequence of pixels */
+        bstart = (fptr->Fptr)->datastart + offset + (fptr->Fptr)->heapstart + estart;
+    }
+
+    /* move the i/o pointer to the start of the pixel sequence */
+    if (ffmbyt(fptr, bstart, REPORT_EOF, status) > 0)
+        return(*status);
+
+    /* read the next byte */
+    while (1)
+    {
+      if (ffgbyt(fptr, 1, &cbuff, status) > 0)
+        return(*status);
+
+      for (ii = bitloc; (ii < 8) && (ndone < nbit); ii++, ndone++)
+      {
+        if(cbuff & onbit[ii])       /* test if bit is set */
+          larray[ndone] = TRUE;
+        else
+          larray[ndone] = FALSE;
+      }
+
+      if (ndone == nbit)   /* finished all the bits */
+        return(*status);
+
+      /* not done, so get the next byte */
+      if (!descrp)
+      {
+        estart++;
+        if (estart == repeat) 
+        {
+          /* move the i/o pointer to the next row of pixels */
+          estart = 0;
+          rstart = rstart + 1;
+          bstart = (fptr->Fptr)->datastart + ((fptr->Fptr)->rowlength * rstart) +
+               colptr->tbcol;
+
+          ffmbyt(fptr, bstart, REPORT_EOF, status);
+        }
+      }
+      bitloc = 0;
+    }
+}
+/*--------------------------------------------------------------------------*/
+int ffgcxui(fitsfile *fptr,   /* I - FITS file pointer                       */
+            int  colnum,      /* I - number of column to read (1 = 1st col)  */
+            LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+            LONGLONG  nrows,      /* I - no. of rows to read                     */
+            long  input_first_bit, /* I - first bit to read (1 = 1st)        */
+            int   input_nbits,     /* I - number of bits to read (<= 32)     */
+            unsigned short *array, /* O - array of integer values            */
+            int  *status)     /* IO - error status                           */
+/*
+  Read a consecutive string of bits from an 'X' or 'B' column and
+  interprete them as an unsigned integer.  The number of bits must be
+  less than or equal to 16 or the total number of bits in the column, 
+  which ever is less.
+*/
+{
+    int ii, firstbit, nbits, bytenum, startbit, numbits, endbit;
+    int firstbyte, lastbyte, nbytes, rshift, lshift;
+    unsigned short colbyte[5];
+    tcolumn *colptr;
+    char message[81];
+
+    if (*status > 0 || nrows == 0)
+        return(*status);
+
+    /*  check input parameters */
+    if (firstrow < 1)
+    {
+          sprintf(message, "Starting row number is less than 1: %ld (ffgcxui)",
+                (long) firstrow);
+          ffpmsg(message);
+          return(*status = BAD_ROW_NUM);
+    }
+    else if (input_first_bit < 1)
+    {
+          sprintf(message, "Starting bit number is less than 1: %ld (ffgcxui)",
+                input_first_bit);
+          ffpmsg(message);
+          return(*status = BAD_ELEM_NUM);
+    }
+    else if (input_nbits > 16)
+    {
+          sprintf(message, "Number of bits to read is > 16: %d (ffgcxui)",
+                input_nbits);
+          ffpmsg(message);
+          return(*status = BAD_ELEM_NUM);
+    }
+
+    /* position to the correct HDU */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    /* rescan header if data structure is undefined */
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+        if ( ffrdef(fptr, status) > 0)               
+            return(*status);
+
+    if ((fptr->Fptr)->hdutype != BINARY_TBL)
+    {
+        ffpmsg("This is not a binary table extension (ffgcxui)");
+        return(*status = NOT_BTABLE);
+    }
+
+    if (colnum > (fptr->Fptr)->tfield)
+    {
+      sprintf(message, "Specified column number is out of range: %d (ffgcxui)",
+                colnum);
+        ffpmsg(message);
+        sprintf(message, "  There are %d columns in this table.",
+                (fptr->Fptr)->tfield );
+        ffpmsg(message);
+
+        return(*status = BAD_COL_NUM);
+    }       
+
+    colptr  = (fptr->Fptr)->tableptr;   /* point to first column */
+    colptr += (colnum - 1);     /* offset to correct column structure */
+
+    if (abs(colptr->tdatatype) > TBYTE)
+    {
+        ffpmsg("Can only read bits from X or B type columns. (ffgcxui)");
+        return(*status = NOT_LOGICAL_COL); /* not correct datatype column */
+    }
+
+    firstbyte = (input_first_bit - 1              ) / 8 + 1;
+    lastbyte  = (input_first_bit + input_nbits - 2) / 8 + 1;
+    nbytes = lastbyte - firstbyte + 1;
+
+    if (colptr->tdatatype == TBIT && 
+        input_first_bit + input_nbits - 1 > (long) colptr->trepeat)
+    {
+        ffpmsg("Too many bits. Tried to read past width of column (ffgcxui)");
+        return(*status = BAD_ELEM_NUM);
+    }
+    else if (colptr->tdatatype == TBYTE && lastbyte > (long) colptr->trepeat)
+    {
+        ffpmsg("Too many bits. Tried to read past width of column (ffgcxui)");
+        return(*status = BAD_ELEM_NUM);
+    }
+
+    for (ii = 0; ii < nrows; ii++)
+    {
+        /* read the relevant bytes from the row */
+        if (ffgcvui(fptr, colnum, firstrow+ii, firstbyte, nbytes, 0, 
+               colbyte, NULL, status) > 0)
+        {
+             ffpmsg("Error reading bytes from column (ffgcxui)");
+             return(*status);
+        }
+
+        firstbit = (input_first_bit - 1) % 8; /* modulus operator */
+        nbits = input_nbits;
+
+        array[ii] = 0;
+
+        /* select and shift the bits from each byte into the output word */
+        while(nbits)
+        {
+            bytenum = firstbit / 8;
+
+            startbit = firstbit % 8;  
+            numbits = minvalue(nbits, 8 - startbit);
+            endbit = startbit + numbits - 1;
+
+            rshift = 7 - endbit;
+            lshift = nbits - numbits;
+
+            array[ii] = ((colbyte[bytenum] >> rshift) << lshift) | array[ii];
+
+            nbits -= numbits;
+            firstbit += numbits;
+        }
+    }
+
+    return(*status);
+}
+
+/*--------------------------------------------------------------------------*/
+int ffgcxuk(fitsfile *fptr,   /* I - FITS file pointer                       */
+            int  colnum,      /* I - number of column to read (1 = 1st col)  */
+            LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+            LONGLONG  nrows,      /* I - no. of rows to read                     */
+            long  input_first_bit, /* I - first bit to read (1 = 1st)        */
+            int   input_nbits,     /* I - number of bits to read (<= 32)     */
+            unsigned int *array,   /* O - array of integer values            */
+            int  *status)     /* IO - error status                           */
+/*
+  Read a consecutive string of bits from an 'X' or 'B' column and
+  interprete them as an unsigned integer.  The number of bits must be
+  less than or equal to 32 or the total number of bits in the column, 
+  which ever is less.
+*/
+{
+    int ii, firstbit, nbits, bytenum, startbit, numbits, endbit;
+    int firstbyte, lastbyte, nbytes, rshift, lshift;
+    unsigned int colbyte[5];
+    tcolumn *colptr;
+    char message[81];
+
+    if (*status > 0 || nrows == 0)
+        return(*status);
+
+    /*  check input parameters */
+    if (firstrow < 1)
+    {
+          sprintf(message, "Starting row number is less than 1: %ld (ffgcxuk)",
+                (long) firstrow);
+          ffpmsg(message);
+          return(*status = BAD_ROW_NUM);
+    }
+    else if (input_first_bit < 1)
+    {
+          sprintf(message, "Starting bit number is less than 1: %ld (ffgcxuk)",
+                input_first_bit);
+          ffpmsg(message);
+          return(*status = BAD_ELEM_NUM);
+    }
+    else if (input_nbits > 32)
+    {
+          sprintf(message, "Number of bits to read is > 32: %d (ffgcxuk)",
+                input_nbits);
+          ffpmsg(message);
+          return(*status = BAD_ELEM_NUM);
+    }
+
+    /* position to the correct HDU */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    /* rescan header if data structure is undefined */
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+        if ( ffrdef(fptr, status) > 0)               
+            return(*status);
+
+    if ((fptr->Fptr)->hdutype != BINARY_TBL)
+    {
+        ffpmsg("This is not a binary table extension (ffgcxuk)");
+        return(*status = NOT_BTABLE);
+    }
+
+    if (colnum > (fptr->Fptr)->tfield)
+    {
+      sprintf(message, "Specified column number is out of range: %d (ffgcxuk)",
+                colnum);
+        ffpmsg(message);
+        sprintf(message, "  There are %d columns in this table.",
+                (fptr->Fptr)->tfield );
+        ffpmsg(message);
+
+        return(*status = BAD_COL_NUM);
+    }       
+
+    colptr  = (fptr->Fptr)->tableptr;   /* point to first column */
+    colptr += (colnum - 1);     /* offset to correct column structure */
+
+    if (abs(colptr->tdatatype) > TBYTE)
+    {
+        ffpmsg("Can only read bits from X or B type columns. (ffgcxuk)");
+        return(*status = NOT_LOGICAL_COL); /* not correct datatype column */
+    }
+
+    firstbyte = (input_first_bit - 1              ) / 8 + 1;
+    lastbyte  = (input_first_bit + input_nbits - 2) / 8 + 1;
+    nbytes = lastbyte - firstbyte + 1;
+
+    if (colptr->tdatatype == TBIT && 
+        input_first_bit + input_nbits - 1 > (long) colptr->trepeat)
+    {
+        ffpmsg("Too many bits. Tried to read past width of column (ffgcxuk)");
+        return(*status = BAD_ELEM_NUM);
+    }
+    else if (colptr->tdatatype == TBYTE && lastbyte > (long) colptr->trepeat)
+    {
+        ffpmsg("Too many bits. Tried to read past width of column (ffgcxuk)");
+        return(*status = BAD_ELEM_NUM);
+    }
+
+    for (ii = 0; ii < nrows; ii++)
+    {
+        /* read the relevant bytes from the row */
+        if (ffgcvuk(fptr, colnum, firstrow+ii, firstbyte, nbytes, 0, 
+               colbyte, NULL, status) > 0)
+        {
+             ffpmsg("Error reading bytes from column (ffgcxuk)");
+             return(*status);
+        }
+
+        firstbit = (input_first_bit - 1) % 8; /* modulus operator */
+        nbits = input_nbits;
+
+        array[ii] = 0;
+
+        /* select and shift the bits from each byte into the output word */
+        while(nbits)
+        {
+            bytenum = firstbit / 8;
+
+            startbit = firstbit % 8;  
+            numbits = minvalue(nbits, 8 - startbit);
+            endbit = startbit + numbits - 1;
+
+            rshift = 7 - endbit;
+            lshift = nbits - numbits;
+
+            array[ii] = ((colbyte[bytenum] >> rshift) << lshift) | array[ii];
+
+            nbits -= numbits;
+            firstbit += numbits;
+        }
+    }
+
+    return(*status);
+}
diff --git a/src/plugins/cfitsio/getcols.c b/src/plugins/cfitsio/getcols.c
new file mode 100644
index 0000000..7033d6c
--- /dev/null
+++ b/src/plugins/cfitsio/getcols.c
@@ -0,0 +1,835 @@
+/*  This file, getcols.c, contains routines that read data elements from   */
+/*  a FITS image or table, with a character string datatype.               */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+
+#include <stdlib.h>
+#include <string.h>
+/* stddef.h is apparently needed to define size_t */
+#include <stddef.h>
+#include <ctype.h>
+#include "fitsio2.h"
+/*--------------------------------------------------------------------------*/
+int ffgcvs( fitsfile *fptr,   /* I - FITS file pointer                       */
+            int  colnum,      /* I - number of column to read (1 = 1st col)  */
+            LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+            LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+            LONGLONG  nelem,      /* I - number of strings to read               */
+            char *nulval,     /* I - string for null pixels                  */
+            char **array,     /* O - array of values that are read           */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of string values from a column in the current FITS HDU.
+  Any undefined pixels will be set equal to the value of 'nulval' unless
+  nulval = null in which case no checks for undefined pixels will be made.
+*/
+{
+    char cdummy[2];
+
+    ffgcls(fptr, colnum, firstrow, firstelem, nelem, 1, nulval,
+           array, cdummy, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcfs( fitsfile *fptr,   /* I - FITS file pointer                       */
+            int  colnum,      /* I - number of column to read (1 = 1st col) */
+            LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)        */
+            LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st) */
+            LONGLONG  nelem,      /* I - number of strings to read              */
+            char **array,     /* O - array of values that are read           */
+            char *nularray,   /* O - array of flags = 1 if nultyp = 2        */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of string values from a column in the current FITS HDU.
+  Nularray will be set = 1 if the corresponding array pixel is undefined, 
+  otherwise nularray will = 0.
+*/
+{
+    char dummy[2];
+
+    ffgcls(fptr, colnum, firstrow, firstelem, nelem, 2, dummy,
+           array, nularray, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcls( fitsfile *fptr,   /* I - FITS file pointer                       */
+            int  colnum,      /* I - number of column to read (1 = 1st col) */
+            LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)        */
+            LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st) */
+            LONGLONG  nelem,      /* I - number of strings to read              */
+            int   nultyp,     /* I - null value handling code:               */
+                              /*     1: set undefined pixels = nulval        */
+                              /*     2: set nularray=1 for undefined pixels  */
+            char  *nulval,    /* I - value for null pixels if nultyp = 1     */
+            char **array,     /* O - array of values that are read           */
+            char *nularray,   /* O - array of flags = 1 if nultyp = 2        */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of string values from a column in the current FITS HDU.
+  Returns a formated string value, regardless of the datatype of the column
+*/
+{
+    int tcode, hdutype, tstatus, scaled, intcol, dwidth, nulwidth, ll, dlen;
+    long ii, jj;
+    tcolumn *colptr;
+    char message[FLEN_ERRMSG], *carray, keyname[FLEN_KEYWORD];
+    char cform[20], dispfmt[20], tmpstr[400], *flgarray, tmpnull[80];
+    unsigned char byteval;
+    float *earray;
+    double *darray, tscale = 1.0;
+    LONGLONG *llarray;
+
+    if (*status > 0 || nelem == 0)  /* inherit input status value if > 0 */
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    /* rescan header if data structure is undefined */
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+        if ( ffrdef(fptr, status) > 0)               
+            return(*status);
+
+    if (colnum < 1 || colnum > (fptr->Fptr)->tfield)
+    {
+        sprintf(message, "Specified column number is out of range: %d",
+                colnum);
+        ffpmsg(message);
+        return(*status = BAD_COL_NUM);
+    }
+
+    colptr  = (fptr->Fptr)->tableptr;   /* point to first column */
+    colptr += (colnum - 1);     /* offset to correct column structure */
+    tcode = abs(colptr->tdatatype);
+
+    if (tcode == TSTRING)
+    {
+      /* simply call the string column reading routine */
+      ffgcls2(fptr, colnum, firstrow, firstelem, nelem, nultyp, nulval,
+           array, nularray, anynul, status);
+    }
+    else if (tcode == TLOGICAL)
+    {
+      /* allocate memory for the array of logical values */
+      carray = (char *) malloc((size_t) nelem);
+
+      /*  call the logical column reading routine */
+      ffgcll(fptr, colnum, firstrow, firstelem, nelem, nultyp, *nulval,
+           carray, nularray, anynul, status); 
+
+      if (*status <= 0)
+      {
+         /* convert logical values to "T", "F", or "N" (Null) */
+         for (ii = 0; ii < nelem; ii++)
+         {
+           if (carray[ii] == 1)
+              strcpy(array[ii], "T");
+           else if (carray[ii] == 0)
+              strcpy(array[ii], "F");
+           else  /* undefined values = 2 */
+              strcpy(array[ii],"N");
+         }
+      }
+
+      free(carray);  /* free the memory */
+    }
+    else if (tcode == TCOMPLEX)
+    {
+      /* allocate memory for the array of double values */
+      earray = (float *) calloc((size_t) (nelem * 2), sizeof(float) );
+      
+      ffgcle(fptr, colnum, firstrow, (firstelem - 1) * 2 + 1, nelem * 2,
+        1, 1, FLOATNULLVALUE, earray, nularray, anynul, status);
+
+      if (*status <= 0)
+      {
+
+         /* determine the format for the output strings */
+
+         ffgcdw(fptr, colnum, &dwidth, status);
+         dwidth = (dwidth - 3) / 2;
+ 
+         /* use the TDISPn keyword if it exists */
+         ffkeyn("TDISP", colnum, keyname, status);
+         tstatus = 0;
+         cform[0] = '\0';
+
+         if (ffgkys(fptr, keyname, dispfmt, NULL, &tstatus) == 0)
+         {
+             /* convert the Fortran style format to a C style format */
+             ffcdsp(dispfmt, cform);
+         }
+
+         if (!cform[0])
+             strcpy(cform, "%14.6E");
+
+         /* write the formated string for each value:  "(real,imag)" */
+         jj = 0;
+         for (ii = 0; ii < nelem; ii++)
+         {
+           strcpy(array[ii], "(");
+
+           /* test for null value */
+           if (earray[jj] == FLOATNULLVALUE)
+           {
+             strcpy(tmpstr, "NULL");
+             if (nultyp == 2)
+                nularray[ii] = 1;
+           }
+           else
+             sprintf(tmpstr, cform, earray[jj]);
+
+           strncat(array[ii], tmpstr, dwidth);
+           strcat(array[ii], ",");
+           jj++;
+
+           /* test for null value */
+           if (earray[jj] == FLOATNULLVALUE)
+           {
+             strcpy(tmpstr, "NULL");
+             if (nultyp == 2)
+                nularray[ii] = 1;
+           }
+           else
+             sprintf(tmpstr, cform, earray[jj]);
+
+           strncat(array[ii], tmpstr, dwidth);
+           strcat(array[ii], ")");
+           jj++;
+         }
+      }
+
+      free(earray);  /* free the memory */
+    }
+    else if (tcode == TDBLCOMPLEX)
+    {
+      /* allocate memory for the array of double values */
+      darray = (double *) calloc((size_t) (nelem * 2), sizeof(double) );
+      
+      ffgcld(fptr, colnum, firstrow, (firstelem - 1) * 2 + 1, nelem * 2,
+        1, 1, DOUBLENULLVALUE, darray, nularray, anynul, status);
+
+      if (*status <= 0)
+      {
+         /* determine the format for the output strings */
+
+         ffgcdw(fptr, colnum, &dwidth, status);
+         dwidth = (dwidth - 3) / 2;
+
+         /* use the TDISPn keyword if it exists */
+         ffkeyn("TDISP", colnum, keyname, status);
+         tstatus = 0;
+         cform[0] = '\0';
+ 
+         if (ffgkys(fptr, keyname, dispfmt, NULL, &tstatus) == 0)
+         {
+             /* convert the Fortran style format to a C style format */
+             ffcdsp(dispfmt, cform);
+         }
+
+         if (!cform[0])
+            strcpy(cform, "%23.15E");
+
+         /* write the formated string for each value:  "(real,imag)" */
+         jj = 0;
+         for (ii = 0; ii < nelem; ii++)
+         {
+           strcpy(array[ii], "(");
+
+           /* test for null value */
+           if (darray[jj] == DOUBLENULLVALUE)
+           {
+             strcpy(tmpstr, "NULL");
+             if (nultyp == 2)
+                nularray[ii] = 1;
+           }
+           else
+             sprintf(tmpstr, cform, darray[jj]);
+
+           strncat(array[ii], tmpstr, dwidth);
+           strcat(array[ii], ",");
+           jj++;
+
+           /* test for null value */
+           if (darray[jj] == DOUBLENULLVALUE)
+           {
+             strcpy(tmpstr, "NULL");
+             if (nultyp == 2)
+                nularray[ii] = 1;
+           }
+           else
+             sprintf(tmpstr, cform, darray[jj]);
+
+           strncat(array[ii], tmpstr, dwidth);
+           strcat(array[ii], ")");
+           jj++;
+         }
+      }
+
+      free(darray);  /* free the memory */
+    }
+    else if (tcode == TLONGLONG)
+    {
+      /* allocate memory for the array of LONGLONG values */
+      llarray = (LONGLONG *) calloc((size_t) nelem, sizeof(LONGLONG) );
+      flgarray = (char *) calloc((size_t) nelem, sizeof(char) );
+      dwidth = 20;  /* max width of displayed long long integer value */
+
+      if (ffgcfjj(fptr, colnum, firstrow, firstelem, nelem,
+            llarray, flgarray, anynul, status) > 0)
+      {
+         free(flgarray);
+         free(llarray);
+         return(*status);
+      }
+
+      /* write the formated string for each value */
+      if (nulval) {
+          strcpy(tmpnull, nulval);
+          nulwidth = strlen(nulval);
+      } else {
+          strcpy(tmpnull, " ");
+          nulwidth = 1;
+      }
+
+      for (ii = 0; ii < nelem; ii++)
+      {
+           if ( flgarray[ii] )
+           {
+              *array[ii] = '\0';
+              if (dwidth < nulwidth)
+                  strncat(array[ii], tmpnull, dwidth);
+              else
+                  sprintf(array[ii],"%*s",dwidth,tmpnull);
+		  
+              if (nultyp == 2)
+	          nularray[ii] = 1;
+           }
+           else
+           {	   
+
+#if defined(_MSC_VER)
+    /* Microsoft Visual C++ 6.0 uses '%I64d' syntax  for 8-byte integers */
+        sprintf(tmpstr, "%20I64d", llarray[ii]);
+#elif (USE_LL_SUFFIX == 1)
+        sprintf(tmpstr, "%20lld", llarray[ii]);
+#else
+        sprintf(tmpstr, "%20ld", llarray[ii]);
+#endif
+              *array[ii] = '\0';
+              strncat(array[ii], tmpstr, 20);
+           }
+      }
+
+      free(flgarray);
+      free(llarray);  /* free the memory */
+
+    }
+    else
+    {
+      /* allocate memory for the array of double values */
+      darray = (double *) calloc((size_t) nelem, sizeof(double) );
+      
+      /* read all other numeric type columns as doubles */
+      if (ffgcld(fptr, colnum, firstrow, firstelem, nelem, 1, nultyp, 
+           DOUBLENULLVALUE, darray, nularray, anynul, status) > 0)
+      {
+         free(darray);
+         return(*status);
+      }
+
+      /* determine the format for the output strings */
+
+      ffgcdw(fptr, colnum, &dwidth, status);
+
+      /* check if  column is scaled */
+      ffkeyn("TSCAL", colnum, keyname, status);
+      tstatus = 0;
+      scaled = 0;
+      if (ffgkyd(fptr, keyname, &tscale, NULL, &tstatus) == 0)
+      {
+            if (tscale != 1.0)
+                scaled = 1;    /* yes, this is a scaled column */
+      }
+
+      intcol = 0;
+      if (tcode <= TLONG && !scaled)
+             intcol = 1;   /* this is an unscaled integer column */
+
+      /* use the TDISPn keyword if it exists */
+      ffkeyn("TDISP", colnum, keyname, status);
+      tstatus = 0;
+      cform[0] = '\0';
+
+      if (ffgkys(fptr, keyname, dispfmt, NULL, &tstatus) == 0)
+      {
+           /* convert the Fortran style TDISPn to a C style format */
+           ffcdsp(dispfmt, cform);
+      }
+
+      if (!cform[0])
+      {
+            /* no TDISPn keyword; use TFORMn instead */
+
+            ffkeyn("TFORM", colnum, keyname, status);
+            ffgkys(fptr, keyname, dispfmt, NULL, status);
+
+            if (scaled && tcode <= TSHORT)
+            {
+                  /* scaled short integer column == float */
+                  strcpy(cform, "%#14.6G");
+            }
+            else if (scaled && tcode == TLONG)
+            {
+                  /* scaled long integer column == double */
+                  strcpy(cform, "%#23.15G");
+            }
+            else
+            {
+               ffghdt(fptr, &hdutype, status);
+               if (hdutype == ASCII_TBL)
+               {
+                  /* convert the Fortran style TFORMn to a C style format */
+                  ffcdsp(dispfmt, cform);
+               }
+               else
+               {
+                 /* this is a binary table, need to convert the format */
+                  if (tcode == TBIT) {            /* 'X' */
+                     strcpy(cform, "%4d");
+                  } else if (tcode == TBYTE) {    /* 'B' */
+                     strcpy(cform, "%4d");
+                  } else if (tcode == TSHORT) {   /* 'I' */
+                     strcpy(cform, "%6d");
+                  } else if (tcode == TLONG) {    /* 'J' */
+                     strcpy(cform, "%11.0f");
+                     intcol = 0;  /* needed to support unsigned int */
+                  } else if (tcode == TFLOAT) {   /* 'E' */
+                     strcpy(cform, "%#14.6G");
+                  } else if (tcode == TDOUBLE) {  /* 'D' */
+                     strcpy(cform, "%#23.15G");
+                  }
+               }
+            }
+      } 
+
+      if (nulval) {
+          strcpy(tmpnull, nulval);
+          nulwidth = strlen(nulval);
+      } else {
+          strcpy(tmpnull, " ");
+          nulwidth = 1;
+      }
+
+      /* write the formated string for each value */
+      for (ii = 0; ii < nelem; ii++)
+      {
+           if (tcode == TBIT)
+           {
+               byteval = (char) darray[ii];
+
+               for (ll=0; ll < 8; ll++)
+               {
+                   if ( ((unsigned char) (byteval << ll)) >> 7 )
+                       *(array[ii] + ll) = '1';
+                   else
+                       *(array[ii] + ll) = '0';
+               }
+               *(array[ii] + 8) = '\0';
+           }
+           /* test for null value */
+           else if ( (nultyp == 1 && darray[ii] == DOUBLENULLVALUE) ||
+                (nultyp == 2 && nularray[ii]) )
+           {
+              *array[ii] = '\0';
+              if (dwidth < nulwidth)
+                  strncat(array[ii], tmpnull, dwidth);
+              else
+                  sprintf(array[ii],"%*s",dwidth,tmpnull);
+           }
+           else
+           {	   
+              if (intcol)
+                sprintf(tmpstr, cform, (int) darray[ii]);
+              else
+                sprintf(tmpstr, cform, darray[ii]);
+
+              /* fill field with '*' if number is too wide */
+              dlen = strlen(tmpstr);
+	      if (dlen > dwidth) {
+	         memset(tmpstr, '*', dwidth);
+              }
+
+              *array[ii] = '\0';
+              strncat(array[ii], tmpstr, dwidth);
+           }
+      }
+
+      free(darray);  /* free the memory */
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcdw( fitsfile *fptr,   /* I - FITS file pointer                       */
+            int  colnum,      /* I - number of column (1 = 1st col)      */
+            int  *width,      /* O - display width                       */
+            int  *status)     /* IO - error status                           */
+/*
+  Get Column Display Width.
+*/
+{
+    tcolumn *colptr;
+    char *cptr;
+    char message[FLEN_ERRMSG], keyname[FLEN_KEYWORD], dispfmt[20];
+    int tcode, hdutype, tstatus, scaled;
+    double tscale;
+
+    if (*status > 0)  /* inherit input status value if > 0 */
+        return(*status);
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    if (colnum < 1 || colnum > (fptr->Fptr)->tfield)
+    {
+        sprintf(message, "Specified column number is out of range: %d",
+                colnum);
+        ffpmsg(message);
+        return(*status = BAD_COL_NUM);
+    }
+
+    colptr  = (fptr->Fptr)->tableptr;   /* point to first column */
+    colptr += (colnum - 1);     /* offset to correct column structure */
+    tcode = abs(colptr->tdatatype);
+
+    /* use the TDISPn keyword if it exists */
+    ffkeyn("TDISP", colnum, keyname, status);
+
+    *width = 0;
+    tstatus = 0;
+    if (ffgkys(fptr, keyname, dispfmt, NULL, &tstatus) == 0)
+    {
+          /* parse TDISPn get the display width */
+          cptr = dispfmt;
+          while(*cptr == ' ') /* skip leading blanks */
+              cptr++;
+
+          if (*cptr == 'A' || *cptr == 'a' ||
+              *cptr == 'I' || *cptr == 'i' ||
+              *cptr == 'O' || *cptr == 'o' ||
+              *cptr == 'Z' || *cptr == 'z' ||
+              *cptr == 'F' || *cptr == 'f' ||
+              *cptr == 'E' || *cptr == 'e' ||
+              *cptr == 'D' || *cptr == 'd' ||
+              *cptr == 'G' || *cptr == 'g')
+          {
+
+            while(!isdigit((int) *cptr) && *cptr != '\0') /* find 1st digit */
+              cptr++;
+
+            *width = atoi(cptr);
+            if (tcode >= TCOMPLEX)
+              *width = (2 * (*width)) + 3;
+          }
+    }
+
+    if (*width == 0)
+    {
+        /* no valid TDISPn keyword; use TFORMn instead */
+
+        ffkeyn("TFORM", colnum, keyname, status);
+        ffgkys(fptr, keyname, dispfmt, NULL, status);
+
+        /* check if  column is scaled */
+        ffkeyn("TSCAL", colnum, keyname, status);
+        tstatus = 0;
+        scaled = 0;
+
+        if (ffgkyd(fptr, keyname, &tscale, NULL, &tstatus) == 0)
+        {
+            if (tscale != 1.0)
+                scaled = 1;    /* yes, this is a scaled column */
+        }
+
+        if (scaled && tcode <= TSHORT)
+        {
+            /* scaled short integer col == float; default format is 14.6G */
+            *width = 14;
+        }
+        else if (scaled && tcode == TLONG)
+        {
+            /* scaled long integer col == double; default format is 23.15G */
+            *width = 23;
+        }
+        else
+        {
+           ffghdt(fptr, &hdutype, status);  /* get type of table */
+           if (hdutype == ASCII_TBL)
+           {
+              /* parse TFORMn get the display width */
+              cptr = dispfmt;
+              while(!isdigit((int) *cptr) && *cptr != '\0') /* find 1st digit */
+                 cptr++;
+
+              *width = atoi(cptr);
+           }
+           else
+           {
+                 /* this is a binary table */
+                  if (tcode == TBIT)           /* 'X' */
+                     *width = 8;
+                  else if (tcode == TBYTE)     /* 'B' */
+                     *width = 4;
+                  else if (tcode == TSHORT)    /* 'I' */
+                     *width = 6;
+                  else if (tcode == TLONG)     /* 'J' */
+                     *width = 11;
+                  else if (tcode == TLONGLONG) /* 'K' */
+                     *width = 20;
+                  else if (tcode == TFLOAT)    /* 'E' */
+                     *width = 14;
+                  else if (tcode == TDOUBLE)   /* 'D' */
+                     *width = 23;
+                  else if (tcode == TCOMPLEX)  /* 'C' */
+                     *width = 31;
+                  else if (tcode == TDBLCOMPLEX)  /* 'M' */
+                     *width = 49;
+                  else if (tcode == TLOGICAL)  /* 'L' */
+                     *width = 1;
+                  else if (tcode == TSTRING)   /* 'A' */
+                  {
+                     cptr = dispfmt;
+                     while(!isdigit((int) *cptr) && *cptr != '\0') 
+                         cptr++;
+
+                     *width = atoi(cptr);
+
+                     if (*width < 1)
+                         *width = 1;  /* default is at least 1 column */
+                  }
+            }
+        }
+    } 
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcls2 ( fitsfile *fptr,   /* I - FITS file pointer                       */
+            int  colnum,      /* I - number of column to read (1 = 1st col) */
+            LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)        */
+            LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st) */
+            LONGLONG  nelem,      /* I - number of strings to read              */
+            int   nultyp,     /* I - null value handling code:               */
+                              /*     1: set undefined pixels = nulval        */
+                              /*     2: set nularray=1 for undefined pixels  */
+            char  *nulval,    /* I - value for null pixels if nultyp = 1     */
+            char **array,     /* O - array of values that are read           */
+            char *nularray,   /* O - array of flags = 1 if nultyp = 2        */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of string values from a column in the current FITS HDU.
+*/
+{
+    double dtemp;
+    long nullen; 
+    int tcode, maxelem, hdutype, nulcheck;
+    long twidth, incre;
+    long ii, jj, ntodo;
+    LONGLONG repeat, startpos, elemnum, readptr, tnull, rowlen, rownum, remain, next;
+    double scale, zero;
+    char tform[20];
+    char message[FLEN_ERRMSG];
+    char snull[20];   /*  the FITS null value  */
+    tcolumn *colptr;
+
+    double cbuff[DBUFFSIZE / sizeof(double)]; /* align cbuff on word boundary */
+    char *buffer, *arrayptr;
+
+    if (*status > 0 || nelem == 0)  /* inherit input status value if > 0 */
+        return(*status);
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    if (anynul)
+        *anynul = 0;
+
+    if (nultyp == 2)
+        memset(nularray, 0, (size_t) nelem);   /* initialize nullarray */
+
+    /*---------------------------------------------------*/
+    /*  Check input and get parameters about the column: */
+    /*---------------------------------------------------*/
+    if (colnum < 1 || colnum > (fptr->Fptr)->tfield)
+    {
+        sprintf(message, "Specified column number is out of range: %d",
+                colnum);
+        ffpmsg(message);
+        return(*status = BAD_COL_NUM);
+    }
+
+    colptr  = (fptr->Fptr)->tableptr;   /* point to first column */
+    colptr += (colnum - 1);     /* offset to correct column structure */
+    tcode = colptr->tdatatype;
+
+    if (tcode == -TSTRING) /* variable length column in a binary table? */
+    {
+      /* only read a single string; ignore value of firstelem */
+
+      if (ffgcprll( fptr, colnum, firstrow, 1, 1, 0, &scale, &zero,
+        tform, &twidth, &tcode, &maxelem, &startpos,  &elemnum, &incre,
+        &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0)
+        return(*status);
+
+      remain = 1;
+      twidth = (long) repeat;  
+    }
+    else if (tcode == TSTRING)
+    {
+      if (ffgcprll( fptr, colnum, firstrow, firstelem, nelem, 0, &scale, &zero,
+        tform, &twidth, &tcode, &maxelem, &startpos,  &elemnum, &incre,
+        &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0)
+        return(*status);
+
+      /* if string length is greater than a FITS block (2880 char) then must */
+      /* only read 1 string at a time, to force reading by ffgbyt instead of */
+      /* ffgbytoff (ffgbytoff can't handle this case) */
+      if (twidth > IOBUFLEN) {
+        maxelem = 1;
+        incre = twidth;
+        repeat = 1;
+      }   
+
+      remain = nelem;
+    }
+    else
+        return(*status = NOT_ASCII_COL);
+
+    nullen = strlen(snull);   /* length of the undefined pixel string */
+    if (nullen == 0)
+        nullen = 1;
+ 
+    /*------------------------------------------------------------------*/
+    /*  Decide whether to check for null values in the input FITS file: */
+    /*------------------------------------------------------------------*/
+    nulcheck = nultyp; /* by default check for null values in the FITS file */
+
+    if (nultyp == 1 && nulval == 0)
+       nulcheck = 0;    /* calling routine does not want to check for nulls */
+
+    else if (nultyp == 1 && nulval && nulval[0] == 0)
+       nulcheck = 0;    /* calling routine does not want to check for nulls */
+
+    else if (snull[0] == ASCII_NULL_UNDEFINED)
+       nulcheck = 0;   /* null value string in ASCII table not defined */
+
+    else if (nullen > twidth)
+       nulcheck = 0;   /* null value string is longer than width of column  */
+                       /* thus impossible for any column elements to = null */
+
+    /*---------------------------------------------------------------------*/
+    /*  Now read the strings one at a time from the FITS column.           */
+    /*---------------------------------------------------------------------*/
+    next = 0;                 /* next element in array to be read  */
+    rownum = 0;               /* row number, relative to firstrow     */
+
+    while (remain)
+    {
+      /* limit the number of pixels to process at one time to the number that
+         will fit in the buffer space or to the number of pixels that remain
+         in the current vector, which ever is smaller.
+      */
+      ntodo = (long) minvalue(remain, maxelem);      
+      ntodo = (long) minvalue(ntodo, (repeat - elemnum));
+
+      readptr = startpos + ((LONGLONG)rownum * rowlen) + (elemnum * incre);
+      ffmbyt(fptr, readptr, REPORT_EOF, status);  /* move to read position */
+
+      /* read the array of strings from the FITS file into the buffer */
+
+      if (incre == twidth)
+         ffgbyt(fptr, ntodo * twidth, cbuff, status);
+      else
+         ffgbytoff(fptr, twidth, ntodo, incre - twidth, cbuff, status);
+
+      /* copy from the buffer into the user's array of strings */
+      /* work backwards from last char of last string to 1st char of 1st */
+
+      buffer = ((char *) cbuff) + (ntodo * twidth) - 1;
+
+      for (ii = (long) (next + ntodo - 1); ii >= next; ii--)
+      {
+         arrayptr = array[ii] + twidth - 1;
+
+         for (jj = twidth - 1; jj > 0; jj--)  /* ignore trailing blanks */
+         {
+            if (*buffer == ' ')
+            {
+              buffer--;
+              arrayptr--;
+            }
+            else
+              break;
+         }
+         *(arrayptr + 1) = 0;  /* write the string terminator */
+         
+         for (; jj >= 0; jj--)    /* copy the string itself */
+         {
+           *arrayptr = *buffer;
+           buffer--;
+           arrayptr--;
+         }
+
+         /* check if null value is defined, and if the   */
+         /* column string is identical to the null string */
+         if (nulcheck && !strncmp(snull, array[ii], nullen) )
+         {
+           *anynul = 1;   /* this is a null value */
+           if (nultyp == 1) {
+	   
+	     if (nulval)
+                strcpy(array[ii], nulval);
+	     else
+	        strcpy(array[ii], " ");
+	     
+           } else
+             nularray[ii] = 1;
+         }
+      }
+    
+      if (*status > 0)  /* test for error during previous read operation */
+      {
+         dtemp = (double) next;
+         sprintf(message,
+          "Error reading elements %.0f thru %.0f of data array (ffpcls).",
+             dtemp+1., dtemp+ntodo);
+
+         ffpmsg(message);
+         return(*status);
+      }
+
+      /*--------------------------------------------*/
+      /*  increment the counters for the next loop  */
+      /*--------------------------------------------*/
+      next += ntodo;
+      remain -= ntodo;
+      if (remain)
+      {
+          elemnum += ntodo;
+          if (elemnum == repeat)  /* completed a row; start on next row */
+          {
+              elemnum = 0;
+              rownum++;
+          }
+      }
+    }  /*  End of main while Loop  */
+
+    return(*status);
+}
+
diff --git a/src/plugins/cfitsio/getcolsb.c b/src/plugins/cfitsio/getcolsb.c
new file mode 100644
index 0000000..f750681
--- /dev/null
+++ b/src/plugins/cfitsio/getcolsb.c
@@ -0,0 +1,1991 @@
+/*  This file, getcolsb.c, contains routines that read data elements from   */
+/*  a FITS image or table, with signed char (signed byte) data type.        */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+
+#include <math.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <string.h>
+#include "fitsio2.h"
+
+/*--------------------------------------------------------------------------*/
+int ffgpvsb(fitsfile *fptr,   /* I - FITS file pointer                       */
+            long  group,      /* I - group to read (1 = 1st group)           */
+            LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+            LONGLONG  nelem,      /* I - number of values to read                */
+            signed char nulval, /* I - value for undefined pixels            */
+            signed char *array, /* O - array of values that are returned     */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+  Undefined elements will be set equal to NULVAL, unless NULVAL=0
+  in which case no checking for undefined values will be performed.
+  ANYNUL is returned with a value of .true. if any pixels are undefined.
+*/
+{
+    long row;
+    char cdummy;
+    int nullcheck = 1;
+    signed char nullvalue;
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+         nullvalue = nulval;  /* set local variable */
+
+        fits_read_compressed_pixels(fptr, TSBYTE, firstelem, nelem,
+            nullcheck, &nullvalue, array, NULL, anynul, status);
+        return(*status);
+    }
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffgclsb(fptr, 2, row, firstelem, nelem, 1, 1, nulval,
+               array, &cdummy, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgpfsb(fitsfile *fptr,   /* I - FITS file pointer                       */
+            long  group,      /* I - group to read (1 = 1st group)           */
+            LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+            LONGLONG  nelem,      /* I - number of values to read                */
+            signed char *array, /* O - array of values that are returned     */
+            char *nularray,   /* O - array of null pixel flags               */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+  Any undefined pixels in the returned array will be set = 0 and the 
+  corresponding nularray value will be set = 1.
+  ANYNUL is returned with a value of .true. if any pixels are undefined.
+*/
+{
+    long row;
+    int nullcheck = 2;
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        fits_read_compressed_pixels(fptr, TSBYTE, firstelem, nelem,
+            nullcheck, NULL, array, nularray, anynul, status);
+        return(*status);
+    }
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffgclsb(fptr, 2, row, firstelem, nelem, 1, 2, 0,
+               array, nularray, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffg2dsb(fitsfile *fptr, /* I - FITS file pointer                       */
+           long  group,     /* I - group to read (1 = 1st group)           */
+           signed char nulval,   /* set undefined pixels equal to this     */
+           LONGLONG  ncols,     /* I - number of pixels in each row of array   */
+           LONGLONG  naxis1,    /* I - FITS image NAXIS1 value                 */
+           LONGLONG  naxis2,    /* I - FITS image NAXIS2 value                 */
+           signed char *array,   /* O - array to be filled and returned    */
+           int  *anynul,    /* O - set to 1 if any values are null; else 0 */
+           int  *status)    /* IO - error status                           */
+/*
+  Read an entire 2-D array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being read).  Any null
+  values in the array will be set equal to the value of nulval, unless
+  nulval = 0 in which case no null checking will be performed.
+*/
+{
+    /* call the 3D reading routine, with the 3rd dimension = 1 */
+
+    ffg3dsb(fptr, group, nulval, ncols, naxis2, naxis1, naxis2, 1, array, 
+           anynul, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffg3dsb(fitsfile *fptr, /* I - FITS file pointer                       */
+           long  group,     /* I - group to read (1 = 1st group)           */
+           signed char nulval,   /* set undefined pixels equal to this     */
+           LONGLONG  ncols,     /* I - number of pixels in each row of array   */
+           LONGLONG  nrows,     /* I - number of rows in each plane of array   */
+           LONGLONG  naxis1,    /* I - FITS image NAXIS1 value                 */
+           LONGLONG  naxis2,    /* I - FITS image NAXIS2 value                 */
+           LONGLONG  naxis3,    /* I - FITS image NAXIS3 value                 */
+           signed char *array,   /* O - array to be filled and returned    */
+           int  *anynul,    /* O - set to 1 if any values are null; else 0 */
+           int  *status)    /* IO - error status                           */
+/*
+  Read an entire 3-D array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being read).  Any null
+  values in the array will be set equal to the value of nulval, unless
+  nulval = 0 in which case no null checking will be performed.
+*/
+{
+    long tablerow, ii, jj;
+    LONGLONG  nfits, narray;
+    char cdummy;
+    int  nullcheck = 1;
+    long inc[] = {1,1,1};
+    LONGLONG fpixel[] = {1,1,1};
+    LONGLONG lpixel[3];
+    signed char nullvalue;
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        lpixel[0] = ncols;
+        lpixel[1] = nrows;
+        lpixel[2] = naxis3;
+        nullvalue = nulval;  /* set local variable */
+
+        fits_read_compressed_img(fptr, TSBYTE, fpixel, lpixel, inc,
+            nullcheck, &nullvalue, array, NULL, anynul, status);
+        return(*status);
+    }
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+    tablerow=maxvalue(1,group);
+
+    if (ncols == naxis1 && nrows == naxis2)  /* arrays have same size? */
+    {
+       /* all the image pixels are contiguous, so read all at once */
+       ffgclsb(fptr, 2, tablerow, 1, naxis1 * naxis2 * naxis3, 1, 1, nulval,
+               array, &cdummy, anynul, status);
+       return(*status);
+    }
+
+    if (ncols < naxis1 || nrows < naxis2)
+       return(*status = BAD_DIMEN);
+
+    nfits = 1;   /* next pixel in FITS image to read */
+    narray = 0;  /* next pixel in output array to be filled */
+
+    /* loop over naxis3 planes in the data cube */
+    for (jj = 0; jj < naxis3; jj++)
+    {
+      /* loop over the naxis2 rows in the FITS image, */
+      /* reading naxis1 pixels to each row            */
+
+      for (ii = 0; ii < naxis2; ii++)
+      {
+       if (ffgclsb(fptr, 2, tablerow, nfits, naxis1, 1, 1, nulval,
+          &array[narray], &cdummy, anynul, status) > 0)
+          return(*status);
+
+       nfits += naxis1;
+       narray += ncols;
+      }
+      narray += (nrows - naxis2) * ncols;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgsvsb(fitsfile *fptr, /* I - FITS file pointer                        */
+           int  colnum,    /* I - number of the column to read (1 = 1st)    */
+           int naxis,      /* I - number of dimensions in the FITS array    */
+           long  *naxes,   /* I - size of each dimension                    */
+           long  *blc,     /* I - 'bottom left corner' of the subsection    */
+           long  *trc,     /* I - 'top right corner' of the subsection      */
+           long  *inc,     /* I - increment to be applied in each dimension */
+           signed char nulval, /* I - value to set undefined pixels         */
+           signed char *array, /* O - array to be filled and returned       */
+           int  *anynul,   /* O - set to 1 if any values are null; else 0   */
+           int  *status)   /* IO - error status                             */
+/*
+  Read a subsection of data values from an image or a table column.
+  This routine is set up to handle a maximum of nine dimensions.
+*/
+{
+    long ii, i0, i1, i2, i3, i4, i5, i6, i7, i8, row, rstr, rstp, rinc;
+    long str[9], stp[9], incr[9], dir[9];
+    long nelem, nultyp, ninc, numcol;
+    LONGLONG felem, dsize[10], blcll[9], trcll[9];
+    int hdutype, anyf;
+    char ldummy, msg[FLEN_ERRMSG];
+    int  nullcheck = 1;
+    signed char nullvalue;
+
+    if (naxis < 1 || naxis > 9)
+    {
+        sprintf(msg, "NAXIS = %d in call to ffgsvsb is out of range", naxis);
+        ffpmsg(msg);
+        return(*status = BAD_DIMEN);
+    }
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        for (ii=0; ii < naxis; ii++) {
+	    blcll[ii] = blc[ii];
+	    trcll[ii] = trc[ii];
+	}
+
+        nullvalue = nulval;  /* set local variable */
+
+        fits_read_compressed_img(fptr, TSBYTE, blcll, trcll, inc,
+            nullcheck, &nullvalue, array, NULL, anynul, status);
+        return(*status);
+    }
+
+/*
+    if this is a primary array, then the input COLNUM parameter should
+    be interpreted as the row number, and we will alway read the image
+    data from column 2 (any group parameters are in column 1).
+*/
+    if (ffghdt(fptr, &hdutype, status) > 0)
+        return(*status);
+
+    if (hdutype == IMAGE_HDU)
+    {
+        /* this is a primary array, or image extension */
+        if (colnum == 0)
+        {
+            rstr = 1;
+            rstp = 1;
+        }
+        else
+        {
+            rstr = colnum;
+            rstp = colnum;
+        }
+        rinc = 1;
+        numcol = 2;
+    }
+    else
+    {
+        /* this is a table, so the row info is in the (naxis+1) elements */
+        rstr = blc[naxis];
+        rstp = trc[naxis];
+        rinc = inc[naxis];
+        numcol = colnum;
+    }
+
+    nultyp = 1;
+    if (anynul)
+        *anynul = FALSE;
+
+    i0 = 0;
+    for (ii = 0; ii < 9; ii++)
+    {
+        str[ii] = 1;
+        stp[ii] = 1;
+        incr[ii] = 1;
+        dsize[ii] = 1;
+        dir[ii] = 1;
+    }
+
+    for (ii = 0; ii < naxis; ii++)
+    {
+      if (trc[ii] < blc[ii])
+      {
+        if (hdutype == IMAGE_HDU)
+        {
+           dir[ii] = -1;
+        }
+        else
+        {
+          sprintf(msg, "ffgsvsb: illegal range specified for axis %ld", ii + 1);
+          ffpmsg(msg);
+          return(*status = BAD_PIX_NUM);
+        }
+      }
+
+      str[ii] = blc[ii];
+      stp[ii] = trc[ii];
+      incr[ii] = inc[ii];
+      dsize[ii + 1] = dsize[ii] * naxes[ii];
+      dsize[ii] = dsize[ii] * dir[ii];
+    }
+    dsize[naxis] = dsize[naxis] * dir[naxis];
+
+    if (naxis == 1 && naxes[0] == 1)
+    {
+      /* This is not a vector column, so read all the rows at once */
+      nelem = (rstp - rstr) / rinc + 1;
+      ninc = rinc;
+      rstp = rstr;
+    }
+    else
+    {
+      /* have to read each row individually, in all dimensions */
+      nelem = (stp[0]*dir[0] - str[0]*dir[0]) / inc[0] + 1;
+      ninc = incr[0] * dir[0];
+    }
+
+    for (row = rstr; row <= rstp; row += rinc)
+    {
+     for (i8 = str[8]*dir[8]; i8 <= stp[8]*dir[8]; i8 += incr[8])
+     {
+      for (i7 = str[7]*dir[7]; i7 <= stp[7]*dir[7]; i7 += incr[7])
+      {
+       for (i6 = str[6]*dir[6]; i6 <= stp[6]*dir[6]; i6 += incr[6])
+       {
+        for (i5 = str[5]*dir[5]; i5 <= stp[5]*dir[5]; i5 += incr[5])
+        {
+         for (i4 = str[4]*dir[4]; i4 <= stp[4]*dir[4]; i4 += incr[4])
+         {
+          for (i3 = str[3]*dir[3]; i3 <= stp[3]*dir[3]; i3 += incr[3])
+          {
+           for (i2 = str[2]*dir[2]; i2 <= stp[2]*dir[2]; i2 += incr[2])
+           {
+            for (i1 = str[1]*dir[1]; i1 <= stp[1]*dir[1]; i1 += incr[1])
+            {
+
+              felem=str[0] + (i1 - dir[1]) * dsize[1] + (i2 - dir[2]) * dsize[2] + 
+                             (i3 - dir[3]) * dsize[3] + (i4 - dir[4]) * dsize[4] +
+                             (i5 - dir[5]) * dsize[5] + (i6 - dir[6]) * dsize[6] +
+                             (i7 - dir[7]) * dsize[7] + (i8 - dir[8]) * dsize[8];
+
+              if ( ffgclsb(fptr, numcol, row, felem, nelem, ninc, nultyp,
+                   nulval, &array[i0], &ldummy, &anyf, status) > 0)
+                   return(*status);
+
+              if (anyf && anynul)
+                  *anynul = TRUE;
+
+              i0 += nelem;
+            }
+           }
+          }
+         }
+        }
+       }
+      }
+     }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgsfsb(fitsfile *fptr, /* I - FITS file pointer                        */
+           int  colnum,    /* I - number of the column to read (1 = 1st)    */
+           int naxis,      /* I - number of dimensions in the FITS array    */
+           long  *naxes,   /* I - size of each dimension                    */
+           long  *blc,     /* I - 'bottom left corner' of the subsection    */
+           long  *trc,     /* I - 'top right corner' of the subsection      */
+           long  *inc,     /* I - increment to be applied in each dimension */
+           signed char *array,   /* O - array to be filled and returned     */
+           char *flagval,  /* O - set to 1 if corresponding value is null   */
+           int  *anynul,   /* O - set to 1 if any values are null; else 0   */
+           int  *status)   /* IO - error status                             */
+/*
+  Read a subsection of data values from an image or a table column.
+  This routine is set up to handle a maximum of nine dimensions.
+*/
+{
+    long ii,i0, i1,i2,i3,i4,i5,i6,i7,i8,row,rstr,rstp,rinc;
+    long str[9],stp[9],incr[9],dsize[10];
+    LONGLONG blcll[9], trcll[9];
+    long felem, nelem, nultyp, ninc, numcol;
+    int hdutype, anyf;
+    signed char nulval = 0;
+    char msg[FLEN_ERRMSG];
+    int  nullcheck = 2;
+
+    if (naxis < 1 || naxis > 9)
+    {
+        sprintf(msg, "NAXIS = %d in call to ffgsvsb is out of range", naxis);
+        ffpmsg(msg);
+        return(*status = BAD_DIMEN);
+    }
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        for (ii=0; ii < naxis; ii++) {
+	    blcll[ii] = blc[ii];
+	    trcll[ii] = trc[ii];
+	}
+
+        fits_read_compressed_img(fptr, TSBYTE, blcll, trcll, inc,
+            nullcheck, NULL, array, flagval, anynul, status);
+        return(*status);
+    }
+
+/*
+    if this is a primary array, then the input COLNUM parameter should
+    be interpreted as the row number, and we will alway read the image
+    data from column 2 (any group parameters are in column 1).
+*/
+    if (ffghdt(fptr, &hdutype, status) > 0)
+        return(*status);
+
+    if (hdutype == IMAGE_HDU)
+    {
+        /* this is a primary array, or image extension */
+        if (colnum == 0)
+        {
+            rstr = 1;
+            rstp = 1;
+        }
+        else
+        {
+            rstr = colnum;
+            rstp = colnum;
+        }
+        rinc = 1;
+        numcol = 2;
+    }
+    else
+    {
+        /* this is a table, so the row info is in the (naxis+1) elements */
+        rstr = blc[naxis];
+        rstp = trc[naxis];
+        rinc = inc[naxis];
+        numcol = colnum;
+    }
+
+    nultyp = 2;
+    if (anynul)
+        *anynul = FALSE;
+
+    i0 = 0;
+    for (ii = 0; ii < 9; ii++)
+    {
+        str[ii] = 1;
+        stp[ii] = 1;
+        incr[ii] = 1;
+        dsize[ii] = 1;
+    }
+
+    for (ii = 0; ii < naxis; ii++)
+    {
+      if (trc[ii] < blc[ii])
+      {
+        sprintf(msg, "ffgsvsb: illegal range specified for axis %ld", ii + 1);
+        ffpmsg(msg);
+        return(*status = BAD_PIX_NUM);
+      }
+
+      str[ii] = blc[ii];
+      stp[ii] = trc[ii];
+      incr[ii] = inc[ii];
+      dsize[ii + 1] = dsize[ii] * naxes[ii];
+    }
+
+    if (naxis == 1 && naxes[0] == 1)
+    {
+      /* This is not a vector column, so read all the rows at once */
+      nelem = (rstp - rstr) / rinc + 1;
+      ninc = rinc;
+      rstp = rstr;
+    }
+    else
+    {
+      /* have to read each row individually, in all dimensions */
+      nelem = (stp[0] - str[0]) / inc[0] + 1;
+      ninc = incr[0];
+    }
+
+    for (row = rstr; row <= rstp; row += rinc)
+    {
+     for (i8 = str[8]; i8 <= stp[8]; i8 += incr[8])
+     {
+      for (i7 = str[7]; i7 <= stp[7]; i7 += incr[7])
+      {
+       for (i6 = str[6]; i6 <= stp[6]; i6 += incr[6])
+       {
+        for (i5 = str[5]; i5 <= stp[5]; i5 += incr[5])
+        {
+         for (i4 = str[4]; i4 <= stp[4]; i4 += incr[4])
+         {
+          for (i3 = str[3]; i3 <= stp[3]; i3 += incr[3])
+          {
+           for (i2 = str[2]; i2 <= stp[2]; i2 += incr[2])
+           {
+            for (i1 = str[1]; i1 <= stp[1]; i1 += incr[1])
+            {
+              felem=str[0] + (i1 - 1) * dsize[1] + (i2 - 1) * dsize[2] + 
+                             (i3 - 1) * dsize[3] + (i4 - 1) * dsize[4] +
+                             (i5 - 1) * dsize[5] + (i6 - 1) * dsize[6] +
+                             (i7 - 1) * dsize[7] + (i8 - 1) * dsize[8];
+
+              if ( ffgclsb(fptr, numcol, row, felem, nelem, ninc, nultyp,
+                   nulval, &array[i0], &flagval[i0], &anyf, status) > 0)
+                   return(*status);
+
+              if (anyf && anynul)
+                  *anynul = TRUE;
+
+              i0 += nelem;
+            }
+           }
+          }
+         }
+        }
+       }
+      }
+     }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffggpsb( fitsfile *fptr,   /* I - FITS file pointer                       */
+            long  group,      /* I - group to read (1 = 1st group)           */
+            long  firstelem,  /* I - first vector element to read (1 = 1st)  */
+            long  nelem,      /* I - number of values to read                */
+            signed char *array,   /* O - array of values that are returned   */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of group parameters from the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+*/
+{
+    long row;
+    int idummy;
+    char cdummy;
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffgclsb(fptr, 1, row, firstelem, nelem, 1, 1, 0,
+               array, &cdummy, &idummy, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcvsb(fitsfile *fptr,  /* I - FITS file pointer                       */
+           int  colnum,      /* I - number of column to read (1 = 1st col)  */
+           LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+           LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+           LONGLONG  nelem,      /* I - number of values to read                */
+           signed char nulval,   /* I - value for null pixels               */
+           signed char *array,   /* O - array of values that are read       */
+           int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+           int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from a column in the current FITS HDU. Automatic
+  datatype conversion will be performed if the datatype of the column does not
+  match the datatype of the array parameter. The output values will be scaled 
+  by the FITS TSCALn and TZEROn values if these values have been defined.
+  Any undefined pixels will be set equal to the value of 'nulval' unless
+  nulval = 0 in which case no checks for undefined pixels will be made.
+*/
+{
+    char cdummy;
+
+    ffgclsb(fptr, colnum, firstrow, firstelem, nelem, 1, 1, nulval,
+           array, &cdummy, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcfsb(fitsfile *fptr,  /* I - FITS file pointer                       */
+           int  colnum,      /* I - number of column to read (1 = 1st col)  */
+           LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+           LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+           LONGLONG  nelem,      /* I - number of values to read                */
+           signed char *array,   /* O - array of values that are read       */
+           char *nularray,   /* O - array of flags: 1 if null pixel; else 0 */
+           int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+           int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from a column in the current FITS HDU. Automatic
+  datatype conversion will be performed if the datatype of the column does not
+  match the datatype of the array parameter. The output values will be scaled 
+  by the FITS TSCALn and TZEROn values if these values have been defined.
+  Nularray will be set = 1 if the corresponding array pixel is undefined, 
+  otherwise nularray will = 0.
+*/
+{
+    signed char dummy = 0;
+
+    ffgclsb(fptr, colnum, firstrow, firstelem, nelem, 1, 2, dummy,
+           array, nularray, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgclsb(fitsfile *fptr,   /* I - FITS file pointer                       */
+            int  colnum,      /* I - number of column to read (1 = 1st col)  */
+            LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+            LONGLONG firstelem,  /* I - first vector element to read (1 = 1st)  */
+            LONGLONG  nelem,      /* I - number of values to read                */
+            long  elemincre,  /* I - pixel increment; e.g., 2 = every other  */
+            int   nultyp,     /* I - null value handling code:               */
+                              /*     1: set undefined pixels = nulval        */
+                              /*     2: set nularray=1 for undefined pixels  */
+            signed char nulval,   /* I - value for null pixels if nultyp = 1 */
+            signed char *array,   /* O - array of values that are read       */
+            char *nularray,   /* O - array of flags = 1 if nultyp = 2        */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from a column in the current FITS HDU.
+  The column number may refer to a real column in an ASCII or binary table, 
+  or it may refer be a virtual column in a 1 or more grouped FITS primary
+  array or image extension.  FITSIO treats a primary array as a binary table
+  with 2 vector columns: the first column contains the group parameters (often
+  with length = 0) and the second column contains the array of image pixels.
+  Each row of the table represents a group in the case of multigroup FITS
+  images.
+
+  The output array of values will be converted from the datatype of the column 
+  and will be scaled by the FITS TSCALn and TZEROn values if necessary.
+*/
+{
+    double scale, zero, power = 1., dtemp;
+    int tcode, maxelem, hdutype, xcode, decimals;
+    long twidth, incre;
+    long ii, xwidth, ntodo;
+    int nulcheck, readcheck = 0;
+    LONGLONG repeat, startpos, elemnum, readptr, tnull;
+    LONGLONG rowlen, rownum, remain, next, rowincre;
+    char tform[20];
+    char message[81];
+    char snull[20];   /*  the FITS null value if reading from ASCII table  */
+
+    double cbuff[DBUFFSIZE / sizeof(double)]; /* align cbuff on word boundary */
+    void *buffer;
+
+    union u_tag {
+       char charval;
+       signed char scharval;
+    } u;
+
+    if (*status > 0 || nelem == 0)  /* inherit input status value if > 0 */
+        return(*status);
+
+    buffer = cbuff;
+
+    if (anynul)
+        *anynul = 0;
+
+    if (nultyp == 2)      
+       memset(nularray, 0, (size_t) nelem);   /* initialize nullarray */
+
+    /*---------------------------------------------------*/
+    /*  Check input and get parameters about the column: */
+    /*---------------------------------------------------*/
+    if (elemincre < 0)
+        readcheck = -1;  /* don't do range checking in this case */
+
+    ffgcprll( fptr, colnum, firstrow, firstelem, nelem, readcheck, &scale, &zero,
+         tform, &twidth, &tcode, &maxelem, &startpos, &elemnum, &incre,
+         &repeat, &rowlen, &hdutype, &tnull, snull, status);
+
+    /* special case: read column of T/F logicals */
+    if (tcode == TLOGICAL && elemincre == 1)
+    {
+        u.scharval = nulval;
+        ffgcll(fptr, colnum, firstrow, firstelem, nelem, nultyp,
+               u.charval, (char *) array, nularray, anynul, status);
+
+        return(*status);
+    }
+
+    if (strchr(tform,'A') != NULL) 
+    {
+        if (*status == BAD_ELEM_NUM)
+        {
+            /* ignore this error message */
+            *status = 0;
+            ffcmsg();   /* clear error stack */
+        }
+
+        /*  interpret a 'A' ASCII column as a 'B' byte column ('8A' == '8B') */
+        /*  This is an undocumented 'feature' in CFITSIO */
+
+        /*  we have to reset some of the values returned by ffgcpr */
+        
+        tcode = TBYTE;
+        incre = 1;         /* each element is 1 byte wide */
+        repeat = twidth;   /* total no. of chars in the col */
+        twidth = 1;        /* width of each element */
+        scale = 1.0;       /* no scaling */
+        zero  = 0.0;
+        tnull = NULL_UNDEFINED;  /* don't test for nulls */
+        maxelem = DBUFFSIZE;
+    }
+
+    if (*status > 0)
+        return(*status);
+        
+    incre *= elemincre;   /* multiply incre to just get every nth pixel */
+
+    if (tcode == TSTRING && hdutype == ASCII_TBL) /* setup for ASCII tables */
+    {
+      /* get the number of implied decimal places if no explicit decmal point */
+      ffasfm(tform, &xcode, &xwidth, &decimals, status); 
+      for(ii = 0; ii < decimals; ii++)
+        power *= 10.;
+    }
+    /*------------------------------------------------------------------*/
+    /*  Decide whether to check for null values in the input FITS file: */
+    /*------------------------------------------------------------------*/
+    nulcheck = nultyp; /* by default, check for null values in the FITS file */
+
+    if (nultyp == 1 && nulval == 0)
+       nulcheck = 0;    /* calling routine does not want to check for nulls */
+
+    else if (tcode%10 == 1 &&        /* if reading an integer column, and  */ 
+            tnull == NULL_UNDEFINED) /* if a null value is not defined,    */
+            nulcheck = 0;            /* then do not check for null values. */
+
+    else if (tcode == TSHORT && (tnull > SHRT_MAX || tnull < SHRT_MIN) )
+            nulcheck = 0;            /* Impossible null value */
+
+    else if (tcode == TBYTE && (tnull > 255 || tnull < 0) )
+            nulcheck = 0;            /* Impossible null value */
+
+    else if (tcode == TSTRING && snull[0] == ASCII_NULL_UNDEFINED)
+         nulcheck = 0;
+
+    /*---------------------------------------------------------------------*/
+    /*  Now read the pixels from the FITS column. If the column does not   */
+    /*  have the same datatype as the output array, then we have to read   */
+    /*  the raw values into a temporary buffer (of limited size).  In      */
+    /*  the case of a vector colum read only 1 vector of values at a time  */
+    /*  then skip to the next row if more values need to be read.          */
+    /*  After reading the raw values, then call the fffXXYY routine to (1) */
+    /*  test for undefined values, (2) convert the datatype if necessary,  */
+    /*  and (3) scale the values by the FITS TSCALn and TZEROn linear      */
+    /*  scaling parameters.                                                */
+    /*---------------------------------------------------------------------*/
+    remain = nelem;           /* remaining number of values to read */
+    next = 0;                 /* next element in array to be read   */
+    rownum = 0;               /* row number, relative to firstrow   */
+
+    while (remain)
+    {
+        /* limit the number of pixels to read at one time to the number that
+           will fit in the buffer or to the number of pixels that remain in
+           the current vector, which ever is smaller.
+        */
+        ntodo = (long) minvalue(remain, maxelem);
+        if (elemincre >= 0)
+        {
+          ntodo = (long) minvalue(ntodo, ((repeat - elemnum - 1)/elemincre +1));
+        }
+        else
+        {
+          ntodo = (long) minvalue(ntodo, (elemnum/(-elemincre) +1));
+        }
+
+        readptr = startpos + (rownum * rowlen) + (elemnum * (incre / elemincre));
+
+        switch (tcode) 
+        {
+            case (TBYTE):
+                ffgi1b(fptr, readptr, ntodo, incre, (unsigned char *) &array[next], status);
+                fffi1s1((unsigned char *)&array[next], ntodo, scale, zero,
+                        nulcheck, (unsigned char) tnull, nulval, &nularray[next], 
+                        anynul, &array[next], status);
+                break;
+            case (TSHORT):
+                ffgi2b(fptr, readptr, ntodo, incre, (short *) buffer, status);
+                fffi2s1((short  *) buffer, ntodo, scale, zero, nulcheck, 
+                       (short) tnull, nulval, &nularray[next], anynul, 
+                       &array[next], status);
+                break;
+            case (TLONG):
+                ffgi4b(fptr, readptr, ntodo, incre, (INT32BIT *) buffer,
+                       status);
+                fffi4s1((INT32BIT *) buffer, ntodo, scale, zero, nulcheck, 
+                       (INT32BIT) tnull, nulval, &nularray[next], anynul, 
+                       &array[next], status);
+                break;
+            case (TLONGLONG):
+                ffgi8b(fptr, readptr, ntodo, incre, (long *) buffer, status);
+                fffi8s1( (LONGLONG *) buffer, ntodo, scale, zero, 
+                           nulcheck, tnull, nulval, &nularray[next], 
+                            anynul, &array[next], status);
+                break;
+            case (TFLOAT):
+                ffgr4b(fptr, readptr, ntodo, incre, (float  *) buffer, status);
+                fffr4s1((float  *) buffer, ntodo, scale, zero, nulcheck, 
+                       nulval, &nularray[next], anynul, 
+                       &array[next], status);
+                break;
+            case (TDOUBLE):
+                ffgr8b(fptr, readptr, ntodo, incre, (double *) buffer, status);
+                fffr8s1((double *) buffer, ntodo, scale, zero, nulcheck, 
+                          nulval, &nularray[next], anynul, 
+                          &array[next], status);
+                break;
+            case (TSTRING):
+                ffmbyt(fptr, readptr, REPORT_EOF, status);
+       
+                if (incre == twidth)    /* contiguous bytes */
+                     ffgbyt(fptr, ntodo * twidth, buffer, status);
+                else
+                     ffgbytoff(fptr, twidth, ntodo, incre - twidth, buffer,
+                               status);
+
+                /* interpret the string as an ASCII formated number */
+                fffstrs1((char *) buffer, ntodo, scale, zero, twidth, power,
+                      nulcheck, snull, nulval, &nularray[next], anynul,
+                      &array[next], status);
+                break;
+
+            default:  /*  error trap for invalid column format */
+                sprintf(message, 
+                   "Cannot read bytes from column %d which has format %s",
+                    colnum, tform);
+                ffpmsg(message);
+                if (hdutype == ASCII_TBL)
+                    return(*status = BAD_ATABLE_FORMAT);
+                else
+                    return(*status = BAD_BTABLE_FORMAT);
+
+        } /* End of switch block */
+
+        /*-------------------------*/
+        /*  Check for fatal error  */
+        /*-------------------------*/
+        if (*status > 0)  /* test for error during previous read operation */
+        {
+	  dtemp = (double) next;
+          if (hdutype > 0)
+            sprintf(message,
+            "Error reading elements %.0f thru %.0f from column %d (ffgclsb).",
+              dtemp+1., dtemp+ntodo, colnum);
+          else
+            sprintf(message,
+            "Error reading elements %.0f thru %.0f from image (ffgclsb).",
+              dtemp+1., dtemp+ntodo);
+
+         ffpmsg(message);
+         return(*status);
+        }
+
+        /*--------------------------------------------*/
+        /*  increment the counters for the next loop  */
+        /*--------------------------------------------*/
+        remain -= ntodo;
+        if (remain)
+        {
+            next += ntodo;
+            elemnum = elemnum + (ntodo * elemincre);
+
+            if (elemnum >= repeat)  /* completed a row; start on later row */
+            {
+                rowincre = elemnum / repeat;
+                rownum += rowincre;
+                elemnum = elemnum - (rowincre * repeat);
+            }
+            else if (elemnum < 0)  /* completed a row; start on a previous row */
+            {
+                rowincre = (-elemnum - 1) / repeat + 1;
+                rownum -= rowincre;
+                elemnum = (rowincre * repeat) + elemnum;
+            }
+        }
+    }  /*  End of main while Loop  */
+
+
+    /*--------------------------------*/
+    /*  check for numerical overflow  */
+    /*--------------------------------*/
+    if (*status == OVERFLOW_ERR)
+    {
+        ffpmsg(
+        "Numerical overflow during type conversion while reading FITS data.");
+        *status = NUM_OVERFLOW;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi1s1(unsigned char *input, /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            unsigned char tnull,  /* I - value of FITS TNULLn keyword if any */
+            signed char nullval,  /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            signed char *output,  /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == -128.)
+        {
+            /* Instead of subtracting 128, it is more efficient */
+            /* to just flip the sign bit with the XOR operator */
+
+            for (ii = 0; ii < ntodo; ii++)
+                 output[ii] =  ( *(signed char *) &input[ii] ) ^ 0x80;
+        }
+        else if (scale == 1. && zero == 0.)      /* no scaling */
+        { 
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] > 127)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 127;
+                }
+                else
+                    output[ii] = (signed char) input[ii]; /* copy input */
+            }
+        }
+        else             /* must scale the data */
+        {                
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DSCHAR_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = -128;
+                }
+                else if (dvalue > DSCHAR_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 127;
+                }
+                else
+                    output[ii] = (signed char) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == -128.)
+        {
+            /* Instead of subtracting 128, it is more efficient */
+            /* to just flip the sign bit with the XOR operator */
+
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                    output[ii] =  ( *(signed char *) &input[ii] ) ^ 0x80;
+            }
+        }
+        else if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                    output[ii] = (signed char) input[ii];
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DSCHAR_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = -128;
+                    }
+                    else if (dvalue > DSCHAR_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 127;
+                    }
+                    else
+                        output[ii] = (signed char) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi2s1(short *input,         /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            short tnull,          /* I - value of FITS TNULLn keyword if any */
+            signed char nullval,  /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            signed char *output,  /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] < -128)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = -128;
+                }
+                else if (input[ii] > 127)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 127;
+                }
+                else
+                    output[ii] = (signed char) input[ii];
+            }
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DSCHAR_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = -128;
+                }
+                else if (dvalue > DSCHAR_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 127;
+                }
+                else
+                    output[ii] = (signed char) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+
+                else
+                {
+                    if (input[ii] < -128)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = -128;
+                    }
+                    else if (input[ii] > 127)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 127;
+                    }
+                    else
+                        output[ii] = (signed char) input[ii];
+                }
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DSCHAR_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = -128;
+                    }
+                    else if (dvalue > DSCHAR_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 127;
+                    }
+                    else
+                        output[ii] = (signed char) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi4s1(INT32BIT *input,      /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            INT32BIT tnull,       /* I - value of FITS TNULLn keyword if any */
+            signed char nullval,  /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            signed char *output,  /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] < -128)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = -128;
+                }
+                else if (input[ii] > 127)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 127;
+                }
+                else
+                    output[ii] = (signed char) input[ii];
+            }
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DSCHAR_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = -128;
+                }
+                else if (dvalue > DSCHAR_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 127;
+                }
+                else
+                    output[ii] = (signed char) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    if (input[ii] < -128)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = -128;
+                    }
+                    else if (input[ii] > 127)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 127;
+                    }
+                    else
+                        output[ii] = (signed char) input[ii];
+                }
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DSCHAR_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = -128;
+                    }
+                    else if (dvalue > DSCHAR_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 127;
+                    }
+                    else
+                        output[ii] = (signed char) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi8s1(LONGLONG *input,      /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            LONGLONG tnull,       /* I - value of FITS TNULLn keyword if any */
+            signed char nullval,  /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            signed char *output,  /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] < -128)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = -128;
+                }
+                else if (input[ii] > 127)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 127;
+                }
+                else
+                    output[ii] = (signed char) input[ii];
+            }
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DSCHAR_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = -128;
+                }
+                else if (dvalue > DSCHAR_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 127;
+                }
+                else
+                    output[ii] = (signed char) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    if (input[ii] < -128)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = -128;
+                    }
+                    else if (input[ii] > 127)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 127;
+                    }
+                    else
+                        output[ii] = (signed char) input[ii];
+                }
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DSCHAR_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = -128;
+                    }
+                    else if (dvalue > DSCHAR_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 127;
+                    }
+                    else
+                        output[ii] = (signed char) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffr4s1(float *input,         /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            signed char nullval,  /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            signed char *output,  /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to NaN.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+    short *sptr, iret;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] < DSCHAR_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = -128;
+                }
+                else if (input[ii] > DSCHAR_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 127;
+                }
+                else
+                    output[ii] = (signed char) input[ii];
+            }
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DSCHAR_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = -128;
+                }
+                else if (dvalue > DSCHAR_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 127;
+                }
+                else
+                    output[ii] = (signed char) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        sptr = (short *) input;
+
+#if BYTESWAPPED && MACHINE != VAXVMS && MACHINE != ALPHAVMS
+        sptr++;       /* point to MSBs */
+#endif
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++, sptr += 2)
+            {
+              /* use redundant boolean logic in following statement */
+              /* to suppress irritating Borland compiler warning message */
+              if (0 != (iret = fnan(*sptr) ) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                  }
+                  else            /* it's an underflow */
+                     output[ii] = 0;
+              }
+              else
+                {
+                    if (input[ii] < DSCHAR_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = -128;
+                    }
+                    else if (input[ii] > DSCHAR_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 127;
+                    }
+                    else
+                        output[ii] = (signed char) input[ii];
+                }
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++, sptr += 2)
+            {
+              if (0 != (iret = fnan(*sptr) ) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {  
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                  }
+                  else            /* it's an underflow */
+                  {
+                    if (zero < DSCHAR_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = -128;
+                    }
+                    else if (zero > DSCHAR_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 127;
+                    }
+                    else
+                        output[ii] = (signed char) zero;
+                  }
+              }
+              else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DSCHAR_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = -128;
+                    }
+                    else if (dvalue > DSCHAR_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 127;
+                    }
+                    else
+                        output[ii] = (signed char) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffr8s1(double *input,        /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            signed char nullval,  /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            signed char *output,  /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to NaN.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+    short *sptr, iret;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] < DSCHAR_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = -128;
+                }
+                else if (input[ii] > DSCHAR_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 127;
+                }
+                else
+                    output[ii] = (signed char) input[ii];
+            }
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DSCHAR_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = -128;
+                }
+                else if (dvalue > DSCHAR_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 127;
+                }
+                else
+                    output[ii] = (signed char) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        sptr = (short *) input;
+
+#if BYTESWAPPED && MACHINE != VAXVMS && MACHINE != ALPHAVMS
+        sptr += 3;       /* point to MSBs */
+#endif
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++, sptr += 4)
+            {
+              if (0 != (iret = dnan(*sptr)) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                  }
+                  else            /* it's an underflow */
+                     output[ii] = 0;
+              }
+              else
+                {
+                    if (input[ii] < DSCHAR_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = -128;
+                    }
+                    else if (input[ii] > DSCHAR_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 127;
+                    }
+                    else
+                        output[ii] = (signed char) input[ii];
+                }
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++, sptr += 4)
+            {
+              if (0 != (iret = dnan(*sptr)) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {  
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                  }
+                  else            /* it's an underflow */
+                  {
+                    if (zero < DSCHAR_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = -128;
+                    }
+                    else if (zero > DSCHAR_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 127;
+                    }
+                    else
+                        output[ii] = (signed char) zero;
+                  }
+              }
+              else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DSCHAR_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = -128;
+                    }
+                    else if (dvalue > DSCHAR_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 127;
+                    }
+                    else
+                        output[ii] = (signed char) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffstrs1(char *input,         /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            long twidth,          /* I - width of each substring of chars    */
+            double implipower,    /* I - power of 10 of implied decimal      */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            char  *snull,         /* I - value of FITS null string, if any   */
+            signed char nullval,  /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            signed char *output,  /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file. Check
+  for null values and do scaling if required. The nullcheck code value
+  determines how any null values in the input array are treated. A null
+  value is an input pixel that is equal to snull.  If nullcheck= 0, then
+  no special checking for nulls is performed.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    int  nullen;
+    long ii;
+    double dvalue;
+    char *cstring, message[81];
+    char *cptr, *tpos;
+    char tempstore, chrzero = '0';
+    double val, power;
+    int exponent, sign, esign, decpt;
+
+    nullen = strlen(snull);
+    cptr = input;  /* pointer to start of input string */
+    for (ii = 0; ii < ntodo; ii++)
+    {
+      cstring = cptr;
+      /* temporarily insert a null terminator at end of the string */
+      tpos = cptr + twidth;
+      tempstore = *tpos;
+      *tpos = 0;
+
+      /* check if null value is defined, and if the    */
+      /* column string is identical to the null string */
+      if (snull[0] != ASCII_NULL_UNDEFINED && 
+         !strncmp(snull, cptr, nullen) )
+      {
+        if (nullcheck)  
+        {
+          *anynull = 1;    
+          if (nullcheck == 1)
+            output[ii] = nullval;
+          else
+            nullarray[ii] = 1;
+        }
+        cptr += twidth;
+      }
+      else
+      {
+        /* value is not the null value, so decode it */
+        /* remove any embedded blank characters from the string */
+
+        decpt = 0;
+        sign = 1;
+        val  = 0.;
+        power = 1.;
+        exponent = 0;
+        esign = 1;
+
+        while (*cptr == ' ')               /* skip leading blanks */
+           cptr++;
+
+        if (*cptr == '-' || *cptr == '+')  /* check for leading sign */
+        {
+          if (*cptr == '-')
+             sign = -1;
+
+          cptr++;
+
+          while (*cptr == ' ')         /* skip blanks between sign and value */
+            cptr++;
+        }
+
+        while (*cptr >= '0' && *cptr <= '9')
+        {
+          val = val * 10. + *cptr - chrzero;  /* accumulate the value */
+          cptr++;
+
+          while (*cptr == ' ')         /* skip embedded blanks in the value */
+            cptr++;
+        }
+
+        if (*cptr == '.' || *cptr == ',')       /* check for decimal point */
+        {
+          decpt = 1;
+          cptr++;
+          while (*cptr == ' ')         /* skip any blanks */
+            cptr++;
+
+          while (*cptr >= '0' && *cptr <= '9')
+          {
+            val = val * 10. + *cptr - chrzero;  /* accumulate the value */
+            power = power * 10.;
+            cptr++;
+
+            while (*cptr == ' ')         /* skip embedded blanks in the value */
+              cptr++;
+          }
+        }
+
+        if (*cptr == 'E' || *cptr == 'D')  /* check for exponent */
+        {
+          cptr++;
+          while (*cptr == ' ')         /* skip blanks */
+              cptr++;
+  
+          if (*cptr == '-' || *cptr == '+')  /* check for exponent sign */
+          {
+            if (*cptr == '-')
+               esign = -1;
+
+            cptr++;
+
+            while (*cptr == ' ')        /* skip blanks between sign and exp */
+              cptr++;
+          }
+
+          while (*cptr >= '0' && *cptr <= '9')
+          {
+            exponent = exponent * 10 + *cptr - chrzero;  /* accumulate exp */
+            cptr++;
+
+            while (*cptr == ' ')         /* skip embedded blanks */
+              cptr++;
+          }
+        }
+
+        if (*cptr  != 0)  /* should end up at the null terminator */
+        {
+          sprintf(message, "Cannot read number from ASCII table");
+          ffpmsg(message);
+          sprintf(message, "Column field = %s.", cstring);
+          ffpmsg(message);
+          /* restore the char that was overwritten by the null */
+          *tpos = tempstore;
+          return(*status = BAD_C2D);
+        }
+
+        if (!decpt)  /* if no explicit decimal, use implied */
+           power = implipower;
+
+        dvalue = (sign * val / power) * pow(10., (double) (esign * exponent));
+
+        dvalue = dvalue * scale + zero;   /* apply the scaling */
+
+        if (dvalue < DSCHAR_MIN)
+        {
+            *status = OVERFLOW_ERR;
+            output[ii] = -128;
+        }
+        else if (dvalue > DSCHAR_MAX)
+        {
+            *status = OVERFLOW_ERR;
+            output[ii] = 127;
+        }
+        else
+            output[ii] = (signed char) dvalue;
+      }
+      /* restore the char that was overwritten by the null */
+      *tpos = tempstore;
+    }
+    return(*status);
+}
diff --git a/src/plugins/cfitsio/getcolui.c b/src/plugins/cfitsio/getcolui.c
new file mode 100644
index 0000000..6de4efc
--- /dev/null
+++ b/src/plugins/cfitsio/getcolui.c
@@ -0,0 +1,1907 @@
+/*  This file, getcolui.c, contains routines that read data elements from   */
+/*  a FITS image or table, with unsigned short datatype.                    */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+
+#include <math.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <string.h>
+#include "fitsio2.h"
+
+/*--------------------------------------------------------------------------*/
+int ffgpvui( fitsfile *fptr,   /* I - FITS file pointer                       */
+            long  group,      /* I - group to read (1 = 1st group)           */
+            LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+            LONGLONG  nelem,      /* I - number of values to read                */
+   unsigned short nulval,     /* I - value for undefined pixels              */
+   unsigned short *array,     /* O - array of values that are returned       */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+  Undefined elements will be set equal to NULVAL, unless NULVAL=0
+  in which case no checking for undefined values will be performed.
+  ANYNUL is returned with a value of .true. if any pixels are undefined.
+*/
+{
+    long row;
+    char cdummy;
+    int nullcheck = 1;
+    unsigned short nullvalue;
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+         nullvalue = nulval;  /* set local variable */
+
+        fits_read_compressed_pixels(fptr, TUSHORT, firstelem, nelem,
+            nullcheck, &nullvalue, array, NULL, anynul, status);
+        return(*status);
+    }
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffgclui(fptr, 2, row, firstelem, nelem, 1, 1, nulval,
+               array, &cdummy, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgpfui( fitsfile *fptr,   /* I - FITS file pointer                       */
+            long  group,      /* I - group to read (1 = 1st group)           */
+            LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+            LONGLONG  nelem,      /* I - number of values to read                */
+   unsigned short *array,     /* O - array of values that are returned       */
+            char *nularray,   /* O - array of null pixel flags               */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+  Any undefined pixels in the returned array will be set = 0 and the 
+  corresponding nularray value will be set = 1.
+  ANYNUL is returned with a value of .true. if any pixels are undefined.
+*/
+{
+    long row;
+    int nullcheck = 2;
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        fits_read_compressed_pixels(fptr, TUSHORT, firstelem, nelem,
+            nullcheck, NULL, array, nularray, anynul, status);
+        return(*status);
+    }
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffgclui(fptr, 2, row, firstelem, nelem, 1, 2, 0,
+               array, nularray, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffg2dui(fitsfile *fptr,  /* I - FITS file pointer                       */
+           long  group,     /* I - group to read (1 = 1st group)           */
+  unsigned short nulval,    /* set undefined pixels equal to this          */
+           LONGLONG  ncols,     /* I - number of pixels in each row of array   */
+           LONGLONG  naxis1,    /* I - FITS image NAXIS1 value                 */
+           LONGLONG  naxis2,    /* I - FITS image NAXIS2 value                 */
+  unsigned short *array,    /* O - array to be filled and returned         */
+           int  *anynul,    /* O - set to 1 if any values are null; else 0 */
+           int  *status)    /* IO - error status                           */
+/*
+  Read an entire 2-D array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being read).  Any null
+  values in the array will be set equal to the value of nulval, unless
+  nulval = 0 in which case no null checking will be performed.
+*/
+{
+    /* call the 3D reading routine, with the 3rd dimension = 1 */
+
+    ffg3dui(fptr, group, nulval, ncols, naxis2, naxis1, naxis2, 1, array, 
+           anynul, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffg3dui(fitsfile *fptr,  /* I - FITS file pointer                       */
+           long  group,     /* I - group to read (1 = 1st group)           */
+  unsigned short nulval,    /* set undefined pixels equal to this          */
+           LONGLONG  ncols,     /* I - number of pixels in each row of array   */
+           LONGLONG  nrows,     /* I - number of rows in each plane of array   */
+           LONGLONG  naxis1,    /* I - FITS image NAXIS1 value                 */
+           LONGLONG  naxis2,    /* I - FITS image NAXIS2 value                 */
+           LONGLONG  naxis3,    /* I - FITS image NAXIS3 value                 */
+  unsigned short *array,    /* O - array to be filled and returned         */
+           int  *anynul,    /* O - set to 1 if any values are null; else 0 */
+           int  *status)    /* IO - error status                           */
+/*
+  Read an entire 3-D array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being read).  Any null
+  values in the array will be set equal to the value of nulval, unless
+  nulval = 0 in which case no null checking will be performed.
+*/
+{
+    long tablerow, ii, jj;
+    char cdummy;
+    int nullcheck = 1;
+    long inc[] = {1,1,1};
+    LONGLONG fpixel[] = {1,1,1}, nfits, narray;
+    LONGLONG lpixel[3];
+    unsigned short nullvalue;
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        lpixel[0] = ncols;
+        lpixel[1] = nrows;
+        lpixel[2] = naxis3;
+        nullvalue = nulval;  /* set local variable */
+
+        fits_read_compressed_img(fptr, TUSHORT, fpixel, lpixel, inc,
+            nullcheck, &nullvalue, array, NULL, anynul, status);
+        return(*status);
+    }
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+    tablerow=maxvalue(1,group);
+
+    if (ncols == naxis1 && nrows == naxis2)  /* arrays have same size? */
+    {
+       /* all the image pixels are contiguous, so read all at once */
+       ffgclui(fptr, 2, tablerow, 1, naxis1 * naxis2 * naxis3, 1, 1, nulval,
+               array, &cdummy, anynul, status);
+       return(*status);
+    }
+
+    if (ncols < naxis1 || nrows < naxis2)
+       return(*status = BAD_DIMEN);
+
+    nfits = 1;   /* next pixel in FITS image to read */
+    narray = 0;  /* next pixel in output array to be filled */
+
+    /* loop over naxis3 planes in the data cube */
+    for (jj = 0; jj < naxis3; jj++)
+    {
+      /* loop over the naxis2 rows in the FITS image, */
+      /* reading naxis1 pixels to each row            */
+
+      for (ii = 0; ii < naxis2; ii++)
+      {
+       if (ffgclui(fptr, 2, tablerow, nfits, naxis1, 1, 1, nulval,
+          &array[narray], &cdummy, anynul, status) > 0)
+          return(*status);
+
+       nfits += naxis1;
+       narray += ncols;
+      }
+      narray += (nrows - naxis2) * ncols;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgsvui(fitsfile *fptr, /* I - FITS file pointer                         */
+           int  colnum,    /* I - number of the column to read (1 = 1st)    */
+           int naxis,      /* I - number of dimensions in the FITS array    */
+           long  *naxes,   /* I - size of each dimension                    */
+           long  *blc,     /* I - 'bottom left corner' of the subsection    */
+           long  *trc,     /* I - 'top right corner' of the subsection      */
+           long  *inc,     /* I - increment to be applied in each dimension */
+  unsigned short nulval,   /* I - value to set undefined pixels             */
+  unsigned short *array,   /* O - array to be filled and returned           */
+           int  *anynul,   /* O - set to 1 if any values are null; else 0   */
+           int  *status)   /* IO - error status                             */
+/*
+  Read a subsection of data values from an image or a table column.
+  This routine is set up to handle a maximum of nine dimensions.
+*/
+{
+    long ii,i0, i1,i2,i3,i4,i5,i6,i7,i8,row,rstr,rstp,rinc;
+    long str[9],stp[9],incr[9];
+    long nelem, nultyp, ninc, numcol;
+    LONGLONG felem, dsize[10], blcll[9], trcll[9];
+    int hdutype, anyf;
+    char ldummy, msg[FLEN_ERRMSG];
+    int nullcheck = 1;
+    unsigned short nullvalue;
+
+    if (naxis < 1 || naxis > 9)
+    {
+        sprintf(msg, "NAXIS = %d in call to ffgsvui is out of range", naxis);
+        ffpmsg(msg);
+        return(*status = BAD_DIMEN);
+    }
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        for (ii=0; ii < naxis; ii++) {
+	    blcll[ii] = blc[ii];
+	    trcll[ii] = trc[ii];
+	}
+
+        nullvalue = nulval;  /* set local variable */
+
+        fits_read_compressed_img(fptr, TUSHORT, blcll, trcll, inc,
+            nullcheck, &nullvalue, array, NULL, anynul, status);
+        return(*status);
+    }
+
+/*
+    if this is a primary array, then the input COLNUM parameter should
+    be interpreted as the row number, and we will alway read the image
+    data from column 2 (any group parameters are in column 1).
+*/
+    if (ffghdt(fptr, &hdutype, status) > 0)
+        return(*status);
+
+    if (hdutype == IMAGE_HDU)
+    {
+        /* this is a primary array, or image extension */
+        if (colnum == 0)
+        {
+            rstr = 1;
+            rstp = 1;
+        }
+        else
+        {
+            rstr = colnum;
+            rstp = colnum;
+        }
+        rinc = 1;
+        numcol = 2;
+    }
+    else
+    {
+        /* this is a table, so the row info is in the (naxis+1) elements */
+        rstr = blc[naxis];
+        rstp = trc[naxis];
+        rinc = inc[naxis];
+        numcol = colnum;
+    }
+
+    nultyp = 1;
+    if (anynul)
+        *anynul = FALSE;
+
+    i0 = 0;
+    for (ii = 0; ii < 9; ii++)
+    {
+        str[ii] = 1;
+        stp[ii] = 1;
+        incr[ii] = 1;
+        dsize[ii] = 1;
+    }
+
+    for (ii = 0; ii < naxis; ii++)
+    {
+      if (trc[ii] < blc[ii])
+      {
+        sprintf(msg, "ffgsvui: illegal range specified for axis %ld", ii + 1);
+        ffpmsg(msg);
+        return(*status = BAD_PIX_NUM);
+      }
+
+      str[ii] = blc[ii];
+      stp[ii] = trc[ii];
+      incr[ii] = inc[ii];
+      dsize[ii + 1] = dsize[ii] * naxes[ii];
+    }
+
+    if (naxis == 1 && naxes[0] == 1)
+    {
+      /* This is not a vector column, so read all the rows at once */
+      nelem = (rstp - rstr) / rinc + 1;
+      ninc = rinc;
+      rstp = rstr;
+    }
+    else
+    {
+      /* have to read each row individually, in all dimensions */
+      nelem = (stp[0] - str[0]) / inc[0] + 1;
+      ninc = incr[0];
+    }
+
+    for (row = rstr; row <= rstp; row += rinc)
+    {
+     for (i8 = str[8]; i8 <= stp[8]; i8 += incr[8])
+     {
+      for (i7 = str[7]; i7 <= stp[7]; i7 += incr[7])
+      {
+       for (i6 = str[6]; i6 <= stp[6]; i6 += incr[6])
+       {
+        for (i5 = str[5]; i5 <= stp[5]; i5 += incr[5])
+        {
+         for (i4 = str[4]; i4 <= stp[4]; i4 += incr[4])
+         {
+          for (i3 = str[3]; i3 <= stp[3]; i3 += incr[3])
+          {
+           for (i2 = str[2]; i2 <= stp[2]; i2 += incr[2])
+           {
+            for (i1 = str[1]; i1 <= stp[1]; i1 += incr[1])
+            {
+              felem=str[0] + (i1 - 1) * dsize[1] + (i2 - 1) * dsize[2] + 
+                             (i3 - 1) * dsize[3] + (i4 - 1) * dsize[4] +
+                             (i5 - 1) * dsize[5] + (i6 - 1) * dsize[6] +
+                             (i7 - 1) * dsize[7] + (i8 - 1) * dsize[8];
+              if ( ffgclui(fptr, numcol, row, felem, nelem, ninc, nultyp,
+                   nulval, &array[i0], &ldummy, &anyf, status) > 0)
+                   return(*status);
+
+              if (anyf && anynul)
+                  *anynul = TRUE;
+
+              i0 += nelem;
+            }
+           }
+          }
+         }
+        }
+       }
+      }
+     }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgsfui(fitsfile *fptr, /* I - FITS file pointer                         */
+           int  colnum,    /* I - number of the column to read (1 = 1st)    */
+           int naxis,      /* I - number of dimensions in the FITS array    */
+           long  *naxes,   /* I - size of each dimension                    */
+           long  *blc,     /* I - 'bottom left corner' of the subsection    */
+           long  *trc,     /* I - 'top right corner' of the subsection      */
+           long  *inc,     /* I - increment to be applied in each dimension */
+  unsigned short *array,   /* O - array to be filled and returned           */
+           char *flagval,  /* O - set to 1 if corresponding value is null   */
+           int  *anynul,   /* O - set to 1 if any values are null; else 0   */
+           int  *status)   /* IO - error status                             */
+/*
+  Read a subsection of data values from an image or a table column.
+  This routine is set up to handle a maximum of nine dimensions.
+*/
+{
+    long ii,i0, i1,i2,i3,i4,i5,i6,i7,i8,row,rstr,rstp,rinc;
+    long str[9],stp[9],incr[9],dsize[10];
+    LONGLONG blcll[9], trcll[9];
+    long felem, nelem, nultyp, ninc, numcol;
+    int hdutype, anyf;
+    unsigned short nulval = 0;
+    char msg[FLEN_ERRMSG];
+    int nullcheck = 2;
+
+    if (naxis < 1 || naxis > 9)
+    {
+        sprintf(msg, "NAXIS = %d in call to ffgsvi is out of range", naxis);
+        ffpmsg(msg);
+        return(*status = BAD_DIMEN);
+    }
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        for (ii=0; ii < naxis; ii++) {
+	    blcll[ii] = blc[ii];
+	    trcll[ii] = trc[ii];
+	}
+
+        fits_read_compressed_img(fptr, TUSHORT, blcll, trcll, inc,
+            nullcheck, NULL, array, flagval, anynul, status);
+        return(*status);
+    }
+
+/*
+    if this is a primary array, then the input COLNUM parameter should
+    be interpreted as the row number, and we will alway read the image
+    data from column 2 (any group parameters are in column 1).
+*/
+    if (ffghdt(fptr, &hdutype, status) > 0)
+        return(*status);
+
+    if (hdutype == IMAGE_HDU)
+    {
+        /* this is a primary array, or image extension */
+        if (colnum == 0)
+        {
+            rstr = 1;
+            rstp = 1;
+        }
+        else
+        {
+            rstr = colnum;
+            rstp = colnum;
+        }
+        rinc = 1;
+        numcol = 2;
+    }
+    else
+    {
+        /* this is a table, so the row info is in the (naxis+1) elements */
+        rstr = blc[naxis];
+        rstp = trc[naxis];
+        rinc = inc[naxis];
+        numcol = colnum;
+    }
+
+    nultyp = 2;
+    if (anynul)
+        *anynul = FALSE;
+
+    i0 = 0;
+    for (ii = 0; ii < 9; ii++)
+    {
+        str[ii] = 1;
+        stp[ii] = 1;
+        incr[ii] = 1;
+        dsize[ii] = 1;
+    }
+
+    for (ii = 0; ii < naxis; ii++)
+    {
+      if (trc[ii] < blc[ii])
+      {
+        sprintf(msg, "ffgsvi: illegal range specified for axis %ld", ii + 1);
+        ffpmsg(msg);
+        return(*status = BAD_PIX_NUM);
+      }
+
+      str[ii] = blc[ii];
+      stp[ii] = trc[ii];
+      incr[ii] = inc[ii];
+      dsize[ii + 1] = dsize[ii] * naxes[ii];
+    }
+
+    if (naxis == 1 && naxes[0] == 1)
+    {
+      /* This is not a vector column, so read all the rows at once */
+      nelem = (rstp - rstr) / rinc + 1;
+      ninc = rinc;
+      rstp = rstr;
+    }
+    else
+    {
+      /* have to read each row individually, in all dimensions */
+      nelem = (stp[0] - str[0]) / inc[0] + 1;
+      ninc = incr[0];
+    }
+
+    for (row = rstr; row <= rstp; row += rinc)
+    {
+     for (i8 = str[8]; i8 <= stp[8]; i8 += incr[8])
+     {
+      for (i7 = str[7]; i7 <= stp[7]; i7 += incr[7])
+      {
+       for (i6 = str[6]; i6 <= stp[6]; i6 += incr[6])
+       {
+        for (i5 = str[5]; i5 <= stp[5]; i5 += incr[5])
+        {
+         for (i4 = str[4]; i4 <= stp[4]; i4 += incr[4])
+         {
+          for (i3 = str[3]; i3 <= stp[3]; i3 += incr[3])
+          {
+           for (i2 = str[2]; i2 <= stp[2]; i2 += incr[2])
+           {
+            for (i1 = str[1]; i1 <= stp[1]; i1 += incr[1])
+            {
+              felem=str[0] + (i1 - 1) * dsize[1] + (i2 - 1) * dsize[2] + 
+                             (i3 - 1) * dsize[3] + (i4 - 1) * dsize[4] +
+                             (i5 - 1) * dsize[5] + (i6 - 1) * dsize[6] +
+                             (i7 - 1) * dsize[7] + (i8 - 1) * dsize[8];
+
+              if ( ffgclui(fptr, numcol, row, felem, nelem, ninc, nultyp,
+                   nulval, &array[i0], &flagval[i0], &anyf, status) > 0)
+                   return(*status);
+
+              if (anyf && anynul)
+                  *anynul = TRUE;
+
+              i0 += nelem;
+            }
+           }
+          }
+         }
+        }
+       }
+      }
+     }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffggpui( fitsfile *fptr,   /* I - FITS file pointer                       */
+            long  group,      /* I - group to read (1 = 1st group)           */
+            long  firstelem,  /* I - first vector element to read (1 = 1st)  */
+            long  nelem,      /* I - number of values to read                */
+   unsigned short *array,     /* O - array of values that are returned       */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of group parameters from the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+*/
+{
+    long row;
+    int idummy;
+    char cdummy;
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffgclui(fptr, 1, row, firstelem, nelem, 1, 1, 0,
+               array, &cdummy, &idummy, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcvui(fitsfile *fptr,   /* I - FITS file pointer                       */
+           int  colnum,      /* I - number of column to read (1 = 1st col)  */
+           LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+           LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+           LONGLONG  nelem,      /* I - number of values to read                */
+  unsigned short nulval,     /* I - value for null pixels                   */
+  unsigned short *array,     /* O - array of values that are read           */
+           int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+           int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from a column in the current FITS HDU. Automatic
+  datatype conversion will be performed if the datatype of the column does not
+  match the datatype of the array parameter. The output values will be scaled 
+  by the FITS TSCALn and TZEROn values if these values have been defined.
+  Any undefined pixels will be set equal to the value of 'nulval' unless
+  nulval = 0 in which case no checks for undefined pixels will be made.
+*/
+{
+    char cdummy;
+
+    ffgclui(fptr, colnum, firstrow, firstelem, nelem, 1, 1, nulval,
+           array, &cdummy, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcfui(fitsfile *fptr,   /* I - FITS file pointer                       */
+           int  colnum,      /* I - number of column to read (1 = 1st col)  */
+           LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+           LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+           LONGLONG  nelem,      /* I - number of values to read                */
+  unsigned short *array,     /* O - array of values that are read           */
+           char *nularray,   /* O - array of flags: 1 if null pixel; else 0 */
+           int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+           int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from a column in the current FITS HDU. Automatic
+  datatype conversion will be performed if the datatype of the column does not
+  match the datatype of the array parameter. The output values will be scaled 
+  by the FITS TSCALn and TZEROn values if these values have been defined.
+  Nularray will be set = 1 if the corresponding array pixel is undefined, 
+  otherwise nularray will = 0.
+*/
+{
+    unsigned short dummy = 0;
+
+    ffgclui(fptr, colnum, firstrow, firstelem, nelem, 1, 2, dummy,
+           array, nularray, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgclui( fitsfile *fptr,   /* I - FITS file pointer                       */
+            int  colnum,      /* I - number of column to read (1 = 1st col)  */
+            LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+            LONGLONG firstelem,  /* I - first vector element to read (1 = 1st)  */
+            LONGLONG  nelem,      /* I - number of values to read                */
+            long  elemincre,  /* I - pixel increment; e.g., 2 = every other  */
+            int   nultyp,     /* I - null value handling code:               */
+                              /*     1: set undefined pixels = nulval        */
+                              /*     2: set nularray=1 for undefined pixels  */
+   unsigned short nulval,     /* I - value for null pixels if nultyp = 1     */
+   unsigned short *array,     /* O - array of values that are read           */
+            char *nularray,   /* O - array of flags = 1 if nultyp = 2        */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from a column in the current FITS HDU.
+  The column number may refer to a real column in an ASCII or binary table, 
+  or it may refer be a virtual column in a 1 or more grouped FITS primary
+  array or image extension.  FITSIO treats a primary array as a binary table
+  with 2 vector columns: the first column contains the group parameters (often
+  with length = 0) and the second column contains the array of image pixels.
+  Each row of the table represents a group in the case of multigroup FITS
+  images.
+
+  The output array of values will be converted from the datatype of the column 
+  and will be scaled by the FITS TSCALn and TZEROn values if necessary.
+*/
+{
+    double scale, zero, power = 1., dtemp;
+    int tcode, maxelem, hdutype, xcode, decimals;
+    long twidth, incre;
+    long ii, xwidth, ntodo;
+    int nulcheck;
+    LONGLONG repeat, startpos, elemnum, readptr, tnull;
+    LONGLONG rowlen, rownum, remain, next, rowincre;
+    char tform[20];
+    char message[81];
+    char snull[20];   /*  the FITS null value if reading from ASCII table  */
+
+    double cbuff[DBUFFSIZE / sizeof(double)]; /* align cbuff on word boundary */
+    void *buffer;
+
+    if (*status > 0 || nelem == 0)  /* inherit input status value if > 0 */
+        return(*status);
+
+    buffer = cbuff;
+
+    if (anynul)
+        *anynul = 0;
+
+    if (nultyp == 2)
+        memset(nularray, 0, (size_t) nelem);   /* initialize nullarray */
+
+    /*---------------------------------------------------*/
+    /*  Check input and get parameters about the column: */
+    /*---------------------------------------------------*/
+    if ( ffgcprll( fptr, colnum, firstrow, firstelem, nelem, 0, &scale, &zero,
+         tform, &twidth, &tcode, &maxelem, &startpos, &elemnum, &incre,
+         &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0 )
+         return(*status);
+
+    incre *= elemincre;   /* multiply incre to just get every nth pixel */
+
+    if (tcode == TSTRING)    /* setup for ASCII tables */
+    {
+      /* get the number of implied decimal places if no explicit decmal point */
+      ffasfm(tform, &xcode, &xwidth, &decimals, status); 
+      for(ii = 0; ii < decimals; ii++)
+        power *= 10.;
+    }
+    /*------------------------------------------------------------------*/
+    /*  Decide whether to check for null values in the input FITS file: */
+    /*------------------------------------------------------------------*/
+    nulcheck = nultyp; /* by default check for null values in the FITS file */
+
+    if (nultyp == 1 && nulval == 0)
+       nulcheck = 0;    /* calling routine does not want to check for nulls */
+
+    else if (tcode%10 == 1 &&        /* if reading an integer column, and  */ 
+            tnull == NULL_UNDEFINED) /* if a null value is not defined,    */
+            nulcheck = 0;            /* then do not check for null values. */
+
+    else if (tcode == TSHORT && (tnull > SHRT_MAX || tnull < SHRT_MIN) )
+            nulcheck = 0;            /* Impossible null value */
+
+    else if (tcode == TBYTE && (tnull > 255 || tnull < 0) )
+            nulcheck = 0;            /* Impossible null value */
+
+    else if (tcode == TSTRING && snull[0] == ASCII_NULL_UNDEFINED)
+         nulcheck = 0;
+
+    /*----------------------------------------------------------------------*/
+    /*  If FITS column and output data array have same datatype, then we do */
+    /*  not need to use a temporary buffer to store intermediate datatype.  */
+    /*----------------------------------------------------------------------*/
+    if (tcode == TSHORT) /* Special Case:                        */
+    {                             /* no type convertion required, so read */
+        maxelem = (int) nelem;          /* data directly into output buffer.    */
+    }
+
+    /*---------------------------------------------------------------------*/
+    /*  Now read the pixels from the FITS column. If the column does not   */
+    /*  have the same datatype as the output array, then we have to read   */
+    /*  the raw values into a temporary buffer (of limited size).  In      */
+    /*  the case of a vector colum read only 1 vector of values at a time  */
+    /*  then skip to the next row if more values need to be read.          */
+    /*  After reading the raw values, then call the fffXXYY routine to (1) */
+    /*  test for undefined values, (2) convert the datatype if necessary,  */
+    /*  and (3) scale the values by the FITS TSCALn and TZEROn linear      */
+    /*  scaling parameters.                                                */
+    /*---------------------------------------------------------------------*/
+    remain = nelem;           /* remaining number of values to read */
+    next = 0;                 /* next element in array to be read   */
+    rownum = 0;               /* row number, relative to firstrow   */
+
+    while (remain)
+    {
+        /* limit the number of pixels to read at one time to the number that
+           will fit in the buffer or to the number of pixels that remain in
+           the current vector, which ever is smaller.
+        */
+        ntodo = (long) minvalue(remain, maxelem);      
+        ntodo = (long) minvalue(ntodo, ((repeat - elemnum - 1)/elemincre +1));
+
+        readptr = startpos + ((LONGLONG)rownum * rowlen) + (elemnum * (incre / elemincre));
+
+        switch (tcode) 
+        {
+            case (TSHORT):
+                ffgi2b(fptr, readptr, ntodo, incre,
+                       (short *) &array[next], status);
+                fffi2u2((short *) &array[next], ntodo, scale,
+                       zero, nulcheck, (short) tnull, nulval, &nularray[next],
+                       anynul, &array[next], status);
+                break;
+            case (TLONGLONG):
+
+                ffgi8b(fptr, readptr, ntodo, incre, (long *) buffer, status);
+                fffi8u2( (LONGLONG *) buffer, ntodo, scale, zero, 
+                           nulcheck, tnull, nulval, &nularray[next], 
+                            anynul, &array[next], status);
+                break;
+            case (TBYTE):
+                ffgi1b(fptr, readptr, ntodo, incre, (unsigned char *) buffer,
+                      status);
+                fffi1u2((unsigned char *) buffer, ntodo, scale, zero, nulcheck, 
+                    (unsigned char) tnull, nulval, &nularray[next], anynul, 
+                    &array[next], status);
+                break;
+            case (TLONG):
+                ffgi4b(fptr, readptr, ntodo, incre, (INT32BIT *) buffer,
+                       status);
+                fffi4u2((INT32BIT *) buffer, ntodo, scale, zero, nulcheck, 
+                       (INT32BIT) tnull, nulval, &nularray[next], anynul, 
+                       &array[next], status);
+                break;
+            case (TFLOAT):
+                ffgr4b(fptr, readptr, ntodo, incre, (float  *) buffer, status);
+                fffr4u2((float  *) buffer, ntodo, scale, zero, nulcheck, 
+                       nulval, &nularray[next], anynul, 
+                       &array[next], status);
+                break;
+            case (TDOUBLE):
+                ffgr8b(fptr, readptr, ntodo, incre, (double *) buffer, status);
+                fffr8u2((double *) buffer, ntodo, scale, zero, nulcheck, 
+                          nulval, &nularray[next], anynul, 
+                          &array[next], status);
+                break;
+            case (TSTRING):
+                ffmbyt(fptr, readptr, REPORT_EOF, status);
+       
+                if (incre == twidth)    /* contiguous bytes */
+                     ffgbyt(fptr, ntodo * twidth, buffer, status);
+                else
+                     ffgbytoff(fptr, twidth, ntodo, incre - twidth, buffer,
+                               status);
+
+                fffstru2((char *) buffer, ntodo, scale, zero, twidth, power,
+                     nulcheck, snull, nulval, &nularray[next], anynul,
+                     &array[next], status);
+                break;
+
+            default:  /*  error trap for invalid column format */
+                sprintf(message, 
+                   "Cannot read numbers from column %d which has format %s",
+                    colnum, tform);
+                ffpmsg(message);
+                if (hdutype == ASCII_TBL)
+                    return(*status = BAD_ATABLE_FORMAT);
+                else
+                    return(*status = BAD_BTABLE_FORMAT);
+
+        } /* End of switch block */
+
+        /*-------------------------*/
+        /*  Check for fatal error  */
+        /*-------------------------*/
+        if (*status > 0)  /* test for error during previous read operation */
+        {
+	  dtemp = (double) next;
+          if (hdutype > 0)
+            sprintf(message,
+            "Error reading elements %.0f thru %.0f from column %d (ffgclui).",
+              dtemp+1., dtemp+ntodo, colnum);
+          else
+            sprintf(message,
+            "Error reading elements %.0f thru %.0f from image (ffgclui).",
+              dtemp+1., dtemp+ntodo);
+
+          ffpmsg(message);
+          return(*status);
+        }
+
+        /*--------------------------------------------*/
+        /*  increment the counters for the next loop  */
+        /*--------------------------------------------*/
+        remain -= ntodo;
+        if (remain)
+        {
+            next += ntodo;
+            elemnum = elemnum + (ntodo * elemincre);
+
+            if (elemnum >= repeat)  /* completed a row; start on later row */
+            {
+                rowincre = elemnum / repeat;
+                rownum += rowincre;
+                elemnum = elemnum - (rowincre * repeat);
+            }
+        }
+    }  /*  End of main while Loop  */
+
+
+    /*--------------------------------*/
+    /*  check for numerical overflow  */
+    /*--------------------------------*/
+    if (*status == OVERFLOW_ERR)
+    {
+        ffpmsg(
+        "Numerical overflow during type conversion while reading FITS data.");
+        *status = NUM_OVERFLOW;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi1u2(unsigned char *input, /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            unsigned char tnull,  /* I - value of FITS TNULLn keyword if any */
+   unsigned short nullval,        /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+   unsigned short *output,        /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (unsigned short) input[ii]; /* copy input */
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DUSHRT_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else if (dvalue > DUSHRT_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = USHRT_MAX;
+                }
+                else
+                    output[ii] = (unsigned short) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                    output[ii] = (unsigned short) input[ii];
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DUSHRT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (dvalue > DUSHRT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = USHRT_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned short) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi2u2(short *input,         /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            short tnull,          /* I - value of FITS TNULLn keyword if any */
+   unsigned short nullval,        /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+   unsigned short *output,        /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 32768.) 
+        {       
+           /* Instead of adding 32768, it is more efficient */
+           /* to just flip the sign bit with the XOR operator */
+
+           for (ii = 0; ii < ntodo; ii++)
+              output[ii] =  ( *(unsigned short *) &input[ii] ) ^ 0x8000;
+        }
+        else if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] < 0)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else
+                    output[ii] = (unsigned short) input[ii]; /* copy input */
+            }
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DUSHRT_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else if (dvalue > DUSHRT_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = USHRT_MAX;
+                }
+                else
+                    output[ii] = (unsigned short) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 32768.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                    output[ii] =  ( *(unsigned short *) &input[ii] ) ^ 0x8000;
+            }
+        }
+        else if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else if (input[ii] < 0)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else
+                    output[ii] = (unsigned short) input[ii]; /* copy input */
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DUSHRT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (dvalue > DUSHRT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = USHRT_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned short) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi4u2(INT32BIT *input,      /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            INT32BIT tnull,       /* I - value of FITS TNULLn keyword if any */
+   unsigned short nullval,        /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+   unsigned short *output,        /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] < 0)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else if (input[ii] > USHRT_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = USHRT_MAX;
+                }
+                else
+                    output[ii] = (unsigned short) input[ii];
+            }
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DUSHRT_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else if (dvalue > DUSHRT_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = USHRT_MAX;
+                }
+                else
+                    output[ii] = (unsigned short) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    if (input[ii] < 0)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (input[ii] > USHRT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = USHRT_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned short) input[ii];
+                }
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DUSHRT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (dvalue > DUSHRT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = USHRT_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned short) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi8u2(LONGLONG *input,      /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            LONGLONG tnull,       /* I - value of FITS TNULLn keyword if any */
+   unsigned short nullval,        /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+   unsigned short *output,        /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] < 0)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else if (input[ii] > USHRT_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = USHRT_MAX;
+                }
+                else
+                    output[ii] = (unsigned short) input[ii];
+            }
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DUSHRT_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else if (dvalue > DUSHRT_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = USHRT_MAX;
+                }
+                else
+                    output[ii] = (unsigned short) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    if (input[ii] < 0)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (input[ii] > USHRT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = USHRT_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned short) input[ii];
+                }
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DUSHRT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (dvalue > DUSHRT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = USHRT_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned short) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffr4u2(float *input,         /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+   unsigned short nullval,        /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+   unsigned short *output,        /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to NaN.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+    short *sptr, iret;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] < DUSHRT_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else if (input[ii] > DUSHRT_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = USHRT_MAX;
+                }
+                else
+                    output[ii] = (unsigned short) input[ii];
+            }
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DUSHRT_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else if (dvalue > DUSHRT_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = USHRT_MAX;
+                }
+                else
+                    output[ii] = (unsigned short) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        sptr = (short *) input;
+
+#if BYTESWAPPED && MACHINE != VAXVMS && MACHINE != ALPHAVMS
+        sptr++;       /* point to MSBs */
+#endif
+
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++, sptr += 2)
+            {
+              if (0 != (iret = fnan(*sptr) ) )   /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                  }
+                  else            /* it's an underflow */
+                     output[ii] = 0;
+              }
+              else
+                {
+                    if (input[ii] < DUSHRT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (input[ii] > DUSHRT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = USHRT_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned short) input[ii];
+                }
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++, sptr += 2)
+            {
+              if (0 != (iret = fnan(*sptr) ) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {  
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                  }
+                  else            /* it's an underflow */
+                  { 
+                    if (zero < DUSHRT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (zero > DUSHRT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = USHRT_MAX;
+                    }
+                    else
+                      output[ii] = (unsigned short) zero;
+                  }
+              }
+              else
+              {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DUSHRT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (dvalue > DUSHRT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = USHRT_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned short) dvalue;
+              }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffr8u2(double *input,        /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+   unsigned short nullval,        /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+   unsigned short *output,        /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to NaN.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+    short *sptr, iret;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] < DUSHRT_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else if (input[ii] > DUSHRT_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = USHRT_MAX;
+                }
+                else
+                    output[ii] = (unsigned short) input[ii];
+            }
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DUSHRT_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else if (dvalue > DUSHRT_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = USHRT_MAX;
+                }
+                else
+                    output[ii] = (unsigned short) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        sptr = (short *) input;
+
+#if BYTESWAPPED && MACHINE != VAXVMS && MACHINE != ALPHAVMS
+        sptr += 3;       /* point to MSBs */
+#endif
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++, sptr += 4)
+            {
+              if (0 != (iret = dnan(*sptr)) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                  }
+                  else            /* it's an underflow */
+                     output[ii] = 0;
+              }
+              else
+                {
+                    if (input[ii] < DUSHRT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (input[ii] > DUSHRT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = USHRT_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned short) input[ii];
+                }
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++, sptr += 4)
+            {
+              if (0 != (iret = dnan(*sptr)) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {  
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                  }
+                  else            /* it's an underflow */
+                  { 
+                    if (zero < DUSHRT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (zero > DUSHRT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = USHRT_MAX;
+                    }
+                    else
+                      output[ii] = (unsigned short) zero;
+                  }
+              }
+              else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DUSHRT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (dvalue > DUSHRT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = USHRT_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned short) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffstru2(char *input,         /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            long twidth,          /* I - width of each substring of chars    */
+            double implipower,    /* I - power of 10 of implied decimal      */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            char  *snull,         /* I - value of FITS null string, if any   */
+   unsigned short nullval,        /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+   unsigned short *output,        /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file. Check
+  for null values and do scaling if required. The nullcheck code value
+  determines how any null values in the input array are treated. A null
+  value is an input pixel that is equal to snull.  If nullcheck= 0, then
+  no special checking for nulls is performed.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    int nullen;
+    long ii;
+    double dvalue;
+    char *cstring, message[81];
+    char *cptr, *tpos;
+    char tempstore, chrzero = '0';
+    double val, power;
+    int exponent, sign, esign, decpt;
+
+    nullen = strlen(snull);
+    cptr = input;  /* pointer to start of input string */
+    for (ii = 0; ii < ntodo; ii++)
+    {
+      cstring = cptr;
+      /* temporarily insert a null terminator at end of the string */
+      tpos = cptr + twidth;
+      tempstore = *tpos;
+      *tpos = 0;
+
+      /* check if null value is defined, and if the    */
+      /* column string is identical to the null string */
+      if (snull[0] != ASCII_NULL_UNDEFINED && 
+         !strncmp(snull, cptr, nullen) )
+      {
+        if (nullcheck)  
+        {
+          *anynull = 1;    
+          if (nullcheck == 1)
+            output[ii] = nullval;
+          else
+            nullarray[ii] = 1;
+        }
+        cptr += twidth;
+      }
+      else
+      {
+        /* value is not the null value, so decode it */
+        /* remove any embedded blank characters from the string */
+
+        decpt = 0;
+        sign = 1;
+        val  = 0.;
+        power = 1.;
+        exponent = 0;
+        esign = 1;
+
+        while (*cptr == ' ')               /* skip leading blanks */
+           cptr++;
+
+        if (*cptr == '-' || *cptr == '+')  /* check for leading sign */
+        {
+          if (*cptr == '-')
+             sign = -1;
+
+          cptr++;
+
+          while (*cptr == ' ')         /* skip blanks between sign and value */
+            cptr++;
+        }
+
+        while (*cptr >= '0' && *cptr <= '9')
+        {
+          val = val * 10. + *cptr - chrzero;  /* accumulate the value */
+          cptr++;
+
+          while (*cptr == ' ')         /* skip embedded blanks in the value */
+            cptr++;
+        }
+
+        if (*cptr == '.' || *cptr == ',')       /* check for decimal point */
+        {
+          decpt = 1;       /* set flag to show there was a decimal point */
+          cptr++;
+          while (*cptr == ' ')         /* skip any blanks */
+            cptr++;
+
+          while (*cptr >= '0' && *cptr <= '9')
+          {
+            val = val * 10. + *cptr - chrzero;  /* accumulate the value */
+            power = power * 10.;
+            cptr++;
+
+            while (*cptr == ' ')         /* skip embedded blanks in the value */
+              cptr++;
+          }
+        }
+
+        if (*cptr == 'E' || *cptr == 'D')  /* check for exponent */
+        {
+          cptr++;
+          while (*cptr == ' ')         /* skip blanks */
+              cptr++;
+  
+          if (*cptr == '-' || *cptr == '+')  /* check for exponent sign */
+          {
+            if (*cptr == '-')
+               esign = -1;
+
+            cptr++;
+
+            while (*cptr == ' ')        /* skip blanks between sign and exp */
+              cptr++;
+          }
+
+          while (*cptr >= '0' && *cptr <= '9')
+          {
+            exponent = exponent * 10 + *cptr - chrzero;  /* accumulate exp */
+            cptr++;
+
+            while (*cptr == ' ')         /* skip embedded blanks */
+              cptr++;
+          }
+        }
+
+        if (*cptr  != 0)  /* should end up at the null terminator */
+        {
+          sprintf(message, "Cannot read number from ASCII table");
+          ffpmsg(message);
+          sprintf(message, "Column field = %s.", cstring);
+          ffpmsg(message);
+          /* restore the char that was overwritten by the null */
+          *tpos = tempstore;
+          return(*status = BAD_C2D);
+        }
+
+        if (!decpt)  /* if no explicit decimal, use implied */
+           power = implipower;
+
+        dvalue = (sign * val / power) * pow(10., (double) (esign * exponent));
+
+        dvalue = dvalue * scale + zero;   /* apply the scaling */
+
+        if (dvalue < DUSHRT_MIN)
+        {
+            *status = OVERFLOW_ERR;
+            output[ii] = 0;
+        }
+        else if (dvalue > DUSHRT_MAX)
+        {
+            *status = OVERFLOW_ERR;
+            output[ii] = USHRT_MAX;
+        }
+        else
+            output[ii] = (unsigned short) dvalue;
+      }
+      /* restore the char that was overwritten by the null */
+      *tpos = tempstore;
+    }
+    return(*status);
+}
diff --git a/src/plugins/cfitsio/getcoluj.c b/src/plugins/cfitsio/getcoluj.c
new file mode 100644
index 0000000..bb69134
--- /dev/null
+++ b/src/plugins/cfitsio/getcoluj.c
@@ -0,0 +1,1901 @@
+/*  This file, getcoluj.c, contains routines that read data elements from  */
+/*  a FITS image or table, with unsigned long data type.                   */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+
+#include <math.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <string.h>
+#include "fitsio2.h"
+
+/*--------------------------------------------------------------------------*/
+int ffgpvuj(fitsfile *fptr,   /* I - FITS file pointer                       */
+            long  group,      /* I - group to read (1 = 1st group)           */
+            LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+            LONGLONG  nelem,      /* I - number of values to read                */
+   unsigned long  nulval,     /* I - value for undefined pixels              */
+   unsigned long  *array,     /* O - array of values that are returned       */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+  Undefined elements will be set equal to NULVAL, unless NULVAL=0
+  in which case no checking for undefined values will be performed.
+  ANYNUL is returned with a value of .true. if any pixels are undefined.
+*/
+{
+    long row;
+    char cdummy;
+    int nullcheck = 1;
+    unsigned long nullvalue;
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+        nullvalue = nulval;  /* set local variable */
+
+        fits_read_compressed_pixels(fptr, TULONG, firstelem, nelem,
+            nullcheck, &nullvalue, array, NULL, anynul, status);
+        return(*status);
+    }
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffgcluj(fptr, 2, row, firstelem, nelem, 1, 1, nulval,
+               array, &cdummy, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgpfuj(fitsfile *fptr,   /* I - FITS file pointer                       */
+            long  group,      /* I - group to read (1 = 1st group)           */
+            LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+            LONGLONG  nelem,      /* I - number of values to read                */
+   unsigned long  *array,     /* O - array of values that are returned       */
+            char *nularray,   /* O - array of null pixel flags               */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+  Any undefined pixels in the returned array will be set = 0 and the 
+  corresponding nularray value will be set = 1.
+  ANYNUL is returned with a value of .true. if any pixels are undefined.
+*/
+{
+    long row;
+    int nullcheck = 2;
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        fits_read_compressed_pixels(fptr, TULONG, firstelem, nelem,
+            nullcheck, NULL, array, nularray, anynul, status);
+        return(*status);
+    }
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffgcluj(fptr, 2, row, firstelem, nelem, 1, 2, 0L,
+               array, nularray, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffg2duj(fitsfile *fptr,  /* I - FITS file pointer                       */
+           long  group,     /* I - group to read (1 = 1st group)           */
+  unsigned long  nulval,    /* set undefined pixels equal to this          */
+           LONGLONG  ncols,     /* I - number of pixels in each row of array   */
+           LONGLONG  naxis1,    /* I - FITS image NAXIS1 value                 */
+           LONGLONG  naxis2,    /* I - FITS image NAXIS2 value                 */
+  unsigned long  *array,    /* O - array to be filled and returned         */
+           int  *anynul,    /* O - set to 1 if any values are null; else 0 */
+           int  *status)    /* IO - error status                           */
+/*
+  Read an entire 2-D array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being read).  Any null
+  values in the array will be set equal to the value of nulval, unless
+  nulval = 0 in which case no null checking will be performed.
+*/
+{
+    /* call the 3D reading routine, with the 3rd dimension = 1 */
+
+    ffg3duj(fptr, group, nulval, ncols, naxis2, naxis1, naxis2, 1, array, 
+           anynul, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffg3duj(fitsfile *fptr,  /* I - FITS file pointer                       */
+           long  group,     /* I - group to read (1 = 1st group)           */
+  unsigned long  nulval,    /* set undefined pixels equal to this          */
+           LONGLONG  ncols,     /* I - number of pixels in each row of array   */
+           LONGLONG  nrows,     /* I - number of rows in each plane of array   */
+           LONGLONG  naxis1,    /* I - FITS image NAXIS1 value                 */
+           LONGLONG  naxis2,    /* I - FITS image NAXIS2 value                 */
+           LONGLONG  naxis3,    /* I - FITS image NAXIS3 value                 */
+  unsigned long  *array,    /* O - array to be filled and returned         */
+           int  *anynul,    /* O - set to 1 if any values are null; else 0 */
+           int  *status)    /* IO - error status                           */
+/*
+  Read an entire 3-D array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being read).  Any null
+  values in the array will be set equal to the value of nulval, unless
+  nulval = 0 in which case no null checking will be performed.
+*/
+{
+    long tablerow, ii, jj;
+    char cdummy;
+    int nullcheck = 1;
+    long inc[] = {1,1,1};
+    LONGLONG fpixel[] = {1,1,1}, nfits, narray;
+    LONGLONG lpixel[3];
+    unsigned long nullvalue;
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        lpixel[0] = ncols;
+        lpixel[1] = nrows;
+        lpixel[2] = naxis3;
+        nullvalue = nulval;  /* set local variable */
+
+        fits_read_compressed_img(fptr, TULONG, fpixel, lpixel, inc,
+            nullcheck, &nullvalue, array, NULL, anynul, status);
+        return(*status);
+    }
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+    tablerow=maxvalue(1,group);
+
+    if (ncols == naxis1 && nrows == naxis2)  /* arrays have same size? */
+    {
+       /* all the image pixels are contiguous, so read all at once */
+       ffgcluj(fptr, 2, tablerow, 1, naxis1 * naxis2 * naxis3, 1, 1, nulval,
+               array, &cdummy, anynul, status);
+       return(*status);
+    }
+
+    if (ncols < naxis1 || nrows < naxis2)
+       return(*status = BAD_DIMEN);
+
+    nfits = 1;   /* next pixel in FITS image to read */
+    narray = 0;  /* next pixel in output array to be filled */
+
+    /* loop over naxis3 planes in the data cube */
+    for (jj = 0; jj < naxis3; jj++)
+    {
+      /* loop over the naxis2 rows in the FITS image, */
+      /* reading naxis1 pixels to each row            */
+
+      for (ii = 0; ii < naxis2; ii++)
+      {
+       if (ffgcluj(fptr, 2, tablerow, nfits, naxis1, 1, 1, nulval,
+          &array[narray], &cdummy, anynul, status) > 0)
+          return(*status);
+
+       nfits += naxis1;
+       narray += ncols;
+      }
+      narray += (nrows - naxis2) * ncols;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgsvuj(fitsfile *fptr, /* I - FITS file pointer                         */
+           int  colnum,    /* I - number of the column to read (1 = 1st)    */
+           int naxis,      /* I - number of dimensions in the FITS array    */
+           long  *naxes,   /* I - size of each dimension                    */
+           long  *blc,     /* I - 'bottom left corner' of the subsection    */
+           long  *trc,     /* I - 'top right corner' of the subsection      */
+           long  *inc,     /* I - increment to be applied in each dimension */
+  unsigned long nulval,    /* I - value to set undefined pixels             */
+  unsigned long *array,    /* O - array to be filled and returned           */
+           int  *anynul,   /* O - set to 1 if any values are null; else 0   */
+           int  *status)   /* IO - error status                             */
+/*
+  Read a subsection of data values from an image or a table column.
+  This routine is set up to handle a maximum of nine dimensions.
+*/
+{
+    long ii,i0, i1,i2,i3,i4,i5,i6,i7,i8,row,rstr,rstp,rinc;
+    long str[9],stp[9],incr[9];
+    long nelem, nultyp, ninc, numcol;
+    LONGLONG felem, dsize[10], blcll[9], trcll[9];
+    int hdutype, anyf;
+    char ldummy, msg[FLEN_ERRMSG];
+    int nullcheck = 1;
+    unsigned long nullvalue;
+
+    if (naxis < 1 || naxis > 9)
+    {
+        sprintf(msg, "NAXIS = %d in call to ffgsvuj is out of range", naxis);
+        ffpmsg(msg);
+        return(*status = BAD_DIMEN);
+    }
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        for (ii=0; ii < naxis; ii++) {
+	    blcll[ii] = blc[ii];
+	    trcll[ii] = trc[ii];
+	}
+
+        nullvalue = nulval;  /* set local variable */
+
+        fits_read_compressed_img(fptr, TULONG, blcll, trcll, inc,
+            nullcheck, &nullvalue, array, NULL, anynul, status);
+        return(*status);
+    }
+
+/*
+    if this is a primary array, then the input COLNUM parameter should
+    be interpreted as the row number, and we will alway read the image
+    data from column 2 (any group parameters are in column 1).
+*/
+    if (ffghdt(fptr, &hdutype, status) > 0)
+        return(*status);
+
+    if (hdutype == IMAGE_HDU)
+    {
+        /* this is a primary array, or image extension */
+        if (colnum == 0)
+        {
+            rstr = 1;
+            rstp = 1;
+        }
+        else
+        {
+            rstr = colnum;
+            rstp = colnum;
+        }
+        rinc = 1;
+        numcol = 2;
+    }
+    else
+    {
+        /* this is a table, so the row info is in the (naxis+1) elements */
+        rstr = blc[naxis];
+        rstp = trc[naxis];
+        rinc = inc[naxis];
+        numcol = colnum;
+    }
+
+    nultyp = 1;
+    if (anynul)
+        *anynul = FALSE;
+
+    i0 = 0;
+    for (ii = 0; ii < 9; ii++)
+    {
+        str[ii] = 1;
+        stp[ii] = 1;
+        incr[ii] = 1;
+        dsize[ii] = 1;
+    }
+
+    for (ii = 0; ii < naxis; ii++)
+    {
+      if (trc[ii] < blc[ii])
+      {
+        sprintf(msg, "ffgsvuj: illegal range specified for axis %ld", ii + 1);
+        ffpmsg(msg);
+        return(*status = BAD_PIX_NUM);
+      }
+
+      str[ii] = blc[ii];
+      stp[ii] = trc[ii];
+      incr[ii] = inc[ii];
+      dsize[ii + 1] = dsize[ii] * naxes[ii];
+    }
+
+    if (naxis == 1 && naxes[0] == 1)
+    {
+      /* This is not a vector column, so read all the rows at once */
+      nelem = (rstp - rstr) / rinc + 1;
+      ninc = rinc;
+      rstp = rstr;
+    }
+    else
+    {
+      /* have to read each row individually, in all dimensions */
+      nelem = (stp[0] - str[0]) / inc[0] + 1;
+      ninc = incr[0];
+    }
+
+    for (row = rstr; row <= rstp; row += rinc)
+    {
+     for (i8 = str[8]; i8 <= stp[8]; i8 += incr[8])
+     {
+      for (i7 = str[7]; i7 <= stp[7]; i7 += incr[7])
+      {
+       for (i6 = str[6]; i6 <= stp[6]; i6 += incr[6])
+       {
+        for (i5 = str[5]; i5 <= stp[5]; i5 += incr[5])
+        {
+         for (i4 = str[4]; i4 <= stp[4]; i4 += incr[4])
+         {
+          for (i3 = str[3]; i3 <= stp[3]; i3 += incr[3])
+          {
+           for (i2 = str[2]; i2 <= stp[2]; i2 += incr[2])
+           {
+            for (i1 = str[1]; i1 <= stp[1]; i1 += incr[1])
+            {
+              felem=str[0] + (i1 - 1) * dsize[1] + (i2 - 1) * dsize[2] + 
+                             (i3 - 1) * dsize[3] + (i4 - 1) * dsize[4] +
+                             (i5 - 1) * dsize[5] + (i6 - 1) * dsize[6] +
+                             (i7 - 1) * dsize[7] + (i8 - 1) * dsize[8];
+
+              if ( ffgcluj(fptr, numcol, row, felem, nelem, ninc, nultyp,
+                   nulval, &array[i0], &ldummy, &anyf, status) > 0)
+                   return(*status);
+
+              if (anyf && anynul)
+                  *anynul = TRUE;
+
+              i0 += nelem;
+            }
+           }
+          }
+         }
+        }
+       }
+      }
+     }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgsfuj(fitsfile *fptr, /* I - FITS file pointer                         */
+           int  colnum,    /* I - number of the column to read (1 = 1st)    */
+           int naxis,      /* I - number of dimensions in the FITS array    */
+           long  *naxes,   /* I - size of each dimension                    */
+           long  *blc,     /* I - 'bottom left corner' of the subsection    */
+           long  *trc,     /* I - 'top right corner' of the subsection      */
+           long  *inc,     /* I - increment to be applied in each dimension */
+  unsigned long *array,    /* O - array to be filled and returned           */
+           char *flagval,  /* O - set to 1 if corresponding value is null   */
+           int  *anynul,   /* O - set to 1 if any values are null; else 0   */
+           int  *status)   /* IO - error status                             */
+/*
+  Read a subsection of data values from an image or a table column.
+  This routine is set up to handle a maximum of nine dimensions.
+*/
+{
+    long ii,i0, i1,i2,i3,i4,i5,i6,i7,i8,row,rstr,rstp,rinc;
+    long str[9],stp[9],incr[9],dsize[10];
+    LONGLONG blcll[9], trcll[9];
+    long felem, nelem, nultyp, ninc, numcol;
+    unsigned long nulval = 0;
+    int hdutype, anyf;
+    char msg[FLEN_ERRMSG];
+    int nullcheck = 2;
+
+    if (naxis < 1 || naxis > 9)
+    {
+        sprintf(msg, "NAXIS = %d in call to ffgsvj is out of range", naxis);
+        ffpmsg(msg);
+        return(*status = BAD_DIMEN);
+    }
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        for (ii=0; ii < naxis; ii++) {
+	    blcll[ii] = blc[ii];
+	    trcll[ii] = trc[ii];
+	}
+
+        fits_read_compressed_img(fptr, TULONG, blcll, trcll, inc,
+            nullcheck, NULL, array, flagval, anynul, status);
+        return(*status);
+    }
+
+/*
+    if this is a primary array, then the input COLNUM parameter should
+    be interpreted as the row number, and we will alway read the image
+    data from column 2 (any group parameters are in column 1).
+*/
+    if (ffghdt(fptr, &hdutype, status) > 0)
+        return(*status);
+
+    if (hdutype == IMAGE_HDU)
+    {
+        /* this is a primary array, or image extension */
+        if (colnum == 0)
+        {
+            rstr = 1;
+            rstp = 1;
+        }
+        else
+        {
+            rstr = colnum;
+            rstp = colnum;
+        }
+        rinc = 1;
+        numcol = 2;
+    }
+    else
+    {
+        /* this is a table, so the row info is in the (naxis+1) elements */
+        rstr = blc[naxis];
+        rstp = trc[naxis];
+        rinc = inc[naxis];
+        numcol = colnum;
+    }
+
+    nultyp = 2;
+    if (anynul)
+        *anynul = FALSE;
+
+    i0 = 0;
+    for (ii = 0; ii < 9; ii++)
+    {
+        str[ii] = 1;
+        stp[ii] = 1;
+        incr[ii] = 1;
+        dsize[ii] = 1;
+    }
+
+    for (ii = 0; ii < naxis; ii++)
+    {
+      if (trc[ii] < blc[ii])
+      {
+        sprintf(msg, "ffgsvj: illegal range specified for axis %ld", ii + 1);
+        ffpmsg(msg);
+        return(*status = BAD_PIX_NUM);
+      }
+
+      str[ii] = blc[ii];
+      stp[ii] = trc[ii];
+      incr[ii] = inc[ii];
+      dsize[ii + 1] = dsize[ii] * naxes[ii];
+    }
+
+    if (naxis == 1 && naxes[0] == 1)
+    {
+      /* This is not a vector column, so read all the rows at once */
+      nelem = (rstp - rstr) / rinc + 1;
+      ninc = rinc;
+      rstp = rstr;
+    }
+    else
+    {
+      /* have to read each row individually, in all dimensions */
+      nelem = (stp[0] - str[0]) / inc[0] + 1;
+      ninc = incr[0];
+    }
+
+    for (row = rstr; row <= rstp; row += rinc)
+    {
+     for (i8 = str[8]; i8 <= stp[8]; i8 += incr[8])
+     {
+      for (i7 = str[7]; i7 <= stp[7]; i7 += incr[7])
+      {
+       for (i6 = str[6]; i6 <= stp[6]; i6 += incr[6])
+       {
+        for (i5 = str[5]; i5 <= stp[5]; i5 += incr[5])
+        {
+         for (i4 = str[4]; i4 <= stp[4]; i4 += incr[4])
+         {
+          for (i3 = str[3]; i3 <= stp[3]; i3 += incr[3])
+          {
+           for (i2 = str[2]; i2 <= stp[2]; i2 += incr[2])
+           {
+            for (i1 = str[1]; i1 <= stp[1]; i1 += incr[1])
+            {
+              felem=str[0] + (i1 - 1) * dsize[1] + (i2 - 1) * dsize[2] + 
+                             (i3 - 1) * dsize[3] + (i4 - 1) * dsize[4] +
+                             (i5 - 1) * dsize[5] + (i6 - 1) * dsize[6] +
+                             (i7 - 1) * dsize[7] + (i8 - 1) * dsize[8];
+
+              if ( ffgcluj(fptr, numcol, row, felem, nelem, ninc, nultyp,
+                   nulval, &array[i0], &flagval[i0], &anyf, status) > 0)
+                   return(*status);
+
+              if (anyf && anynul)
+                  *anynul = TRUE;
+
+              i0 += nelem;
+            }
+           }
+          }
+         }
+        }
+       }
+      }
+     }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffggpuj(fitsfile *fptr,   /* I - FITS file pointer                       */
+            long  group,      /* I - group to read (1 = 1st group)           */
+            long  firstelem,  /* I - first vector element to read (1 = 1st)  */
+            long  nelem,      /* I - number of values to read                */
+   unsigned long  *array,     /* O - array of values that are returned       */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of group parameters from the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+*/
+{
+    long row;
+    int idummy;
+    char cdummy;
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffgcluj(fptr, 1, row, firstelem, nelem, 1, 1, 0L,
+               array, &cdummy, &idummy, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcvuj(fitsfile *fptr,   /* I - FITS file pointer                       */
+           int  colnum,      /* I - number of column to read (1 = 1st col)  */
+           LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+           LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+           LONGLONG  nelem,      /* I - number of values to read                */
+  unsigned long  nulval,     /* I - value for null pixels                   */
+  unsigned long *array,      /* O - array of values that are read           */
+           int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+           int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from a column in the current FITS HDU. Automatic
+  datatype conversion will be performed if the datatype of the column does not
+  match the datatype of the array parameter. The output values will be scaled 
+  by the FITS TSCALn and TZEROn values if these values have been defined.
+  Any undefined pixels will be set equal to the value of 'nulval' unless
+  nulval = 0 in which case no checks for undefined pixels will be made.
+*/
+{
+    char cdummy;
+
+    ffgcluj(fptr, colnum, firstrow, firstelem, nelem, 1, 1, nulval,
+           array, &cdummy, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcfuj(fitsfile *fptr,   /* I - FITS file pointer                       */
+           int  colnum,      /* I - number of column to read (1 = 1st col)  */
+           LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+           LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+           LONGLONG  nelem,      /* I - number of values to read                */
+  unsigned long  *array,     /* O - array of values that are read           */
+           char *nularray,   /* O - array of flags: 1 if null pixel; else 0 */
+           int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+           int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from a column in the current FITS HDU. Automatic
+  datatype conversion will be performed if the datatype of the column does not
+  match the datatype of the array parameter. The output values will be scaled 
+  by the FITS TSCALn and TZEROn values if these values have been defined.
+  Nularray will be set = 1 if the corresponding array pixel is undefined, 
+  otherwise nularray will = 0.
+*/
+{
+    unsigned long dummy = 0;
+
+    ffgcluj(fptr, colnum, firstrow, firstelem, nelem, 1, 2, dummy,
+           array, nularray, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcluj(fitsfile *fptr,   /* I - FITS file pointer                       */
+            int  colnum,      /* I - number of column to read (1 = 1st col)  */
+            LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+            LONGLONG  firstelem, /* I - first vector element to read (1 = 1st)  */
+            LONGLONG  nelem,      /* I - number of values to read                */
+            long  elemincre,  /* I - pixel increment; e.g., 2 = every other  */
+            int   nultyp,     /* I - null value handling code:               */
+                              /*     1: set undefined pixels = nulval        */
+                              /*     2: set nularray=1 for undefined pixels  */
+   unsigned long  nulval,     /* I - value for null pixels if nultyp = 1     */
+   unsigned long  *array,     /* O - array of values that are read           */
+            char *nularray,   /* O - array of flags = 1 if nultyp = 2        */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from a column in the current FITS HDU.
+  The column number may refer to a real column in an ASCII or binary table, 
+  or it may refer be a virtual column in a 1 or more grouped FITS primary
+  array or image extension.  FITSIO treats a primary array as a binary table
+  with 2 vector columns: the first column contains the group parameters (often
+  with length = 0) and the second column contains the array of image pixels.
+  Each row of the table represents a group in the case of multigroup FITS
+  images.
+
+  The output array of values will be converted from the datatype of the column 
+  and will be scaled by the FITS TSCALn and TZEROn values if necessary.
+*/
+{
+    double scale, zero, power = 1., dtemp;
+    int tcode, maxelem, hdutype, xcode, decimals;
+    long twidth, incre;
+    long ii, xwidth, ntodo;
+    int nulcheck;
+    LONGLONG repeat, startpos, elemnum, readptr, tnull;
+    LONGLONG rowlen, rownum, remain, next, rowincre;
+    char tform[20];
+    char message[81];
+    char snull[20];   /*  the FITS null value if reading from ASCII table  */
+
+    double cbuff[DBUFFSIZE / sizeof(double)]; /* align cbuff on word boundary */
+    void *buffer;
+
+    if (*status > 0 || nelem == 0)  /* inherit input status value if > 0 */
+        return(*status);
+
+    buffer = cbuff;
+
+    if (anynul)
+        *anynul = 0;
+
+    if (nultyp == 2)
+        memset(nularray, 0, (size_t) nelem);   /* initialize nullarray */
+
+    /*---------------------------------------------------*/
+    /*  Check input and get parameters about the column: */
+    /*---------------------------------------------------*/
+    if ( ffgcprll( fptr, colnum, firstrow, firstelem, nelem, 0, &scale, &zero,
+         tform, &twidth, &tcode, &maxelem, &startpos, &elemnum, &incre,
+         &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0 )
+         return(*status);
+
+    incre *= elemincre;   /* multiply incre to just get every nth pixel */
+
+    if (tcode == TSTRING)    /* setup for ASCII tables */
+    {
+      /* get the number of implied decimal places if no explicit decmal point */
+      ffasfm(tform, &xcode, &xwidth, &decimals, status); 
+      for(ii = 0; ii < decimals; ii++)
+        power *= 10.;
+    }
+    /*------------------------------------------------------------------*/
+    /*  Decide whether to check for null values in the input FITS file: */
+    /*------------------------------------------------------------------*/
+    nulcheck = nultyp; /* by default check for null values in the FITS file */
+
+    if (nultyp == 1 && nulval == 0)
+       nulcheck = 0;    /* calling routine does not want to check for nulls */
+
+    else if (tcode%10 == 1 &&        /* if reading an integer column, and  */ 
+            tnull == NULL_UNDEFINED) /* if a null value is not defined,    */
+            nulcheck = 0;            /* then do not check for null values. */
+
+    else if (tcode == TSHORT && (tnull > SHRT_MAX || tnull < SHRT_MIN) )
+            nulcheck = 0;            /* Impossible null value */
+
+    else if (tcode == TBYTE && (tnull > 255 || tnull < 0) )
+            nulcheck = 0;            /* Impossible null value */
+
+    else if (tcode == TSTRING && snull[0] == ASCII_NULL_UNDEFINED)
+         nulcheck = 0;
+
+    /*----------------------------------------------------------------------*/
+    /*  If FITS column and output data array have same datatype, then we do */
+    /*  not need to use a temporary buffer to store intermediate datatype.  */
+    /*----------------------------------------------------------------------*/
+    if (tcode == TLONG)  /* Special Case:                        */
+    {                             /* no type convertion required, so read */
+        maxelem = (int) nelem;          /* data directly into output buffer.    */
+    }
+
+    /*---------------------------------------------------------------------*/
+    /*  Now read the pixels from the FITS column. If the column does not   */
+    /*  have the same datatype as the output array, then we have to read   */
+    /*  the raw values into a temporary buffer (of limited size).  In      */
+    /*  the case of a vector colum read only 1 vector of values at a time  */
+    /*  then skip to the next row if more values need to be read.          */
+    /*  After reading the raw values, then call the fffXXYY routine to (1) */
+    /*  test for undefined values, (2) convert the datatype if necessary,  */
+    /*  and (3) scale the values by the FITS TSCALn and TZEROn linear      */
+    /*  scaling parameters.                                                */
+    /*---------------------------------------------------------------------*/
+    remain = nelem;           /* remaining number of values to read */
+    next = 0;                 /* next element in array to be read   */
+    rownum = 0;               /* row number, relative to firstrow   */
+
+    while (remain)
+    {
+        /* limit the number of pixels to read at one time to the number that
+           will fit in the buffer or to the number of pixels that remain in
+           the current vector, which ever is smaller.
+        */
+        ntodo = (long) minvalue(remain, maxelem);      
+        ntodo = (long) minvalue(ntodo, ((repeat - elemnum - 1)/elemincre +1));
+
+        readptr = startpos + ((LONGLONG)rownum * rowlen) + (elemnum * (incre / elemincre));
+
+        switch (tcode) 
+        {
+            case (TLONG):
+                ffgi4b(fptr, readptr, ntodo, incre, (INT32BIT *) &array[next],
+                       status);
+                fffi4u4((INT32BIT *) &array[next], ntodo, scale, zero,
+                         nulcheck, (INT32BIT) tnull, nulval, &nularray[next],
+                         anynul, &array[next], status);
+                break;
+            case (TLONGLONG):
+
+                ffgi8b(fptr, readptr, ntodo, incre, (long *) buffer, status);
+                fffi8u4( (LONGLONG *) buffer, ntodo, scale, zero, 
+                           nulcheck, tnull, nulval, &nularray[next], 
+                            anynul, &array[next], status);
+                break;
+            case (TBYTE):
+                ffgi1b(fptr, readptr, ntodo, incre, (unsigned char *) buffer,
+                       status);
+                fffi1u4((unsigned char *) buffer, ntodo, scale, zero, nulcheck, 
+                     (unsigned char) tnull, nulval, &nularray[next], anynul, 
+                     &array[next], status);
+                break;
+            case (TSHORT):
+                ffgi2b(fptr, readptr, ntodo, incre, (short  *) buffer, status);
+                fffi2u4((short  *) buffer, ntodo, scale, zero, nulcheck, 
+                      (short) tnull, nulval, &nularray[next], anynul, 
+                      &array[next], status);
+                break;
+            case (TFLOAT):
+                ffgr4b(fptr, readptr, ntodo, incre, (float  *) buffer, status);
+                fffr4u4((float  *) buffer, ntodo, scale, zero, nulcheck, 
+                       nulval, &nularray[next], anynul, 
+                       &array[next], status);
+                break;
+            case (TDOUBLE):
+                ffgr8b(fptr, readptr, ntodo, incre, (double *) buffer, status);
+                fffr8u4((double *) buffer, ntodo, scale, zero, nulcheck, 
+                          nulval, &nularray[next], anynul, 
+                          &array[next], status);
+                break;
+            case (TSTRING):
+                ffmbyt(fptr, readptr, REPORT_EOF, status);
+       
+                if (incre == twidth)    /* contiguous bytes */
+                     ffgbyt(fptr, ntodo * twidth, buffer, status);
+                else
+                     ffgbytoff(fptr, twidth, ntodo, incre - twidth, buffer,
+                               status);
+
+                fffstru4((char *) buffer, ntodo, scale, zero, twidth, power,
+                     nulcheck, snull, nulval, &nularray[next], anynul,
+                     &array[next], status);
+                break;
+
+            default:  /*  error trap for invalid column format */
+                sprintf(message, 
+                   "Cannot read numbers from column %d which has format %s",
+                    colnum, tform);
+                ffpmsg(message);
+                if (hdutype == ASCII_TBL)
+                    return(*status = BAD_ATABLE_FORMAT);
+                else
+                    return(*status = BAD_BTABLE_FORMAT);
+
+        } /* End of switch block */
+
+        /*-------------------------*/
+        /*  Check for fatal error  */
+        /*-------------------------*/
+        if (*status > 0)  /* test for error during previous read operation */
+        {
+	  dtemp = (double) next;
+          if (hdutype > 0)
+            sprintf(message,
+            "Error reading elements %.0f thru %.0f from column %d (ffgcluj).",
+              dtemp+1., dtemp+ntodo, colnum);
+          else
+            sprintf(message,
+            "Error reading elements %.0f thru %.0f from image (ffgcluj).",
+              dtemp+1., dtemp+ntodo);
+
+          ffpmsg(message);
+          return(*status);
+        }
+
+        /*--------------------------------------------*/
+        /*  increment the counters for the next loop  */
+        /*--------------------------------------------*/
+        remain -= ntodo;
+        if (remain)
+        {
+            next += ntodo;
+            elemnum = elemnum + (ntodo * elemincre);
+
+            if (elemnum >= repeat)  /* completed a row; start on later row */
+            {
+                rowincre = elemnum / repeat;
+                rownum += rowincre;
+                elemnum = elemnum - (rowincre * repeat);
+            }
+        }
+    }  /*  End of main while Loop  */
+
+
+    /*--------------------------------*/
+    /*  check for numerical overflow  */
+    /*--------------------------------*/
+    if (*status == OVERFLOW_ERR)
+    {
+        ffpmsg(
+        "Numerical overflow during type conversion while reading FITS data.");
+        *status = NUM_OVERFLOW;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi1u4(unsigned char *input, /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            unsigned char tnull,  /* I - value of FITS TNULLn keyword if any */
+   unsigned long nullval,         /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+   unsigned long *output,         /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (unsigned long) input[ii];  /* copy input */
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DULONG_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else if (dvalue > DULONG_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = ULONG_MAX;
+                }
+                else
+                    output[ii] = (unsigned long) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                    output[ii] = (unsigned long) input[ii];
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DULONG_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (dvalue > DULONG_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = ULONG_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned long) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi2u4(short *input,         /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            short tnull,          /* I - value of FITS TNULLn keyword if any */
+   unsigned long nullval,         /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+   unsigned long *output,         /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] < 0)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else
+                    output[ii] = (unsigned long) input[ii];
+            }
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DULONG_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else if (dvalue > DULONG_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = ULONG_MAX;
+                }
+                else
+                    output[ii] = (unsigned long) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    if (input[ii] < 0)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else
+                        output[ii] = (unsigned long) input[ii];
+                }
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DULONG_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (dvalue > DULONG_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = ULONG_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned long) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi4u4(INT32BIT *input,      /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            INT32BIT tnull,       /* I - value of FITS TNULLn keyword if any */
+   unsigned long nullval,         /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+   unsigned long *output,         /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+
+  Process the array of data in reverse order, to handle the case where
+  the input data is 4-bytes and the output is  8-bytes and the conversion
+  is being done in place in the same array.
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 2147483648.)
+        {       
+           /* Instead of adding 2147483648, it is more efficient */
+           /* to just flip the sign bit with the XOR operator */
+
+            for (ii = ntodo - 1; ii >= 0; ii--)
+               output[ii] =  ( *(unsigned int *) &input[ii] ) ^ 0x80000000;
+        }
+        else if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = ntodo - 1; ii >= 0; ii--)
+            {
+                if (input[ii] < 0)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else
+                    output[ii] = (unsigned long) input[ii]; /* copy input */
+            }
+        }
+        else             /* must scale the data */
+        {
+            for (ii = ntodo - 1; ii >= 0; ii--)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DULONG_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else if (dvalue > DULONG_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = ULONG_MAX;
+                }
+                else
+                    output[ii] = (unsigned long) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 2147483648.) 
+        {       
+            for (ii = ntodo - 1; ii >= 0; ii--)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                   output[ii] =  ( *(unsigned int *) &input[ii] ) ^ 0x80000000;
+            }
+        }
+        else if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = ntodo - 1; ii >= 0; ii--)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else if (input[ii] < 0)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else
+                    output[ii] = (unsigned long) input[ii]; /* copy input */
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = ntodo - 1; ii >= 0; ii--)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DULONG_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (dvalue > DULONG_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = ULONG_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned long) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi8u4(LONGLONG *input,      /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            LONGLONG tnull,       /* I - value of FITS TNULLn keyword if any */
+   unsigned long nullval,         /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+   unsigned long *output,         /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] < 0)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else if (input[ii] > ULONG_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = ULONG_MAX;
+                }
+                else
+                    output[ii] = (unsigned long) input[ii];
+            }
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DULONG_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else if (dvalue > DULONG_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = ULONG_MAX;
+                }
+                else
+                    output[ii] = (unsigned long) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    if (input[ii] < 0)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (input[ii] > ULONG_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = ULONG_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned long) input[ii];
+                }
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DULONG_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (dvalue > DULONG_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = ULONG_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned long) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffr4u4(float *input,         /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+   unsigned long nullval,         /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+   unsigned long *output,         /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to NaN.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+    short *sptr, iret;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] < DULONG_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else if (input[ii] > DULONG_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = ULONG_MAX;
+                }
+                else
+                    output[ii] = (unsigned long) input[ii];
+            }
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DULONG_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else if (dvalue > DULONG_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = ULONG_MAX;
+                }
+                else
+                    output[ii] = (unsigned long) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        sptr = (short *) input;
+
+#if BYTESWAPPED && MACHINE != VAXVMS && MACHINE != ALPHAVMS
+        sptr++;       /* point to MSBs */
+#endif
+
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++, sptr += 2)
+            {
+              if (0 != (iret = fnan(*sptr) ) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                  }
+                  else            /* it's an underflow */
+                     output[ii] = 0;
+              }
+              else
+                {
+                    if (input[ii] < DULONG_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (input[ii] > DULONG_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = ULONG_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned long) input[ii];
+                }
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++, sptr += 2)
+            {
+              if (0 != (iret = fnan(*sptr) ) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {  
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                  }
+                  else            /* it's an underflow */
+                  { 
+                    if (zero < DULONG_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (zero > DULONG_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = ULONG_MAX;
+                    }
+                    else
+                      output[ii] = (unsigned long) zero;
+                  }
+              }
+              else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DULONG_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (dvalue > DULONG_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = ULONG_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned long) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffr8u4(double *input,        /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+   unsigned long nullval,         /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+   unsigned long *output,         /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to NaN.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+    short *sptr, iret;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] < DULONG_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else if (input[ii] > DULONG_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = ULONG_MAX;
+                }
+                else
+                    output[ii] = (unsigned long) input[ii];
+            }
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DULONG_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else if (dvalue > DULONG_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = ULONG_MAX;
+                }
+                else
+                    output[ii] = (unsigned long) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        sptr = (short *) input;
+
+#if BYTESWAPPED && MACHINE != VAXVMS && MACHINE != ALPHAVMS
+        sptr += 3;       /* point to MSBs */
+#endif
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++, sptr += 4)
+            {
+              if (0 != (iret = dnan(*sptr)) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                  }
+                  else            /* it's an underflow */
+                     output[ii] = 0;
+              }
+              else
+                {
+                    if (input[ii] < DULONG_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (input[ii] > DULONG_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = ULONG_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned long) input[ii];
+                }
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++, sptr += 4)
+            {
+              if (0 != (iret = dnan(*sptr)) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {  
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                  }
+                  else            /* it's an underflow */
+                  { 
+                    if (zero < DULONG_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (zero > DULONG_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = ULONG_MAX;
+                    }
+                    else
+                      output[ii] = (unsigned long) zero;
+                  }
+              }
+              else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DULONG_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (dvalue > DULONG_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = ULONG_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned long) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffstru4(char *input,         /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            long twidth,          /* I - width of each substring of chars    */
+            double implipower,    /* I - power of 10 of implied decimal      */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            char  *snull,         /* I - value of FITS null string, if any   */
+   unsigned long nullval,         /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+   unsigned long *output,         /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file. Check
+  for null values and do scaling if required. The nullcheck code value
+  determines how any null values in the input array are treated. A null
+  value is an input pixel that is equal to snull.  If nullcheck= 0, then
+  no special checking for nulls is performed.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    int nullen;
+    long ii;
+    double dvalue;
+    char *cstring, message[81];
+    char *cptr, *tpos;
+    char tempstore, chrzero = '0';
+    double val, power;
+    int exponent, sign, esign, decpt;
+
+    nullen = strlen(snull);
+    cptr = input;  /* pointer to start of input string */
+    for (ii = 0; ii < ntodo; ii++)
+    {
+      cstring = cptr;
+      /* temporarily insert a null terminator at end of the string */
+      tpos = cptr + twidth;
+      tempstore = *tpos;
+      *tpos = 0;
+
+      /* check if null value is defined, and if the    */
+      /* column string is identical to the null string */
+      if (snull[0] != ASCII_NULL_UNDEFINED && 
+         !strncmp(snull, cptr, nullen) )
+      {
+        if (nullcheck)  
+        {
+          *anynull = 1;    
+          if (nullcheck == 1)
+            output[ii] = nullval;
+          else
+            nullarray[ii] = 1;
+        }
+        cptr += twidth;
+      }
+      else
+      {
+        /* value is not the null value, so decode it */
+        /* remove any embedded blank characters from the string */
+
+        decpt = 0;
+        sign = 1;
+        val  = 0.;
+        power = 1.;
+        exponent = 0;
+        esign = 1;
+
+        while (*cptr == ' ')               /* skip leading blanks */
+           cptr++;
+
+        if (*cptr == '-' || *cptr == '+')  /* check for leading sign */
+        {
+          if (*cptr == '-')
+             sign = -1;
+
+          cptr++;
+
+          while (*cptr == ' ')         /* skip blanks between sign and value */
+            cptr++;
+        }
+
+        while (*cptr >= '0' && *cptr <= '9')
+        {
+          val = val * 10. + *cptr - chrzero;  /* accumulate the value */
+          cptr++;
+
+          while (*cptr == ' ')         /* skip embedded blanks in the value */
+            cptr++;
+        }
+
+        if (*cptr == '.' || *cptr == ',')       /* check for decimal point */
+        {
+          decpt = 1;       /* set flag to show there was a decimal point */
+          cptr++;
+          while (*cptr == ' ')         /* skip any blanks */
+            cptr++;
+
+          while (*cptr >= '0' && *cptr <= '9')
+          {
+            val = val * 10. + *cptr - chrzero;  /* accumulate the value */
+            power = power * 10.;
+            cptr++;
+
+            while (*cptr == ' ')         /* skip embedded blanks in the value */
+              cptr++;
+          }
+        }
+
+        if (*cptr == 'E' || *cptr == 'D')  /* check for exponent */
+        {
+          cptr++;
+          while (*cptr == ' ')         /* skip blanks */
+              cptr++;
+  
+          if (*cptr == '-' || *cptr == '+')  /* check for exponent sign */
+          {
+            if (*cptr == '-')
+               esign = -1;
+
+            cptr++;
+
+            while (*cptr == ' ')        /* skip blanks between sign and exp */
+              cptr++;
+          }
+
+          while (*cptr >= '0' && *cptr <= '9')
+          {
+            exponent = exponent * 10 + *cptr - chrzero;  /* accumulate exp */
+            cptr++;
+
+            while (*cptr == ' ')         /* skip embedded blanks */
+              cptr++;
+          }
+        }
+
+        if (*cptr  != 0)  /* should end up at the null terminator */
+        {
+          sprintf(message, "Cannot read number from ASCII table");
+          ffpmsg(message);
+          sprintf(message, "Column field = %s.", cstring);
+          ffpmsg(message);
+          /* restore the char that was overwritten by the null */
+          *tpos = tempstore;
+          return(*status = BAD_C2D);
+        }
+
+        if (!decpt)  /* if no explicit decimal, use implied */
+           power = implipower;
+
+        dvalue = (sign * val / power) * pow(10., (double) (esign * exponent));
+
+        dvalue = dvalue * scale + zero;   /* apply the scaling */
+
+        if (dvalue < DULONG_MIN)
+        {
+            *status = OVERFLOW_ERR;
+            output[ii] = 0;
+        }
+        else if (dvalue > DULONG_MAX)
+        {
+            *status = OVERFLOW_ERR;
+            output[ii] = ULONG_MAX;
+        }
+        else
+            output[ii] = (unsigned long) dvalue;
+      }
+      /* restore the char that was overwritten by the null */
+      *tpos = tempstore;
+    }
+    return(*status);
+}
diff --git a/src/plugins/cfitsio/getcoluk.c b/src/plugins/cfitsio/getcoluk.c
new file mode 100644
index 0000000..36d32e0
--- /dev/null
+++ b/src/plugins/cfitsio/getcoluk.c
@@ -0,0 +1,1916 @@
+/*  This file, getcolk.c, contains routines that read data elements from   */
+/*  a FITS image or table, with 'unsigned int' data type.                  */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+
+#include <math.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <string.h>
+#include "fitsio2.h"
+
+/*--------------------------------------------------------------------------*/
+int ffgpvuk( fitsfile *fptr,   /* I - FITS file pointer                      */
+            long  group,      /* I - group to read (1 = 1st group)           */
+            LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+            LONGLONG  nelem,      /* I - number of values to read                */
+   unsigned int   nulval,     /* I - value for undefined pixels              */
+   unsigned int   *array,     /* O - array of values that are returned       */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+  Undefined elements will be set equal to NULVAL, unless NULVAL=0
+  in which case no checking for undefined values will be performed.
+  ANYNUL is returned with a value of .true. if any pixels are undefined.
+*/
+{
+    long row;
+    char cdummy;
+    int nullcheck = 1;
+    unsigned int nullvalue;
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+         nullvalue = nulval;  /* set local variable */
+
+        fits_read_compressed_pixels(fptr, TUINT, firstelem, nelem,
+            nullcheck, &nullvalue, array, NULL, anynul, status);
+        return(*status);
+    }
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffgcluk(fptr, 2, row, firstelem, nelem, 1, 1, nulval,
+               array, &cdummy, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgpfuk(fitsfile *fptr,   /* I - FITS file pointer                       */
+            long  group,      /* I - group to read (1 = 1st group)           */
+            LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+            LONGLONG  nelem,      /* I - number of values to read                */
+   unsigned int   *array,     /* O - array of values that are returned       */
+            char *nularray,   /* O - array of null pixel flags               */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+  Any undefined pixels in the returned array will be set = 0 and the 
+  corresponding nularray value will be set = 1.
+  ANYNUL is returned with a value of .true. if any pixels are undefined.
+*/
+{
+    long row;
+    int nullcheck = 2;
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        fits_read_compressed_pixels(fptr, TUINT, firstelem, nelem,
+            nullcheck, NULL, array, nularray, anynul, status);
+        return(*status);
+    }
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffgcluk(fptr, 2, row, firstelem, nelem, 1, 2, 0L,
+               array, nularray, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffg2duk(fitsfile *fptr,  /* I - FITS file pointer                       */
+           long  group,     /* I - group to read (1 = 1st group)           */
+  unsigned int  nulval,    /* set undefined pixels equal to this          */
+           LONGLONG  ncols,     /* I - number of pixels in each row of array   */
+           LONGLONG  naxis1,    /* I - FITS image NAXIS1 value                 */
+           LONGLONG  naxis2,    /* I - FITS image NAXIS2 value                 */
+  unsigned int  *array,    /* O - array to be filled and returned         */
+           int  *anynul,    /* O - set to 1 if any values are null; else 0 */
+           int  *status)    /* IO - error status                           */
+/*
+  Read an entire 2-D array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being read).  Any null
+  values in the array will be set equal to the value of nulval, unless
+  nulval = 0 in which case no null checking will be performed.
+*/
+{
+    /* call the 3D reading routine, with the 3rd dimension = 1 */
+
+    ffg3duk(fptr, group, nulval, ncols, naxis2, naxis1, naxis2, 1, array, 
+           anynul, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffg3duk(fitsfile *fptr,  /* I - FITS file pointer                       */
+           long  group,     /* I - group to read (1 = 1st group)           */
+  unsigned int   nulval,    /* set undefined pixels equal to this          */
+           LONGLONG  ncols,     /* I - number of pixels in each row of array   */
+           LONGLONG  nrows,     /* I - number of rows in each plane of array   */
+           LONGLONG  naxis1,    /* I - FITS image NAXIS1 value                 */
+           LONGLONG  naxis2,    /* I - FITS image NAXIS2 value                 */
+           LONGLONG  naxis3,    /* I - FITS image NAXIS3 value                 */
+  unsigned int   *array,    /* O - array to be filled and returned         */
+           int  *anynul,    /* O - set to 1 if any values are null; else 0 */
+           int  *status)    /* IO - error status                           */
+/*
+  Read an entire 3-D array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being read).  Any null
+  values in the array will be set equal to the value of nulval, unless
+  nulval = 0 in which case no null checking will be performed.
+*/
+{
+    long tablerow, ii, jj;
+    char cdummy;
+    int nullcheck = 1;
+    long inc[] = {1,1,1};
+    LONGLONG fpixel[] = {1,1,1}, nfits, narray;
+    LONGLONG lpixel[3];
+    unsigned int nullvalue;
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        lpixel[0] = ncols;
+        lpixel[1] = nrows;
+        lpixel[2] = naxis3;
+        nullvalue = nulval;  /* set local variable */
+
+        fits_read_compressed_img(fptr, TUINT, fpixel, lpixel, inc,
+            nullcheck, &nullvalue, array, NULL, anynul, status);
+        return(*status);
+    }
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+    tablerow=maxvalue(1,group);
+
+    if (ncols == naxis1 && nrows == naxis2)  /* arrays have same size? */
+    {
+       /* all the image pixels are contiguous, so read all at once */
+       ffgcluk(fptr, 2, tablerow, 1, naxis1 * naxis2 * naxis3, 1, 1, nulval,
+               array, &cdummy, anynul, status);
+       return(*status);
+    }
+
+    if (ncols < naxis1 || nrows < naxis2)
+       return(*status = BAD_DIMEN);
+
+    nfits = 1;   /* next pixel in FITS image to read */
+    narray = 0;  /* next pixel in output array to be filled */
+
+    /* loop over naxis3 planes in the data cube */
+    for (jj = 0; jj < naxis3; jj++)
+    {
+      /* loop over the naxis2 rows in the FITS image, */
+      /* reading naxis1 pixels to each row            */
+
+      for (ii = 0; ii < naxis2; ii++)
+      {
+       if (ffgcluk(fptr, 2, tablerow, nfits, naxis1, 1, 1, nulval,
+          &array[narray], &cdummy, anynul, status) > 0)
+          return(*status);
+
+       nfits += naxis1;
+       narray += ncols;
+      }
+      narray += (nrows - naxis2) * ncols;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgsvuk(fitsfile *fptr, /* I - FITS file pointer                         */
+           int  colnum,    /* I - number of the column to read (1 = 1st)    */
+           int naxis,      /* I - number of dimensions in the FITS array    */
+           long  *naxes,   /* I - size of each dimension                    */
+           long  *blc,     /* I - 'bottom left corner' of the subsection    */
+           long  *trc,     /* I - 'top right corner' of the subsection      */
+           long  *inc,     /* I - increment to be applied in each dimension */
+  unsigned int  nulval,    /* I - value to set undefined pixels             */
+  unsigned int  *array,    /* O - array to be filled and returned           */
+           int  *anynul,   /* O - set to 1 if any values are null; else 0   */
+           int  *status)   /* IO - error status                             */
+/*
+  Read a subsection of data values from an image or a table column.
+  This routine is set up to handle a maximum of nine dimensions.
+*/
+{
+    long ii,i0, i1,i2,i3,i4,i5,i6,i7,i8,row,rstr,rstp,rinc;
+    long str[9],stp[9],incr[9];
+    long nelem, nultyp, ninc, numcol;
+    LONGLONG felem, dsize[10], blcll[9], trcll[9];
+    int hdutype, anyf;
+    char ldummy, msg[FLEN_ERRMSG];
+    int nullcheck = 1;
+    unsigned int nullvalue;
+
+    if (naxis < 1 || naxis > 9)
+    {
+        sprintf(msg, "NAXIS = %d in call to ffgsvuk is out of range", naxis);
+        ffpmsg(msg);
+        return(*status = BAD_DIMEN);
+    }
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        for (ii=0; ii < naxis; ii++) {
+	    blcll[ii] = blc[ii];
+	    trcll[ii] = trc[ii];
+	}
+
+        nullvalue = nulval;  /* set local variable */
+
+        fits_read_compressed_img(fptr, TUINT, blcll, trcll, inc,
+            nullcheck, &nullvalue, array, NULL, anynul, status);
+        return(*status);
+    }
+
+/*
+    if this is a primary array, then the input COLNUM parameter should
+    be interpreted as the row number, and we will alway read the image
+    data from column 2 (any group parameters are in column 1).
+*/
+    if (ffghdt(fptr, &hdutype, status) > 0)
+        return(*status);
+
+    if (hdutype == IMAGE_HDU)
+    {
+        /* this is a primary array, or image extension */
+        if (colnum == 0)
+        {
+            rstr = 1;
+            rstp = 1;
+        }
+        else
+        {
+            rstr = colnum;
+            rstp = colnum;
+        }
+        rinc = 1;
+        numcol = 2;
+    }
+    else
+    {
+        /* this is a table, so the row info is in the (naxis+1) elements */
+        rstr = blc[naxis];
+        rstp = trc[naxis];
+        rinc = inc[naxis];
+        numcol = colnum;
+    }
+
+    nultyp = 1;
+    if (anynul)
+        *anynul = FALSE;
+
+    i0 = 0;
+    for (ii = 0; ii < 9; ii++)
+    {
+        str[ii] = 1;
+        stp[ii] = 1;
+        incr[ii] = 1;
+        dsize[ii] = 1;
+    }
+
+    for (ii = 0; ii < naxis; ii++)
+    {
+      if (trc[ii] < blc[ii])
+      {
+        sprintf(msg, "ffgsvuk: illegal range specified for axis %ld", ii + 1);
+        ffpmsg(msg);
+        return(*status = BAD_PIX_NUM);
+      }
+
+      str[ii] = blc[ii];
+      stp[ii] = trc[ii];
+      incr[ii] = inc[ii];
+      dsize[ii + 1] = dsize[ii] * naxes[ii];
+    }
+
+    if (naxis == 1 && naxes[0] == 1)
+    {
+      /* This is not a vector column, so read all the rows at once */
+      nelem = (rstp - rstr) / rinc + 1;
+      ninc = rinc;
+      rstp = rstr;
+    }
+    else
+    {
+      /* have to read each row individually, in all dimensions */
+      nelem = (stp[0] - str[0]) / inc[0] + 1;
+      ninc = incr[0];
+    }
+
+    for (row = rstr; row <= rstp; row += rinc)
+    {
+     for (i8 = str[8]; i8 <= stp[8]; i8 += incr[8])
+     {
+      for (i7 = str[7]; i7 <= stp[7]; i7 += incr[7])
+      {
+       for (i6 = str[6]; i6 <= stp[6]; i6 += incr[6])
+       {
+        for (i5 = str[5]; i5 <= stp[5]; i5 += incr[5])
+        {
+         for (i4 = str[4]; i4 <= stp[4]; i4 += incr[4])
+         {
+          for (i3 = str[3]; i3 <= stp[3]; i3 += incr[3])
+          {
+           for (i2 = str[2]; i2 <= stp[2]; i2 += incr[2])
+           {
+            for (i1 = str[1]; i1 <= stp[1]; i1 += incr[1])
+            {
+              felem=str[0] + (i1 - 1) * dsize[1] + (i2 - 1) * dsize[2] + 
+                             (i3 - 1) * dsize[3] + (i4 - 1) * dsize[4] +
+                             (i5 - 1) * dsize[5] + (i6 - 1) * dsize[6] +
+                             (i7 - 1) * dsize[7] + (i8 - 1) * dsize[8];
+
+              if ( ffgcluk(fptr, numcol, row, felem, nelem, ninc, nultyp,
+                   nulval, &array[i0], &ldummy, &anyf, status) > 0)
+                   return(*status);
+
+              if (anyf && anynul)
+                  *anynul = TRUE;
+
+              i0 += nelem;
+            }
+           }
+          }
+         }
+        }
+       }
+      }
+     }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgsfuk(fitsfile *fptr, /* I - FITS file pointer                         */
+           int  colnum,    /* I - number of the column to read (1 = 1st)    */
+           int naxis,      /* I - number of dimensions in the FITS array    */
+           long  *naxes,   /* I - size of each dimension                    */
+           long  *blc,     /* I - 'bottom left corner' of the subsection    */
+           long  *trc,     /* I - 'top right corner' of the subsection      */
+           long  *inc,     /* I - increment to be applied in each dimension */
+  unsigned int  *array,    /* O - array to be filled and returned           */
+           char *flagval,  /* O - set to 1 if corresponding value is null   */
+           int  *anynul,   /* O - set to 1 if any values are null; else 0   */
+           int  *status)   /* IO - error status                             */
+/*
+  Read a subsection of data values from an image or a table column.
+  This routine is set up to handle a maximum of nine dimensions.
+*/
+{
+    long ii,i0, i1,i2,i3,i4,i5,i6,i7,i8,row,rstr,rstp,rinc;
+    long str[9],stp[9],incr[9],dsize[10];
+    LONGLONG blcll[9], trcll[9];
+    long felem, nelem, nultyp, ninc, numcol;
+    long nulval = 0;
+    int hdutype, anyf;
+    char msg[FLEN_ERRMSG];
+    int nullcheck = 2;
+
+    if (naxis < 1 || naxis > 9)
+    {
+        sprintf(msg, "NAXIS = %d in call to ffgsvj is out of range", naxis);
+        ffpmsg(msg);
+        return(*status = BAD_DIMEN);
+    }
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        for (ii=0; ii < naxis; ii++) {
+	    blcll[ii] = blc[ii];
+	    trcll[ii] = trc[ii];
+	}
+
+        fits_read_compressed_img(fptr, TUINT, blcll, trcll, inc,
+            nullcheck, NULL, array, flagval, anynul, status);
+        return(*status);
+    }
+
+/*
+    if this is a primary array, then the input COLNUM parameter should
+    be interpreted as the row number, and we will alway read the image
+    data from column 2 (any group parameters are in column 1).
+*/
+    if (ffghdt(fptr, &hdutype, status) > 0)
+        return(*status);
+
+    if (hdutype == IMAGE_HDU)
+    {
+        /* this is a primary array, or image extension */
+        if (colnum == 0)
+        {
+            rstr = 1;
+            rstp = 1;
+        }
+        else
+        {
+            rstr = colnum;
+            rstp = colnum;
+        }
+        rinc = 1;
+        numcol = 2;
+    }
+    else
+    {
+        /* this is a table, so the row info is in the (naxis+1) elements */
+        rstr = blc[naxis];
+        rstp = trc[naxis];
+        rinc = inc[naxis];
+        numcol = colnum;
+    }
+
+    nultyp = 2;
+    if (anynul)
+        *anynul = FALSE;
+
+    i0 = 0;
+    for (ii = 0; ii < 9; ii++)
+    {
+        str[ii] = 1;
+        stp[ii] = 1;
+        incr[ii] = 1;
+        dsize[ii] = 1;
+    }
+
+    for (ii = 0; ii < naxis; ii++)
+    {
+      if (trc[ii] < blc[ii])
+      {
+        sprintf(msg, "ffgsvj: illegal range specified for axis %ld", ii + 1);
+        ffpmsg(msg);
+        return(*status = BAD_PIX_NUM);
+      }
+
+      str[ii] = blc[ii];
+      stp[ii] = trc[ii];
+      incr[ii] = inc[ii];
+      dsize[ii + 1] = dsize[ii] * naxes[ii];
+    }
+
+    if (naxis == 1 && naxes[0] == 1)
+    {
+      /* This is not a vector column, so read all the rows at once */
+      nelem = (rstp - rstr) / rinc + 1;
+      ninc = rinc;
+      rstp = rstr;
+    }
+    else
+    {
+      /* have to read each row individually, in all dimensions */
+      nelem = (stp[0] - str[0]) / inc[0] + 1;
+      ninc = incr[0];
+    }
+
+    for (row = rstr; row <= rstp; row += rinc)
+    {
+     for (i8 = str[8]; i8 <= stp[8]; i8 += incr[8])
+     {
+      for (i7 = str[7]; i7 <= stp[7]; i7 += incr[7])
+      {
+       for (i6 = str[6]; i6 <= stp[6]; i6 += incr[6])
+       {
+        for (i5 = str[5]; i5 <= stp[5]; i5 += incr[5])
+        {
+         for (i4 = str[4]; i4 <= stp[4]; i4 += incr[4])
+         {
+          for (i3 = str[3]; i3 <= stp[3]; i3 += incr[3])
+          {
+           for (i2 = str[2]; i2 <= stp[2]; i2 += incr[2])
+           {
+            for (i1 = str[1]; i1 <= stp[1]; i1 += incr[1])
+            {
+              felem=str[0] + (i1 - 1) * dsize[1] + (i2 - 1) * dsize[2] + 
+                             (i3 - 1) * dsize[3] + (i4 - 1) * dsize[4] +
+                             (i5 - 1) * dsize[5] + (i6 - 1) * dsize[6] +
+                             (i7 - 1) * dsize[7] + (i8 - 1) * dsize[8];
+
+              if ( ffgcluk(fptr, numcol, row, felem, nelem, ninc, nultyp,
+                   nulval, &array[i0], &flagval[i0], &anyf, status) > 0)
+                   return(*status);
+
+              if (anyf && anynul)
+                  *anynul = TRUE;
+
+              i0 += nelem;
+            }
+           }
+          }
+         }
+        }
+       }
+      }
+     }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffggpuk( fitsfile *fptr,   /* I - FITS file pointer                       */
+            long  group,      /* I - group to read (1 = 1st group)           */
+            long  firstelem,  /* I - first vector element to read (1 = 1st)  */
+            long  nelem,      /* I - number of values to read                */
+   unsigned int  *array,     /* O - array of values that are returned       */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of group parameters from the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being read).
+*/
+{
+    long row;
+    int idummy;
+    char cdummy;
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffgcluk(fptr, 1, row, firstelem, nelem, 1, 1, 0L,
+               array, &cdummy, &idummy, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcvuk(fitsfile *fptr,   /* I - FITS file pointer                       */
+           int  colnum,      /* I - number of column to read (1 = 1st col)  */
+           LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+           LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+           LONGLONG  nelem,      /* I - number of values to read                */
+  unsigned int   nulval,     /* I - value for null pixels                   */
+  unsigned int  *array,      /* O - array of values that are read           */
+           int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+           int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from a column in the current FITS HDU. Automatic
+  datatype conversion will be performed if the datatype of the column does not
+  match the datatype of the array parameter. The output values will be scaled 
+  by the FITS TSCALn and TZEROn values if these values have been defined.
+  Any undefined pixels will be set equal to the value of 'nulval' unless
+  nulval = 0 in which case no checks for undefined pixels will be made.
+*/
+{
+    char cdummy;
+
+    ffgcluk(fptr, colnum, firstrow, firstelem, nelem, 1, 1, nulval,
+           array, &cdummy, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcfuk(fitsfile *fptr,   /* I - FITS file pointer                       */
+           int  colnum,      /* I - number of column to read (1 = 1st col)  */
+           LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+           LONGLONG  firstelem,  /* I - first vector element to read (1 = 1st)  */
+           LONGLONG  nelem,      /* I - number of values to read                */
+  unsigned int   *array,     /* O - array of values that are read           */
+           char *nularray,   /* O - array of flags: 1 if null pixel; else 0 */
+           int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+           int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from a column in the current FITS HDU. Automatic
+  datatype conversion will be performed if the datatype of the column does not
+  match the datatype of the array parameter. The output values will be scaled 
+  by the FITS TSCALn and TZEROn values if these values have been defined.
+  Nularray will be set = 1 if the corresponding array pixel is undefined, 
+  otherwise nularray will = 0.
+*/
+{
+    int dummy = 0;
+
+    ffgcluk(fptr, colnum, firstrow, firstelem, nelem, 1, 2, dummy,
+           array, nularray, anynul, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcluk( fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  colnum,      /* I - number of column to read (1 = 1st col)  */
+            LONGLONG  firstrow,   /* I - first row to read (1 = 1st row)         */
+            LONGLONG firstelem,  /* I - first vector element to read (1 = 1st)  */
+            LONGLONG  nelem,      /* I - number of values to read                */
+            long  elemincre,  /* I - pixel increment; e.g., 2 = every other  */
+            int   nultyp,     /* I - null value handling code:               */
+                              /*     1: set undefined pixels = nulval        */
+                              /*     2: set nularray=1 for undefined pixels  */
+   unsigned int   nulval,     /* I - value for null pixels if nultyp = 1     */
+   unsigned int  *array,      /* O - array of values that are read           */
+            char *nularray,   /* O - array of flags = 1 if nultyp = 2        */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+  Read an array of values from a column in the current FITS HDU.
+  The column number may refer to a real column in an ASCII or binary table, 
+  or it may refer be a virtual column in a 1 or more grouped FITS primary
+  array or image extension.  FITSIO treats a primary array as a binary table
+  with 2 vector columns: the first column contains the group parameters (often
+  with length = 0) and the second column contains the array of image pixels.
+  Each row of the table represents a group in the case of multigroup FITS
+  images.
+
+  The output array of values will be converted from the datatype of the column 
+  and will be scaled by the FITS TSCALn and TZEROn values if necessary.
+*/
+{
+    double scale, zero, power = 1., dtemp;
+    int tcode, maxelem, hdutype, xcode, decimals;
+    long twidth, incre;
+    long ii, xwidth, ntodo;
+    int nulcheck;
+    LONGLONG repeat, startpos, elemnum, readptr, tnull;
+    LONGLONG rowlen, rownum, remain, next, rowincre;
+    char tform[20];
+    char message[81];
+    char snull[20];   /*  the FITS null value if reading from ASCII table  */
+
+    double cbuff[DBUFFSIZE / sizeof(double)]; /* align cbuff on word boundary */
+    void *buffer;
+
+    if (*status > 0 || nelem == 0)  /* inherit input status value if > 0 */
+        return(*status);
+
+    /* call the 'short' or 'long' version of this routine, if possible */
+    if (sizeof(int) == sizeof(short))
+        ffgclui(fptr, colnum, firstrow, firstelem, nelem, elemincre, nultyp,
+          (unsigned short) nulval, (unsigned short *) array, nularray, anynul,
+           status);
+    else if (sizeof(int) == sizeof(long))
+        ffgcluj(fptr, colnum, firstrow, firstelem, nelem, elemincre, nultyp,
+          (unsigned long) nulval, (unsigned long *) array, nularray, anynul,
+          status);
+    else
+    {
+    /*
+      This is a special case: sizeof(int) is not equal to sizeof(short) or
+      sizeof(long).  This occurs on Alpha OSF systems where short = 2 bytes,
+      int = 4 bytes, and long = 8 bytes.
+    */
+
+    buffer = cbuff;
+
+    if (anynul)
+        *anynul = 0;
+
+    if (nultyp == 2)
+        memset(nularray, 0, (size_t) nelem);   /* initialize nullarray */
+
+    /*---------------------------------------------------*/
+    /*  Check input and get parameters about the column: */
+    /*---------------------------------------------------*/
+    if ( ffgcprll( fptr, colnum, firstrow, firstelem, nelem, 0, &scale, &zero,
+         tform, &twidth, &tcode, &maxelem, &startpos, &elemnum, &incre,
+         &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0 )
+         return(*status);
+
+    incre *= elemincre;   /* multiply incre to just get every nth pixel */
+
+    if (tcode == TSTRING)    /* setup for ASCII tables */
+    {
+      /* get the number of implied decimal places if no explicit decmal point */
+      ffasfm(tform, &xcode, &xwidth, &decimals, status); 
+      for(ii = 0; ii < decimals; ii++)
+        power *= 10.;
+    }
+    /*------------------------------------------------------------------*/
+    /*  Decide whether to check for null values in the input FITS file: */
+    /*------------------------------------------------------------------*/
+    nulcheck = nultyp; /* by default check for null values in the FITS file */
+
+    if (nultyp == 1 && nulval == 0)
+       nulcheck = 0;    /* calling routine does not want to check for nulls */
+
+    else if (tcode%10 == 1 &&        /* if reading an integer column, and  */ 
+            tnull == NULL_UNDEFINED) /* if a null value is not defined,    */
+            nulcheck = 0;            /* then do not check for null values. */
+
+    else if (tcode == TSHORT && (tnull > SHRT_MAX || tnull < SHRT_MIN) )
+            nulcheck = 0;            /* Impossible null value */
+
+    else if (tcode == TBYTE && (tnull > 255 || tnull < 0) )
+            nulcheck = 0;            /* Impossible null value */
+
+    else if (tcode == TSTRING && snull[0] == ASCII_NULL_UNDEFINED)
+         nulcheck = 0;
+
+    /*----------------------------------------------------------------------*/
+    /*  If FITS column and output data array have same datatype, then we do */
+    /*  not need to use a temporary buffer to store intermediate datatype.  */
+    /*----------------------------------------------------------------------*/
+    if (tcode == TLONG)  /* Special Case: */
+    {                             /* data are 4-bytes long, so read       */
+        maxelem = (int) nelem;          /* data directly into output buffer.    */
+    }
+
+    /*---------------------------------------------------------------------*/
+    /*  Now read the pixels from the FITS column. If the column does not   */
+    /*  have the same datatype as the output array, then we have to read   */
+    /*  the raw values into a temporary buffer (of limited size).  In      */
+    /*  the case of a vector colum read only 1 vector of values at a time  */
+    /*  then skip to the next row if more values need to be read.          */
+    /*  After reading the raw values, then call the fffXXYY routine to (1) */
+    /*  test for undefined values, (2) convert the datatype if necessary,  */
+    /*  and (3) scale the values by the FITS TSCALn and TZEROn linear      */
+    /*  scaling parameters.                                                */
+    /*---------------------------------------------------------------------*/
+    remain = nelem;           /* remaining number of values to read */
+    next = 0;                 /* next element in array to be read   */
+    rownum = 0;               /* row number, relative to firstrow   */
+
+    while (remain)
+    {
+        /* limit the number of pixels to read at one time to the number that
+           will fit in the buffer or to the number of pixels that remain in
+           the current vector, which ever is smaller.
+        */
+        ntodo = (long) minvalue(remain, maxelem);      
+        ntodo = (long) minvalue(ntodo, ((repeat - elemnum - 1)/elemincre +1));
+
+        readptr = startpos + ((LONGLONG)rownum * rowlen) + (elemnum * (incre / elemincre));
+
+        switch (tcode) 
+        {
+            case (TLONG):
+                ffgi4b(fptr, readptr, ntodo, incre, (INT32BIT *) &array[next],
+                       status);
+                    fffi4uint((INT32BIT *) &array[next], ntodo, scale, zero, 
+                           nulcheck, (INT32BIT) tnull, nulval, &nularray[next],
+                           anynul, &array[next], status);
+                break;
+            case (TLONGLONG):
+
+                ffgi8b(fptr, readptr, ntodo, incre, (long *) buffer, status);
+                fffi8uint( (LONGLONG *) buffer, ntodo, scale, zero, 
+                           nulcheck, tnull, nulval, &nularray[next], 
+                            anynul, &array[next], status);
+                break;
+            case (TBYTE):
+                ffgi1b(fptr, readptr, ntodo, incre, (unsigned char *) buffer,
+                       status);
+                fffi1uint((unsigned char *) buffer, ntodo, scale, zero,nulcheck,
+                     (unsigned char) tnull, nulval, &nularray[next], anynul, 
+                     &array[next], status);
+                break;
+            case (TSHORT):
+                ffgi2b(fptr, readptr, ntodo, incre, (short  *) buffer, status);
+                fffi2uint((short  *) buffer, ntodo, scale, zero, nulcheck, 
+                      (short) tnull, nulval, &nularray[next], anynul, 
+                      &array[next], status);
+                break;
+            case (TFLOAT):
+                ffgr4b(fptr, readptr, ntodo, incre, (float  *) buffer, status);
+                fffr4uint((float  *) buffer, ntodo, scale, zero, nulcheck, 
+                       nulval, &nularray[next], anynul, 
+                       &array[next], status);
+                break;
+            case (TDOUBLE):
+                ffgr8b(fptr, readptr, ntodo, incre, (double *) buffer, status);
+                fffr8uint((double *) buffer, ntodo, scale, zero, nulcheck, 
+                          nulval, &nularray[next], anynul, 
+                          &array[next], status);
+                break;
+            case (TSTRING):
+                ffmbyt(fptr, readptr, REPORT_EOF, status);
+       
+                if (incre == twidth)    /* contiguous bytes */
+                     ffgbyt(fptr, ntodo * twidth, buffer, status);
+                else
+                     ffgbytoff(fptr, twidth, ntodo, incre - twidth, buffer,
+                               status);
+
+                fffstruint((char *) buffer, ntodo, scale, zero, twidth, power,
+                     nulcheck, snull, nulval, &nularray[next], anynul,
+                     &array[next], status);
+                break;
+
+            default:  /*  error trap for invalid column format */
+                sprintf(message, 
+                   "Cannot read numbers from column %d which has format %s",
+                    colnum, tform);
+                ffpmsg(message);
+                if (hdutype == ASCII_TBL)
+                    return(*status = BAD_ATABLE_FORMAT);
+                else
+                    return(*status = BAD_BTABLE_FORMAT);
+
+        } /* End of switch block */
+
+        /*-------------------------*/
+        /*  Check for fatal error  */
+        /*-------------------------*/
+        if (*status > 0)  /* test for error during previous read operation */
+        {
+	  dtemp = (double) next;
+          if (hdutype > 0)
+            sprintf(message,
+            "Error reading elements %.0f thru %.0f from column %d (ffgcluk).",
+              dtemp+1., dtemp+ntodo, colnum);
+          else
+            sprintf(message,
+            "Error reading elements %.0f thru %.0f from image (ffgcluk).",
+              dtemp+1., dtemp+ntodo);
+
+          ffpmsg(message);
+          return(*status);
+        }
+
+        /*--------------------------------------------*/
+        /*  increment the counters for the next loop  */
+        /*--------------------------------------------*/
+        remain -= ntodo;
+        if (remain)
+        {
+            next += ntodo;
+            elemnum = elemnum + (ntodo * elemincre);
+
+            if (elemnum >= repeat)  /* completed a row; start on later row */
+            {
+                rowincre = elemnum / repeat;
+                rownum += rowincre;
+                elemnum = elemnum - (rowincre * repeat);
+            }
+        }
+    }  /*  End of main while Loop  */
+
+
+    /*--------------------------------*/
+    /*  check for numerical overflow  */
+    /*--------------------------------*/
+    if (*status == OVERFLOW_ERR)
+    {
+        ffpmsg(
+        "Numerical overflow during type conversion while reading FITS data.");
+        *status = NUM_OVERFLOW;
+    }
+
+    }  /* end of DEC Alpha special case */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi1uint(unsigned char *input,/* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            unsigned char tnull,  /* I - value of FITS TNULLn keyword if any */
+   unsigned int  nullval,         /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+   unsigned int  *output,         /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (unsigned int) input[ii];  /* copy input */
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DUINT_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else if (dvalue > DUINT_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = UINT_MAX;
+                }
+                else
+                    output[ii] = (unsigned int) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                    output[ii] = (unsigned int) input[ii];
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DUINT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (dvalue > DUINT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = UINT_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned int) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi2uint(short *input,        /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            short tnull,          /* I - value of FITS TNULLn keyword if any */
+   unsigned int  nullval,         /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+   unsigned int  *output,         /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] < 0)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else
+                    output[ii] = (unsigned int) input[ii];
+            }
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DUINT_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else if (dvalue > DUINT_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = UINT_MAX;
+                }
+                else
+                    output[ii] = (unsigned int) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    if (input[ii] < 0)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else
+                        output[ii] = (unsigned int) input[ii];
+                }
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DUINT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (dvalue > DUINT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = UINT_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned int) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi4uint(INT32BIT *input,    /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            INT32BIT tnull,       /* I - value of FITS TNULLn keyword if any */
+   unsigned int  nullval,         /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+   unsigned int  *output,         /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 2147483648.)
+        {       
+           /* Instead of adding 2147483648, it is more efficient */
+           /* to just flip the sign bit with the XOR operator */
+
+            for (ii = 0; ii < ntodo; ii++)
+               output[ii] =  ( *(unsigned int *) &input[ii] ) ^ 0x80000000;
+        }
+        else if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] < 0)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else
+                    output[ii] = (unsigned int) input[ii]; /* copy to output */
+            }
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DUINT_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else if (dvalue > DUINT_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = UINT_MAX;
+                }
+                else
+                    output[ii] = (unsigned int) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 2147483648.) 
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                   output[ii] =  ( *(unsigned int *) &input[ii] ) ^ 0x80000000;
+            }
+        }
+        else if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else if (input[ii] < 0)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else
+                    output[ii] = (unsigned int) input[ii];
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DUINT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (dvalue > DUINT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = UINT_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned int) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffi8uint(LONGLONG *input,    /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            LONGLONG tnull,       /* I - value of FITS TNULLn keyword if any */
+   unsigned int  nullval,         /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+   unsigned int  *output,         /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to tnull.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] < 0)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else if (input[ii] > UINT_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = UINT_MAX;
+                }
+                else
+                    output[ii] = (unsigned int) input[ii];
+            }
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DUINT_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else if (dvalue > DUINT_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = UINT_MAX;
+                }
+                else
+                    output[ii] = (unsigned int) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    if (input[ii] < 0)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (input[ii] > UINT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = UINT_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned int) input[ii];
+                }
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DUINT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (dvalue > DUINT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = UINT_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned int) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffr4uint(float *input,        /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+   unsigned int  nullval,         /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+   unsigned int  *output,         /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to NaN.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+    short *sptr, iret;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] < DUINT_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else if (input[ii] > DUINT_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = UINT_MAX;
+                }
+                else
+                    output[ii] = (unsigned int) input[ii];
+            }
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DUINT_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else if (dvalue > DUINT_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = UINT_MAX;
+                }
+                else
+                    output[ii] = (unsigned int) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        sptr = (short *) input;
+
+#if BYTESWAPPED && MACHINE != VAXVMS && MACHINE != ALPHAVMS
+        sptr++;       /* point to MSBs */
+#endif
+
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++, sptr += 2)
+            {
+              if (0 != (iret = fnan(*sptr) ) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                  }
+                  else            /* it's an underflow */
+                     output[ii] = 0;
+              }
+              else
+                {
+                    if (input[ii] < DUINT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (input[ii] > DUINT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = UINT_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned int) input[ii];
+                }
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++, sptr += 2)
+            {
+              if (0 != (iret = fnan(*sptr) ) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {  
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                  }
+                  else            /* it's an underflow */
+                  { 
+                    if (zero < DUINT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (zero > DUINT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = UINT_MAX;
+                    }
+                    else
+                      output[ii] = (unsigned int) zero;
+                  }
+              }
+              else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DUINT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (dvalue > DUINT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = UINT_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned int) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffr8uint(double *input,       /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+   unsigned int  nullval,         /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+   unsigned int  *output,         /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file.
+  Check for null values and do datatype conversion and scaling if required.
+  The nullcheck code value determines how any null values in the input array
+  are treated.  A null value is an input pixel that is equal to NaN.  If 
+  nullcheck = 0, then no checking for nulls is performed and any null values
+  will be transformed just like any other pixel.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    long ii;
+    double dvalue;
+    short *sptr, iret;
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+        if (scale == 1. && zero == 0.)      /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] < DUINT_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else if (input[ii] > DUINT_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = UINT_MAX;
+                }
+                else
+                    output[ii] = (unsigned int) input[ii];
+            }
+        }
+        else             /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                dvalue = input[ii] * scale + zero;
+
+                if (dvalue < DUINT_MIN)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = 0;
+                }
+                else if (dvalue > DUINT_MAX)
+                {
+                    *status = OVERFLOW_ERR;
+                    output[ii] = UINT_MAX;
+                }
+                else
+                    output[ii] = (unsigned int) dvalue;
+            }
+        }
+    }
+    else        /* must check for null values */
+    {
+        sptr = (short *) input;
+
+#if BYTESWAPPED && MACHINE != VAXVMS && MACHINE != ALPHAVMS
+        sptr += 3;       /* point to MSBs */
+#endif
+        if (scale == 1. && zero == 0.)  /* no scaling */
+        {       
+            for (ii = 0; ii < ntodo; ii++, sptr += 4)
+            {
+              if (0 != (iret = dnan(*sptr)) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                  }
+                  else            /* it's an underflow */
+                     output[ii] = 0;
+              }
+              else
+                {
+                    if (input[ii] < DUINT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (input[ii] > DUINT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = UINT_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned int) input[ii];
+                }
+            }
+        }
+        else                  /* must scale the data */
+        {
+            for (ii = 0; ii < ntodo; ii++, sptr += 4)
+            {
+              if (0 != (iret = dnan(*sptr)) )  /* test for NaN or underflow */
+              {
+                  if (iret == 1)  /* is it a NaN? */
+                  {  
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                  }
+                  else            /* it's an underflow */
+                  { 
+                    if (zero < DUINT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (zero > DUINT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = UINT_MAX;
+                    }
+                    else
+                      output[ii] = (unsigned int) zero;
+                  }
+              }
+              else
+                {
+                    dvalue = input[ii] * scale + zero;
+
+                    if (dvalue < DUINT_MIN)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = 0;
+                    }
+                    else if (dvalue > DUINT_MAX)
+                    {
+                        *status = OVERFLOW_ERR;
+                        output[ii] = UINT_MAX;
+                    }
+                    else
+                        output[ii] = (unsigned int) dvalue;
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffstruint(char *input,        /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            long twidth,          /* I - width of each substring of chars    */
+            double implipower,    /* I - power of 10 of implied decimal      */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            char  *snull,         /* I - value of FITS null string, if any   */
+   unsigned int nullval,          /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+   unsigned int *output,          /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+  Copy input to output following reading of the input from a FITS file. Check
+  for null values and do scaling if required. The nullcheck code value
+  determines how any null values in the input array are treated. A null
+  value is an input pixel that is equal to snull.  If nullcheck= 0, then
+  no special checking for nulls is performed.  If nullcheck = 1, then the
+  output pixel will be set = nullval if the corresponding input pixel is null.
+  If nullcheck = 2, then if the pixel is null then the corresponding value of
+  nullarray will be set to 1; the value of nullarray for non-null pixels 
+  will = 0.  The anynull parameter will be set = 1 if any of the returned
+  pixels are null, otherwise anynull will be returned with a value = 0;
+*/
+{
+    int nullen;
+    long ii;
+    double dvalue;
+    char *cstring, message[81];
+    char *cptr, *tpos;
+    char tempstore, chrzero = '0';
+    double val, power;
+    int exponent, sign, esign, decpt;
+
+    nullen = strlen(snull);
+    cptr = input;  /* pointer to start of input string */
+    for (ii = 0; ii < ntodo; ii++)
+    {
+      cstring = cptr;
+      /* temporarily insert a null terminator at end of the string */
+      tpos = cptr + twidth;
+      tempstore = *tpos;
+      *tpos = 0;
+
+      /* check if null value is defined, and if the    */
+      /* column string is identical to the null string */
+      if (snull[0] != ASCII_NULL_UNDEFINED && 
+         !strncmp(snull, cptr, nullen) )
+      {
+        if (nullcheck)  
+        {
+          *anynull = 1;    
+          if (nullcheck == 1)
+            output[ii] = nullval;
+          else
+            nullarray[ii] = 1;
+        }
+        cptr += twidth;
+      }
+      else
+      {
+        /* value is not the null value, so decode it */
+        /* remove any embedded blank characters from the string */
+
+        decpt = 0;
+        sign = 1;
+        val  = 0.;
+        power = 1.;
+        exponent = 0;
+        esign = 1;
+
+        while (*cptr == ' ')               /* skip leading blanks */
+           cptr++;
+
+        if (*cptr == '-' || *cptr == '+')  /* check for leading sign */
+        {
+          if (*cptr == '-')
+             sign = -1;
+
+          cptr++;
+
+          while (*cptr == ' ')         /* skip blanks between sign and value */
+            cptr++;
+        }
+
+        while (*cptr >= '0' && *cptr <= '9')
+        {
+          val = val * 10. + *cptr - chrzero;  /* accumulate the value */
+          cptr++;
+
+          while (*cptr == ' ')         /* skip embedded blanks in the value */
+            cptr++;
+        }
+
+        if (*cptr == '.' || *cptr == ',')       /* check for decimal point */
+        {
+          decpt = 1;       /* set flag to show there was a decimal point */
+          cptr++;
+          while (*cptr == ' ')         /* skip any blanks */
+            cptr++;
+
+          while (*cptr >= '0' && *cptr <= '9')
+          {
+            val = val * 10. + *cptr - chrzero;  /* accumulate the value */
+            power = power * 10.;
+            cptr++;
+
+            while (*cptr == ' ')         /* skip embedded blanks in the value */
+              cptr++;
+          }
+        }
+
+        if (*cptr == 'E' || *cptr == 'D')  /* check for exponent */
+        {
+          cptr++;
+          while (*cptr == ' ')         /* skip blanks */
+              cptr++;
+  
+          if (*cptr == '-' || *cptr == '+')  /* check for exponent sign */
+          {
+            if (*cptr == '-')
+               esign = -1;
+
+            cptr++;
+
+            while (*cptr == ' ')        /* skip blanks between sign and exp */
+              cptr++;
+          }
+
+          while (*cptr >= '0' && *cptr <= '9')
+          {
+            exponent = exponent * 10 + *cptr - chrzero;  /* accumulate exp */
+            cptr++;
+
+            while (*cptr == ' ')         /* skip embedded blanks */
+              cptr++;
+          }
+        }
+
+        if (*cptr  != 0)  /* should end up at the null terminator */
+        {
+          sprintf(message, "Cannot read number from ASCII table");
+          ffpmsg(message);
+          sprintf(message, "Column field = %s.", cstring);
+          ffpmsg(message);
+          /* restore the char that was overwritten by the null */
+          *tpos = tempstore;
+          return(*status = BAD_C2D);
+        }
+
+        if (!decpt)  /* if no explicit decimal, use implied */
+           power = implipower;
+
+        dvalue = (sign * val / power) * pow(10., (double) (esign * exponent));
+
+        dvalue = dvalue * scale + zero;   /* apply the scaling */
+
+        if (dvalue < DUINT_MIN)
+        {
+            *status = OVERFLOW_ERR;
+            output[ii] = 0;
+        }
+        else if (dvalue > DUINT_MAX)
+        {
+            *status = OVERFLOW_ERR;
+            output[ii] = UINT_MAX;
+        }
+        else
+            output[ii] = (long) dvalue;
+      }
+      /* restore the char that was overwritten by the null */
+      *tpos = tempstore;
+    }
+    return(*status);
+}
diff --git a/src/plugins/cfitsio/getkey.c b/src/plugins/cfitsio/getkey.c
new file mode 100644
index 0000000..3184779
--- /dev/null
+++ b/src/plugins/cfitsio/getkey.c
@@ -0,0 +1,3241 @@
+/*  This file, getkey.c, contains routines that read keywords from         */
+/*  a FITS header.                                                         */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+
+#include <string.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <ctype.h>
+/* stddef.h is apparently needed to define size_t */
+#include <stddef.h>
+#include "fitsio2.h"
+
+/*--------------------------------------------------------------------------*/
+int ffghsp(fitsfile *fptr,  /* I - FITS file pointer                     */
+           int *nexist,     /* O - number of existing keywords in header */
+           int *nmore,      /* O - how many more keywords will fit       */
+           int *status)     /* IO - error status                         */
+/*
+  returns the number of existing keywords (not counting the END keyword)
+  and the number of more keyword that will fit in the current header 
+  without having to insert more FITS blocks.
+*/
+{
+    if (*status > 0)
+        return(*status);
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    if (nexist)
+        *nexist = (int) (( ((fptr->Fptr)->headend) - 
+                ((fptr->Fptr)->headstart[(fptr->Fptr)->curhdu]) ) / 80);
+
+    if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+    {
+      if (nmore)
+        *nmore = -1;   /* data not written yet, so room for any keywords */
+    }
+    else
+    {
+      /* calculate space available between the data and the END card */
+      if (nmore)
+        *nmore = (int) (((fptr->Fptr)->datastart - (fptr->Fptr)->headend) / 80 - 1);
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffghps(fitsfile *fptr, /* I - FITS file pointer                     */
+          int *nexist,     /* O - number of existing keywords in header */
+          int *position,   /* O - position of next keyword to be read   */
+          int *status)     /* IO - error status                         */
+/*
+  return the number of existing keywords and the position of the next
+  keyword that will be read.
+*/
+{
+    if (*status > 0)
+        return(*status);
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+  *nexist = (int) (( ((fptr->Fptr)->headend) - ((fptr->Fptr)->headstart[(fptr->Fptr)->curhdu]) ) / 80);
+  *position = (int) (( ((fptr->Fptr)->nextkey) - ((fptr->Fptr)->headstart[(fptr->Fptr)->curhdu]) ) / 80 + 1);
+  return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffnchk(fitsfile *fptr,  /* I - FITS file pointer                     */
+           int *status)     /* IO - error status                         */
+/*
+  function returns the position of the first null character (ASCII 0), if
+  any, in the current header.  Null characters are illegal, but the other
+  CFITSIO routines that read the header will not detect this error, because
+  the null gets interpreted as a normal end of string character.
+*/
+{
+    long ii, nblock;
+    LONGLONG bytepos;
+    int length, nullpos;
+    char block[2881];
+    
+    if (*status > 0)
+        return(*status);
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+    {
+        return(0);  /* Don't check a file that is just being created.  */
+                    /* It cannot contain nulls since CFITSIO wrote it. */
+    }
+    else
+    {
+        /* calculate number of blocks in the header */
+        nblock = (long) (( (fptr->Fptr)->datastart - 
+                   (fptr->Fptr)->headstart[(fptr->Fptr)->curhdu] ) / 2880);
+    }
+
+    bytepos = (fptr->Fptr)->headstart[(fptr->Fptr)->curhdu];
+    ffmbyt(fptr, bytepos, REPORT_EOF, status);  /* move to read pos. */
+
+    block[2880] = '\0';
+    for (ii = 0; ii < nblock; ii++)
+    {
+        if (ffgbyt(fptr, 2880, block, status) > 0)
+            return(0);   /* read error of some sort */
+
+        length = strlen(block);
+        if (length != 2880)
+        {
+            nullpos = (ii * 2880) + length + 1;
+            return(nullpos);
+        }
+    }
+
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int ffmaky(fitsfile *fptr,    /* I - FITS file pointer                    */
+          int nrec,           /* I - one-based keyword number to move to  */
+          int *status)        /* IO - error status                        */
+{
+/*
+  move pointer to the specified absolute keyword position.  E.g. this keyword 
+  will then be read by the next call to ffgnky.
+*/
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    (fptr->Fptr)->nextkey = (fptr->Fptr)->headstart[(fptr->Fptr)->curhdu] + ( (nrec - 1) * 80);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffmrky(fitsfile *fptr,    /* I - FITS file pointer                   */
+          int nmove,          /* I - relative number of keywords to move */
+          int *status)        /* IO - error status                       */
+{
+/*
+  move pointer to the specified keyword position relative to the current
+  position.  E.g. this keyword  will then be read by the next call to ffgnky.
+*/
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    (fptr->Fptr)->nextkey += (nmove * 80);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgnky(fitsfile *fptr,  /* I - FITS file pointer     */
+           char *card,      /* O - card string           */
+           int *status)     /* IO - error status         */
+/*
+  read the next keyword from the header - used internally by cfitsio
+*/
+{
+    int jj, nrec;
+    LONGLONG bytepos, endhead;
+    char message[FLEN_ERRMSG];
+
+    if (*status > 0)
+        return(*status);
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    card[0] = '\0';  /* make sure card is terminated, even affer read error */
+
+/*
+  Check that nextkey points to a legal keyword position.  Note that headend
+  is the current end of the header, i.e., the position where a new keyword
+  would be appended, however, if there are more than 1 FITS block worth of
+  blank keywords at the end of the header (36 keywords per 2880 byte block)
+  then the actual physical END card must be located at a starting position
+  which is just 2880 bytes prior to the start of the data unit.
+*/
+
+    bytepos = (fptr->Fptr)->nextkey;
+    endhead = maxvalue( ((fptr->Fptr)->headend), ((fptr->Fptr)->datastart - 2880) );
+
+    /* nextkey must be < endhead and > than  headstart */
+    if (bytepos > endhead ||  
+        bytepos < (fptr->Fptr)->headstart[(fptr->Fptr)->curhdu] ) 
+    {
+        nrec= (int) ((bytepos - (fptr->Fptr)->headstart[(fptr->Fptr)->curhdu]) / 80 + 1);
+        sprintf(message, "Cannot get keyword number %d.  It does not exist.",
+                nrec);
+        ffpmsg(message);
+        return(*status = KEY_OUT_BOUNDS);
+    }
+      
+    ffmbyt(fptr, bytepos, REPORT_EOF, status);  /* move to read pos. */
+
+    card[80] = '\0';  /* make sure card is terminate, even if ffgbyt fails */
+
+    if (ffgbyt(fptr, 80, card, status) <= 0) 
+    {
+        (fptr->Fptr)->nextkey += 80;   /* increment pointer to next keyword */
+
+        /* strip off trailing blanks with terminated string */
+        jj = 79;
+        while (jj >= 0 && card[jj] == ' ')
+               jj--;
+
+        card[jj + 1] = '\0';
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgnxk( fitsfile *fptr,     /* I - FITS file pointer              */
+            char **inclist,     /* I - list of included keyword names */
+            int ninc,           /* I - number of names in inclist     */
+            char **exclist,     /* I - list of excluded keyword names */
+            int nexc,           /* I - number of names in exclist     */
+            char *card,         /* O - first matching keyword         */
+            int  *status)       /* IO - error status                  */
+/*
+    Return the next keyword that matches one of the names in inclist
+    but does not match any of the names in exclist.  The search
+    goes from the current position to the end of the header, only.
+    Wild card characters may be used in the name lists ('*', '?' and '#').
+*/
+{
+    int casesn, match, exact, namelen;
+    long ii, jj;
+    char keybuf[FLEN_CARD], keyname[FLEN_KEYWORD];
+
+    card[0] = '\0';
+    if (*status > 0)
+        return(*status);
+
+    casesn = FALSE;
+
+    /* get next card, and return with an error if hit end of header */
+    while( ffgcrd(fptr, "*", keybuf, status) <= 0)
+    {
+        ffgknm(keybuf, keyname, &namelen, status); /* get the keyword name */
+        
+        /* does keyword match any names in the include list? */
+        for (ii = 0; ii < ninc; ii++)
+        {
+            ffcmps(inclist[ii], keyname, casesn, &match, &exact);
+            if (match)
+            {
+                /* does keyword match any names in the exclusion list? */
+                jj = -1;
+                while ( ++jj < nexc )
+                {
+                    ffcmps(exclist[jj], keyname, casesn, &match, &exact);
+                    if (match)
+                        break;
+                }
+
+                if (jj >= nexc)
+                {
+                    /* not in exclusion list, so return this keyword */
+                    strcat(card, keybuf);
+                    return(*status);
+                }
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgky( fitsfile *fptr,     /* I - FITS file pointer        */
+           int  datatype,      /* I - datatype of the value    */
+           const char *keyname,      /* I - name of keyword to read  */
+           void *value,        /* O - keyword value            */
+           char *comm,         /* O - keyword comment          */
+           int  *status)       /* IO - error status            */
+/*
+  Read (get) the keyword value and comment from the FITS header.
+  Reads a keyword value with the datatype specified by the 2nd argument.
+*/
+{
+    long longval;
+    double doubleval;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (datatype == TSTRING)
+    {
+        ffgkys(fptr, keyname, (char *) value, comm, status);
+    }
+    else if (datatype == TBYTE)
+    {
+        if (ffgkyj(fptr, keyname, &longval, comm, status) <= 0)
+        {
+            if (longval > UCHAR_MAX || longval < 0)
+                *status = NUM_OVERFLOW;
+            else
+                *(unsigned char *) value = (unsigned char) longval;
+        }
+    }
+    else if (datatype == TSBYTE)
+    {
+        if (ffgkyj(fptr, keyname, &longval, comm, status) <= 0)
+        {
+            if (longval > 127 || longval < -128)
+                *status = NUM_OVERFLOW;
+            else
+                *(signed char *) value = (signed char) longval;
+        }
+    }
+    else if (datatype == TUSHORT)
+    {
+        if (ffgkyj(fptr, keyname, &longval, comm, status) <= 0)
+        {
+            if (longval > (long) USHRT_MAX || longval < 0)
+                *status = NUM_OVERFLOW;
+            else
+                *(unsigned short *) value = (unsigned short) longval;
+        }
+    }
+    else if (datatype == TSHORT)
+    {
+        if (ffgkyj(fptr, keyname, &longval, comm, status) <= 0)
+        {
+            if (longval > SHRT_MAX || longval < SHRT_MIN)
+                *status = NUM_OVERFLOW;
+            else
+                *(short *) value = (short) longval;
+        }
+    }
+    else if (datatype == TUINT)
+    {
+        if (ffgkyj(fptr, keyname, &longval, comm, status) <= 0)
+        {
+            if (longval > (long) UINT_MAX || longval < 0)
+                *status = NUM_OVERFLOW;
+            else
+                *(unsigned int *) value = longval;
+        }
+    }
+    else if (datatype == TINT)
+    {
+        if (ffgkyj(fptr, keyname, &longval, comm, status) <= 0)
+        {
+            if (longval > INT_MAX || longval < INT_MIN)
+                *status = NUM_OVERFLOW;
+            else
+                *(int *) value = longval;
+        }
+    }
+    else if (datatype == TLOGICAL)
+    {
+        ffgkyl(fptr, keyname, (int *) value, comm, status);
+    }
+    else if (datatype == TULONG)
+    {
+        if (ffgkyd(fptr, keyname, &doubleval, comm, status) <= 0)
+        {
+            if (doubleval > (double) ULONG_MAX || doubleval < 0)
+                *status = NUM_OVERFLOW;
+            else
+                 *(unsigned long *) value = (unsigned long) doubleval;
+        }
+    }
+    else if (datatype == TLONG)
+    {
+        ffgkyj(fptr, keyname, (long *) value, comm, status);
+    }
+    else if (datatype == TLONGLONG)
+    {
+        ffgkyjj(fptr, keyname, (LONGLONG *) value, comm, status);
+    }
+    else if (datatype == TFLOAT)
+    {
+        ffgkye(fptr, keyname, (float *) value, comm, status);
+    }
+    else if (datatype == TDOUBLE)
+    {
+        ffgkyd(fptr, keyname, (double *) value, comm, status);
+    }
+    else if (datatype == TCOMPLEX)
+    {
+        ffgkyc(fptr, keyname, (float *) value, comm, status);
+    }
+    else if (datatype == TDBLCOMPLEX)
+    {
+        ffgkym(fptr, keyname, (double *) value, comm, status);
+    }
+    else
+        *status = BAD_DATATYPE;
+
+    return(*status);
+} 
+/*--------------------------------------------------------------------------*/
+int ffgkey( fitsfile *fptr,     /* I - FITS file pointer        */
+            const char *keyname,      /* I - name of keyword to read  */
+            char *keyval,       /* O - keyword value            */
+            char *comm,         /* O - keyword comment          */
+            int  *status)       /* IO - error status            */
+/*
+  Read (get) the named keyword, returning the keyword value and comment.
+  The value is just the literal string of characters in the value field
+  of the keyword.  In the case of a string valued keyword, the returned
+  value includes the leading and closing quote characters.  The value may be
+  up to 70 characters long, and the comment may be up to 72 characters long.
+  If the keyword has no value (no equal sign in column 9) then a null value
+  is returned.
+*/
+{
+    char card[FLEN_CARD];
+
+    keyval[0] = '\0';
+    if (comm)
+       comm[0] = '\0';
+
+    if (*status > 0)
+        return(*status);
+
+    if (ffgcrd(fptr, keyname, card, status) > 0)    /* get the 80-byte card */
+        return(*status);
+
+    ffpsvc(card, keyval, comm, status);      /* parse the value and comment */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgrec( fitsfile *fptr,     /* I - FITS file pointer          */
+            int nrec,           /* I - number of keyword to read  */
+            char *card,         /* O - keyword card               */
+            int  *status)       /* IO - error status              */
+/*
+  Read (get) the nrec-th keyword, returning the entire keyword card up to
+  80 characters long.  The first keyword in the header has nrec = 1, not 0.
+  The returned card value is null terminated with any trailing blank 
+  characters removed.  If nrec = 0, then this routine simply moves the
+  current header pointer to the top of the header.
+*/
+{
+    if (*status > 0)
+        return(*status);
+
+    if (nrec == 0)
+    {
+        ffmaky(fptr, 1, status);  /* simply move to beginning of header */
+        if (card)
+            card[0] = '\0';           /* and return null card */
+    }
+    else if (nrec > 0)
+    {
+        ffmaky(fptr, nrec, status);
+        ffgnky(fptr, card, status);
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgcrd( fitsfile *fptr,     /* I - FITS file pointer        */
+            const char *name,         /* I - name of keyword to read  */
+            char *card,         /* O - keyword card             */
+            int  *status)       /* IO - error status            */
+/*
+  Read (get) the named keyword, returning the entire keyword card up to
+  80 characters long.  
+  The returned card value is null terminated with any trailing blank 
+  characters removed.
+
+  If the input name contains wild cards ('?' matches any single char
+  and '*' matches any sequence of chars, # matches any string of decimal
+  digits) then the search ends once the end of header is reached and does 
+  not automatically resume from the top of the header.
+*/
+{
+    int nkeys, nextkey, ntodo, namelen, namelen_limit, namelenminus1, cardlen;
+    int ii = 0, jj, kk, wild, match, exact, hier = 0;
+    char keyname[FLEN_KEYWORD], cardname[FLEN_KEYWORD];
+    char *ptr1, *ptr2, *gotstar;
+
+    if (*status > 0)
+        return(*status);
+
+    *keyname = '\0';
+    
+    while (name[ii] == ' ')  /* skip leading blanks in name */
+        ii++;
+
+    strncat(keyname, &name[ii], FLEN_KEYWORD - 1);
+
+    namelen = strlen(keyname);
+
+    while (namelen > 0 && keyname[namelen - 1] == ' ')
+         namelen--;            /* ignore trailing blanks in name */
+
+    keyname[namelen] = '\0';  /* terminate the name */
+
+    for (ii=0; ii < namelen; ii++)       
+        keyname[ii] = toupper(keyname[ii]);    /*  make upper case  */
+
+    if (FSTRNCMP("HIERARCH", keyname, 8) == 0)
+    {
+        if (namelen == 8)
+        {
+            /* special case: just looking for any HIERARCH keyword */
+            hier = 1;
+        }
+        else
+        {
+            /* ignore the leading HIERARCH and look for the 'real' name */
+            /* starting with first non-blank character following HIERARCH */
+            ptr1 = keyname;
+            ptr2 = &keyname[8];
+
+            while(*ptr2 == ' ')
+                ptr2++;
+
+            namelen = 0;
+            while(*ptr2)
+            {
+                *ptr1 = *ptr2;
+                 ptr1++;
+                 ptr2++;
+                 namelen++;
+            }
+            *ptr1 = '\0';
+        }
+    }
+
+    /* does input name contain wild card chars?  ('?',  '*', or '#') */
+    /* wild cards are currently not supported with HIERARCH keywords */
+
+    namelen_limit = namelen;
+    gotstar = 0;
+    if (namelen < 9 && 
+       (strchr(keyname,'?') || (gotstar = strchr(keyname,'*')) || 
+        strchr(keyname,'#')) )
+    {
+        wild = 1;
+
+        /* if we found a '*' wild card in the name, there might be */
+        /* more than one.  Support up to 2 '*' in the template. */
+        /* Thus we need to compare keywords whose names have at least */
+        /* namelen - 2 characters.                                   */
+        if (gotstar)
+           namelen_limit -= 2;           
+    }
+    else
+        wild = 0;
+
+    ffghps(fptr, &nkeys, &nextkey, status); /* get no. keywords and position */
+
+    namelenminus1 = maxvalue(namelen - 1, 1);
+    ntodo = nkeys - nextkey + 1;  /* first, read from next keyword to end */
+    for (jj=0; jj < 2; jj++)
+    {
+      for (kk = 0; kk < ntodo; kk++)
+      {
+        ffgnky(fptr, card, status);     /* get next keyword */
+
+        if (hier)
+        {
+           if (FSTRNCMP("HIERARCH", card, 8) == 0)
+                return(*status);  /* found a HIERARCH keyword */
+        }
+        else
+        {
+          ffgknm(card, cardname, &cardlen, status); /* get the keyword name */
+
+          if (cardlen >= namelen_limit)  /* can't match if card < name */
+          { 
+            /* if there are no wild cards, lengths must be the same */
+            if (!( !wild && cardlen != namelen) )
+            {
+              for (ii=0; ii < cardlen; ii++)
+              {    
+                /* make sure keyword is in uppercase */
+                if (cardname[ii] > 96)
+                {
+                  /* This assumes the ASCII character set in which */
+                  /* upper case characters start at ASCII(97)  */
+                  /* Timing tests showed that this is 20% faster */
+                  /* than calling the isupper function.          */
+
+                  cardname[ii] = toupper(cardname[ii]);  /* make upper case */
+                }
+              }
+
+              if (wild)
+              {
+                ffcmps(keyname, cardname, 1, &match, &exact);
+                if (match)
+                    return(*status); /* found a matching keyword */
+              }
+              else if (keyname[namelenminus1] == cardname[namelenminus1])
+              {
+                /* test the last character of the keyword name first, on */
+                /* the theory that it is less likely to match then the first */
+                /* character since many keywords begin with 'T', for example */
+
+                if (FSTRNCMP(keyname, cardname, namelenminus1) == 0)
+                {
+                  return(*status);   /* found the matching keyword */
+                }
+              }
+	      else if (namelen == 0 && cardlen == 0)
+	      {
+	         /* matched a blank keyword */
+		 return(*status);
+	      }
+            }
+          }
+        }
+      }
+
+      if (wild || jj == 1)
+            break;  /* stop at end of header if template contains wildcards */
+
+      ffmaky(fptr, 1, status);  /* reset pointer to beginning of header */
+      ntodo = nextkey - 1;      /* number of keyword to read */ 
+    }
+
+    return(*status = KEY_NO_EXIST);  /* couldn't find the keyword */
+}
+/*--------------------------------------------------------------------------*/
+int ffgstr( fitsfile *fptr,     /* I - FITS file pointer        */
+            const char *string, /* I - string to match  */
+            char *card,         /* O - keyword card             */
+            int  *status)       /* IO - error status            */
+/*
+  Read (get) the next keyword record that contains the input character string,
+  returning the entire keyword card up to 80 characters long.
+  The returned card value is null terminated with any trailing blank 
+  characters removed.
+*/
+{
+    int nkeys, nextkey, ntodo, stringlen;
+    int jj, kk;
+
+    if (*status > 0)
+        return(*status);
+
+    stringlen = strlen(string);
+    if (stringlen > 80) {
+        return(*status = KEY_NO_EXIST);  /* matching string is too long to exist */
+    }
+
+    ffghps(fptr, &nkeys, &nextkey, status); /* get no. keywords and position */
+    ntodo = nkeys - nextkey + 1;  /* first, read from next keyword to end */
+
+    for (jj=0; jj < 2; jj++)
+    {
+      for (kk = 0; kk < ntodo; kk++)
+      {
+        ffgnky(fptr, card, status);     /* get next keyword */
+        if (strstr(card, string) != 0) {
+            return(*status);   /* found the matching string */
+        }
+      }
+
+      ffmaky(fptr, 1, status);  /* reset pointer to beginning of header */
+      ntodo = nextkey - 1;      /* number of keyword to read */ 
+    }
+
+    return(*status = KEY_NO_EXIST);  /* couldn't find the keyword */
+}
+/*--------------------------------------------------------------------------*/
+int ffgknm( char *card,         /* I - keyword card                   */
+            char *name,         /* O - name of the keyword            */
+            int *length,        /* O - length of the keyword name     */
+            int  *status)       /* IO - error status                  */
+
+/*
+  Return the name of the keyword, and the name length.  This supports the
+  ESO HIERARCH convention where keyword names may be > 8 characters long.
+*/
+{
+    char *ptr1, *ptr2;
+    int ii;
+
+    *name = '\0';
+    *length = 0;
+
+    /* support for ESO HIERARCH keywords; find the '=' */
+    if (FSTRNCMP(card, "HIERARCH ", 9) == 0)
+    {
+        ptr2 = strchr(card, '=');
+
+        if (!ptr2)   /* no value indicator ??? */
+        {
+            /* this probably indicates an error, so just return FITS name */
+            strcat(name, "HIERARCH");
+            *length = 8;
+            return(*status);
+        }
+
+        /* find the start and end of the HIERARCH name */
+        ptr1 = &card[9];
+        while (*ptr1 == ' ')   /* skip spaces */
+            ptr1++;
+
+        strncat(name, ptr1, ptr2 - ptr1);
+        ii = ptr2 - ptr1;
+
+        while (ii > 0 && name[ii - 1] == ' ')  /* remove trailing spaces */
+            ii--;
+
+        name[ii] = '\0';
+        *length = ii;
+    }
+    else
+    {
+        for (ii = 0; ii < 8; ii++)
+        {
+           /* look for string terminator, or a blank */
+           if (*(card+ii) != ' ' && *(card+ii) !='\0')
+           {
+               *(name+ii) = *(card+ii);
+           }
+           else
+           {
+               name[ii] = '\0';
+               *length = ii;
+               return(*status);
+           }
+        }
+
+        /* if we got here, keyword is 8 characters long */
+        name[8] = '\0';
+        *length = 8;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgunt( fitsfile *fptr,     /* I - FITS file pointer         */
+            const char *keyname,      /* I - name of keyword to read   */
+            char *unit,         /* O - keyword units             */
+            int  *status)       /* IO - error status             */
+/*
+    Read (get) the units string from the comment field of the existing
+    keyword. This routine uses a local FITS convention (not defined in the
+    official FITS standard) in which the units are enclosed in 
+    square brackets following the '/' comment field delimiter, e.g.:
+
+    KEYWORD =                   12 / [kpc] comment string goes here
+*/
+{
+    char valstring[FLEN_VALUE];
+    char comm[FLEN_COMMENT];
+    char *loc;
+
+    if (*status > 0)
+        return(*status);
+
+    ffgkey(fptr, keyname, valstring, comm, status);  /* read the keyword */
+
+    if (comm[0] == '[')
+    {
+        loc = strchr(comm, ']');   /*  find the closing bracket */
+        if (loc)
+            *loc = '\0';           /*  terminate the string */
+
+        strcpy(unit, &comm[1]);    /*  copy the string */
+     }
+     else
+        unit[0] = '\0';
+ 
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgkys( fitsfile *fptr,     /* I - FITS file pointer         */
+            const char *keyname,      /* I - name of keyword to read   */
+            char *value,        /* O - keyword value             */
+            char *comm,         /* O - keyword comment           */
+            int  *status)       /* IO - error status             */
+/*
+  Get KeYword with a String value:
+  Read (get) a simple string valued keyword.  The returned value may be up to 
+  68 chars long ( + 1 null terminator char).  The routine does not support the
+  HEASARC convention for continuing long string values over multiple keywords.
+  The ffgkls routine may be used to read long continued strings. The returned
+  comment string may be up to 69 characters long (including null terminator).
+*/
+{
+    char valstring[FLEN_VALUE];
+
+    if (*status > 0)
+        return(*status);
+
+    ffgkey(fptr, keyname, valstring, comm, status);  /* read the keyword */
+    value[0] = '\0';
+    ffc2s(valstring, value, status);   /* remove quotes from string */
+ 
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgkls( fitsfile *fptr,     /* I - FITS file pointer         */
+            const char *keyname,      /* I - name of keyword to read   */
+            char **value,       /* O - pointer to keyword value  */
+            char *comm,         /* O - keyword comment           */
+            int  *status)       /* IO - error status             */
+/*
+  Get Keyword with possible Long String value:
+  Read (get) the named keyword, returning the value and comment.
+  The returned value string may be arbitrarily long (by using the HEASARC
+  convention for continuing long string values over multiple keywords) so
+  this routine allocates the required memory for the returned string value.
+  It is up to the calling routine to free the memory once it is finished
+  with the value string.  The returned comment string may be up to 69
+  characters long.
+*/
+{
+    char valstring[FLEN_VALUE];
+    int contin;
+    size_t len;
+
+    if (*status > 0)
+        return(*status);
+
+    *value = NULL;  /* initialize a null pointer in case of error */
+
+    ffgkey(fptr, keyname, valstring, comm, status);  /* read the keyword */
+
+    if (*status > 0)
+        return(*status);
+
+    if (!valstring[0])   /* null value string? */
+    {
+      *value = (char *) malloc(1);  /* allocate and return a null string */
+      **value = '\0';
+    }
+    else
+    {
+      /* allocate space,  plus 1 for null */
+      *value = (char *) malloc(strlen(valstring) + 1);
+
+      ffc2s(valstring, *value, status);   /* convert string to value */
+      len = strlen(*value);
+
+      /* If last character is a & then value may be continued on next keyword */
+      contin = 1;
+      while (contin)  
+      {
+        if (len && *(*value+len-1) == '&')  /*  is last char an anpersand?  */
+        {
+            ffgcnt(fptr, valstring, status);
+            if (*valstring)    /* a null valstring indicates no continuation */
+            {
+               *(*value+len-1) = '\0';         /* erase the trailing & char */
+               len += strlen(valstring) - 1;
+               *value = (char *) realloc(*value, len + 1); /* increase size */
+               strcat(*value, valstring);     /* append the continued chars */
+            }
+            else
+                contin = 0;
+        }
+        else
+            contin = 0;
+      }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fffree( char *value,       /* I - pointer to keyword value  */
+            int  *status)      /* IO - error status             */
+/*
+  Free the memory that was allocated by ffgkls for the long string keyword value.
+*/
+{
+    if (*status > 0)
+        return(*status);
+
+    if (value)
+        free(value);
+
+    return(*status);
+}
+ /*--------------------------------------------------------------------------*/
+int ffgcnt( fitsfile *fptr,     /* I - FITS file pointer         */
+            char *value,        /* O - continued string value    */
+            int  *status)       /* IO - error status             */
+/*
+  Attempt to read the next keyword, returning the string value
+  if it is a continuation of the previous string keyword value.
+  This uses the HEASARC convention for continuing long string values
+  over multiple keywords.  Each continued string is terminated with a
+  backslash character, and the continuation follows on the next keyword
+  which must have the name CONTINUE without an equal sign in column 9
+  of the card.  If the next card is not a continuation, then the returned
+  value string will be null.
+*/
+{
+    int tstatus;
+    char card[FLEN_CARD], strval[FLEN_VALUE], comm[FLEN_COMMENT];
+
+    if (*status > 0)
+        return(*status);
+
+    tstatus = 0;
+    value[0] = '\0';
+
+    if (ffgnky(fptr, card, &tstatus) > 0)  /*  read next keyword  */
+        return(*status);                   /*  hit end of header  */
+
+    if (strncmp(card, "CONTINUE  ", 10) == 0)  /* a continuation card? */
+    {
+        strncpy(card, "D2345678=  ", 10); /* overwrite a dummy keyword name */
+        ffpsvc(card, strval, comm, &tstatus);  /*  get the string value  */
+        ffc2s(strval, value, &tstatus);    /* remove the surrounding quotes */
+
+        if (tstatus)       /*  return null if error status was returned  */
+           value[0] = '\0';
+    }
+    else
+        ffmrky(fptr, -1, status);  /* reset the keyword pointer */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgkyl( fitsfile *fptr,     /* I - FITS file pointer         */
+            const char *keyname,      /* I - name of keyword to read   */
+            int  *value,        /* O - keyword value             */
+            char *comm,         /* O - keyword comment           */
+            int  *status)       /* IO - error status             */
+/*
+  Read (get) the named keyword, returning the value and comment.
+  The returned value = 1 if the keyword is true, else = 0 if false.
+  The comment may be up to 69 characters long.
+*/
+{
+    char valstring[FLEN_VALUE];
+
+    if (*status > 0)
+        return(*status);
+
+    ffgkey(fptr, keyname, valstring, comm, status);  /* read the keyword */
+    ffc2l(valstring, value, status);   /* convert string to value */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgkyj( fitsfile *fptr,     /* I - FITS file pointer         */
+            const char *keyname,      /* I - name of keyword to read   */
+            long *value,        /* O - keyword value             */
+            char *comm,         /* O - keyword comment           */
+            int  *status)       /* IO - error status             */
+/*
+  Read (get) the named keyword, returning the value and comment.
+  The value will be implicitly converted to a (long) integer if it not
+  already of this datatype.  The comment may be up to 69 characters long.
+*/
+{
+    char valstring[FLEN_VALUE];
+
+    if (*status > 0)
+        return(*status);
+
+    ffgkey(fptr, keyname, valstring, comm, status);  /* read the keyword */
+    ffc2i(valstring, value, status);   /* convert string to value */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgkyjj( fitsfile *fptr,     /* I - FITS file pointer         */
+            const char *keyname,      /* I - name of keyword to read   */
+            LONGLONG *value,    /* O - keyword value             */
+            char *comm,         /* O - keyword comment           */
+            int  *status)       /* IO - error status             */
+/*
+  Read (get) the named keyword, returning the value and comment.
+  The value will be implicitly converted to a (long) integer if it not
+  already of this datatype.  The comment may be up to 69 characters long.
+*/
+{
+    char valstring[FLEN_VALUE];
+
+    if (*status > 0)
+        return(*status);
+
+    ffgkey(fptr, keyname, valstring, comm, status);  /* read the keyword */
+    ffc2j(valstring, value, status);   /* convert string to value */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgkye( fitsfile *fptr,     /* I - FITS file pointer         */
+            const char  *keyname,     /* I - name of keyword to read   */
+            float *value,       /* O - keyword value             */
+            char  *comm,        /* O - keyword comment           */
+            int   *status)      /* IO - error status             */
+/*
+  Read (get) the named keyword, returning the value and comment.
+  The value will be implicitly converted to a float if it not
+  already of this datatype.  The comment may be up to 69 characters long.
+*/
+{
+    char valstring[FLEN_VALUE];
+
+    if (*status > 0)
+        return(*status);
+
+    ffgkey(fptr, keyname, valstring, comm, status);  /* read the keyword */
+    ffc2r(valstring, value, status);   /* convert string to value */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgkyd( fitsfile *fptr,      /* I - FITS file pointer         */
+            const char   *keyname,     /* I - name of keyword to read   */
+            double *value,       /* O - keyword value             */
+            char   *comm,        /* O - keyword comment           */
+            int    *status)      /* IO - error status             */
+/*
+  Read (get) the named keyword, returning the value and comment.
+  The value will be implicitly converted to a double if it not
+  already of this datatype.  The comment may be up to 69 characters long.
+*/
+{
+    char valstring[FLEN_VALUE];
+
+    if (*status > 0)
+        return(*status);
+
+    ffgkey(fptr, keyname, valstring, comm, status);  /* read the keyword */
+    ffc2d(valstring, value, status);   /* convert string to value */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgkyc( fitsfile *fptr,     /* I - FITS file pointer         */
+            const char  *keyname,     /* I - name of keyword to read   */
+            float *value,       /* O - keyword value (real,imag) */
+            char  *comm,        /* O - keyword comment           */
+            int   *status)      /* IO - error status             */
+/*
+  Read (get) the named keyword, returning the value and comment.
+  The keyword must have a complex value. No implicit data conversion
+  will be performed.
+*/
+{
+    char valstring[FLEN_VALUE], message[81];
+    int len;
+
+    if (*status > 0)
+        return(*status);
+
+    ffgkey(fptr, keyname, valstring, comm, status);  /* read the keyword */
+
+    if (valstring[0] != '(' )   /* test that this is a complex keyword */
+    {
+      sprintf(message, "keyword %s does not have a complex value (ffgkyc):",
+              keyname);
+      ffpmsg(message);
+      ffpmsg(valstring);
+      return(*status = BAD_C2F);
+    }
+
+    valstring[0] = ' ';            /* delete the opening parenthesis */
+    len = strcspn(valstring, ")" );  
+    valstring[len] = '\0';         /* delete the closing parenthesis */
+
+    len = strcspn(valstring, ",");
+    valstring[len] = '\0';
+
+    ffc2r(valstring, &value[0], status);       /* convert the real part */
+    ffc2r(&valstring[len + 1], &value[1], status); /* convert imag. part */
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgkym( fitsfile *fptr,     /* I - FITS file pointer         */
+            const char  *keyname,     /* I - name of keyword to read   */
+            double *value,      /* O - keyword value (real,imag) */
+            char  *comm,        /* O - keyword comment           */
+            int   *status)      /* IO - error status             */
+/*
+  Read (get) the named keyword, returning the value and comment.
+  The keyword must have a complex value. No implicit data conversion
+  will be performed.
+*/
+{
+    char valstring[FLEN_VALUE], message[81];
+    int len;
+
+    if (*status > 0)
+        return(*status);
+
+    ffgkey(fptr, keyname, valstring, comm, status);  /* read the keyword */
+
+    if (valstring[0] != '(' )   /* test that this is a complex keyword */
+    {
+      sprintf(message, "keyword %s does not have a complex value (ffgkym):",
+              keyname);
+      ffpmsg(message);
+      ffpmsg(valstring);
+      return(*status = BAD_C2D);
+    }
+
+    valstring[0] = ' ';            /* delete the opening parenthesis */
+    len = strcspn(valstring, ")" );  
+    valstring[len] = '\0';         /* delete the closing parenthesis */
+
+    len = strcspn(valstring, ",");
+    valstring[len] = '\0';
+
+    ffc2d(valstring, &value[0], status);        /* convert the real part */
+    ffc2d(&valstring[len + 1], &value[1], status);  /* convert the imag. part */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgkyt( fitsfile *fptr,      /* I - FITS file pointer                 */
+            const char   *keyname,     /* I - name of keyword to read           */
+            long   *ivalue,      /* O - integer part of keyword value     */
+            double *fraction,    /* O - fractional part of keyword value  */
+            char   *comm,        /* O - keyword comment                   */
+            int    *status)      /* IO - error status                     */
+/*
+  Read (get) the named keyword, returning the value and comment.
+  The integer and fractional parts of the value are returned in separate
+  variables, to allow more numerical precision to be passed.  This
+  effectively passes a 'triple' precision value, with a 4-byte integer
+  and an 8-byte fraction.  The comment may be up to 69 characters long.
+*/
+{
+    char valstring[FLEN_VALUE];
+    char *loc;
+
+    if (*status > 0)
+        return(*status);
+
+    ffgkey(fptr, keyname, valstring, comm, status);  /* read the keyword */
+
+    /*  read the entire value string as a double, to get the integer part */
+    ffc2d(valstring, fraction, status);
+
+    *ivalue = (long) *fraction;
+
+    *fraction = *fraction - *ivalue;
+
+    /* see if we need to read the fractional part again with more precision */
+    /* look for decimal point, without an exponential E or D character */
+
+    loc = strchr(valstring, '.');
+    if (loc)
+    {
+        if (!strchr(valstring, 'E') && !strchr(valstring, 'D'))
+            ffc2d(loc, fraction, status);
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgkyn( fitsfile *fptr,      /* I - FITS file pointer             */
+            int    nkey,         /* I - number of the keyword to read */
+            char   *keyname,     /* O - name of the keyword           */
+            char   *value,       /* O - keyword value                 */
+            char   *comm,        /* O - keyword comment               */
+            int    *status)      /* IO - error status                 */
+/*
+  Read (get) the nkey-th keyword returning the keyword name, value and comment.
+  The value is just the literal string of characters in the value field
+  of the keyword.  In the case of a string valued keyword, the returned
+  value includes the leading and closing quote characters.  The value may be
+  up to 70 characters long, and the comment may be up to 72 characters long.
+  If the keyword has no value (no equal sign in column 9) then a null value
+  is returned.  If comm = NULL, then do not return the comment string.
+*/
+{
+    char card[FLEN_CARD], sbuff[FLEN_CARD];
+    int namelen;
+
+    keyname[0] = '\0';
+    value[0] = '\0';
+    if (comm)
+        comm[0] = '\0';
+
+    if (*status > 0)
+        return(*status);
+
+    if (ffgrec(fptr, nkey, card, status) > 0 )  /* get the 80-byte card */
+        return(*status);
+
+    ffgknm(card, keyname, &namelen, status); /* get the keyword name */
+
+    if (ffpsvc(card, value, comm, status) > 0)   /* parse value and comment */
+        return(*status);
+
+    if (fftrec(keyname, status) > 0)  /* test keyword name; catches no END */
+    {
+     sprintf(sbuff,"Name of keyword no. %d contains illegal character(s): %s",
+              nkey, keyname);
+     ffpmsg(sbuff);
+
+     if (nkey % 36 == 0)  /* test if at beginning of 36-card FITS record */
+            ffpmsg("  (This may indicate a missing END keyword).");
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgkns( fitsfile *fptr,     /* I - FITS file pointer                    */
+            const char *keyname,      /* I - root name of keywords to read        */
+            int  nstart,        /* I - starting index number                */
+            int  nmax,          /* I - maximum number of keywords to return */
+            char *value[],      /* O - array of pointers to keyword values  */
+            int  *nfound,       /* O - number of values that were returned  */
+            int  *status)       /* IO - error status                        */
+/*
+  Read (get) an indexed array of keywords with index numbers between
+  NSTART and (NSTART + NMAX -1) inclusive.  
+  This routine does NOT support the HEASARC long string convention.
+*/
+{
+    int nend, lenroot, ii, nkeys, mkeys, tstatus, undefinedval;
+    long ival;
+    char keyroot[FLEN_KEYWORD], keyindex[8], card[FLEN_CARD];
+    char svalue[FLEN_VALUE], comm[FLEN_COMMENT];
+
+    if (*status > 0)
+        return(*status);
+
+    *nfound = 0;
+    nend = nstart + nmax - 1;
+
+    keyroot[0] = '\0';
+    strncat(keyroot, keyname, 8);
+     
+    lenroot = strlen(keyroot);
+    if (lenroot == 0 || lenroot > 7)     /*  root must be 1 - 7 chars long  */
+        return(*status);
+
+    for (ii=0; ii < lenroot; ii++)           /*  make sure upper case  */
+        keyroot[ii] = toupper(keyroot[ii]);
+
+    ffghps(fptr, &nkeys, &mkeys, status);  /*  get the number of keywords  */
+
+    undefinedval = FALSE;
+    for (ii=3; ii <= nkeys; ii++)  
+    {
+       if (ffgrec(fptr, ii, card, status) > 0)     /*  get next keyword  */
+           return(*status);
+
+       if (strncmp(keyroot, card, lenroot) == 0)  /* see if keyword matches */
+       {
+          keyindex[0] = '\0';
+          strncat(keyindex, &card[lenroot], 8-lenroot);  /*  copy suffix */
+
+          tstatus = 0;
+          if (ffc2ii(keyindex, &ival, &tstatus) <= 0)     /*  test suffix  */
+          {
+             if (ival <= nend && ival >= nstart)
+             {
+                ffpsvc(card, svalue, comm, status);  /*  parse the value */
+                ffc2s(svalue, value[ival-nstart], status); /* convert */
+                if (ival - nstart + 1 > *nfound)
+                      *nfound = ival - nstart + 1;  /*  max found */ 
+
+                if (*status == VALUE_UNDEFINED)
+                {
+                   undefinedval = TRUE;
+                   *status = 0;  /* reset status to read remaining values */
+                }
+             }
+          }
+       }
+    }
+    if (undefinedval && (*status <= 0) )
+        *status = VALUE_UNDEFINED;  /* report at least 1 value undefined */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgknl( fitsfile *fptr,     /* I - FITS file pointer                    */
+            const char *keyname,      /* I - root name of keywords to read        */
+            int  nstart,        /* I - starting index number                */
+            int  nmax,          /* I - maximum number of keywords to return */
+            int  *value,        /* O - array of keyword values              */
+            int  *nfound,       /* O - number of values that were returned  */
+            int  *status)       /* IO - error status                        */
+/*
+  Read (get) an indexed array of keywords with index numbers between
+  NSTART and (NSTART + NMAX -1) inclusive.  
+  The returned value = 1 if the keyword is true, else = 0 if false.
+*/
+{
+    int nend, lenroot, ii, nkeys, mkeys, tstatus, undefinedval;
+    long ival;
+    char keyroot[FLEN_KEYWORD], keyindex[8], card[FLEN_CARD];
+    char svalue[FLEN_VALUE], comm[FLEN_COMMENT];
+
+    if (*status > 0)
+        return(*status);
+
+    *nfound = 0;
+    nend = nstart + nmax - 1;
+
+    keyroot[0] = '\0';
+    strncat(keyroot, keyname, 8);
+
+    lenroot = strlen(keyroot);
+    if (lenroot == 0 || lenroot > 7)     /*  root must be 1 - 7 chars long  */
+        return(*status);
+
+    for (ii=0; ii < lenroot; ii++)           /*  make sure upper case  */
+        keyroot[ii] = toupper(keyroot[ii]);
+
+    ffghps(fptr, &nkeys, &mkeys, status);  /*  get the number of keywords  */
+
+    ffmaky(fptr, 3, status);  /* move to 3rd keyword (skip 1st 2 keywords) */
+
+    undefinedval = FALSE;
+    for (ii=3; ii <= nkeys; ii++)  
+    {
+       if (ffgnky(fptr, card, status) > 0)     /*  get next keyword  */
+           return(*status);
+
+       if (strncmp(keyroot, card, lenroot) == 0)  /* see if keyword matches */
+       {
+          keyindex[0] = '\0';
+          strncat(keyindex, &card[lenroot], 8-lenroot);  /*  copy suffix */
+
+          tstatus = 0;
+          if (ffc2ii(keyindex, &ival, &tstatus) <= 0)    /*  test suffix  */
+          {
+             if (ival <= nend && ival >= nstart)
+             {
+                ffpsvc(card, svalue, comm, status);   /*  parse the value */
+                ffc2l(svalue, &value[ival-nstart], status); /* convert*/
+                if (ival - nstart + 1 > *nfound)
+                      *nfound = ival - nstart + 1;  /*  max found */ 
+
+                if (*status == VALUE_UNDEFINED)
+                {
+                    undefinedval = TRUE;
+                   *status = 0;  /* reset status to read remaining values */
+                }
+             }
+          }
+       }
+    }
+    if (undefinedval && (*status <= 0) )
+        *status = VALUE_UNDEFINED;  /* report at least 1 value undefined */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgknj( fitsfile *fptr,     /* I - FITS file pointer                    */
+            const char *keyname,      /* I - root name of keywords to read        */
+            int  nstart,        /* I - starting index number                */
+            int  nmax,          /* I - maximum number of keywords to return */
+            long *value,        /* O - array of keyword values              */
+            int  *nfound,       /* O - number of values that were returned  */
+            int  *status)       /* IO - error status                        */
+/*
+  Read (get) an indexed array of keywords with index numbers between
+  NSTART and (NSTART + NMAX -1) inclusive.  
+*/
+{
+    int nend, lenroot, ii, nkeys, mkeys, tstatus, undefinedval;
+    long ival;
+    char keyroot[FLEN_KEYWORD], keyindex[8], card[FLEN_CARD];
+    char svalue[FLEN_VALUE], comm[FLEN_COMMENT];
+
+    if (*status > 0)
+        return(*status);
+
+    *nfound = 0;
+    nend = nstart + nmax - 1;
+
+    keyroot[0] = '\0';
+    strncat(keyroot, keyname, 8);
+
+    lenroot = strlen(keyroot);
+    if (lenroot == 0 || lenroot > 7)     /* root must be 1 - 7 chars long */
+        return(*status);
+
+    for (ii=0; ii < lenroot; ii++)           /*  make sure upper case  */
+        keyroot[ii] = toupper(keyroot[ii]);
+
+    ffghps(fptr, &nkeys, &mkeys, status);  /*  get the number of keywords  */
+
+    ffmaky(fptr, 3, status);  /* move to 3rd keyword (skip 1st 2 keywords) */
+
+    undefinedval = FALSE;
+    for (ii=3; ii <= nkeys; ii++)  
+    {
+       if (ffgnky(fptr, card, status) > 0)     /*  get next keyword  */
+           return(*status);
+
+       if (strncmp(keyroot, card, lenroot) == 0)  /* see if keyword matches */
+       {
+          keyindex[0] = '\0';
+          strncat(keyindex, &card[lenroot], 8-lenroot);  /*  copy suffix */
+
+          tstatus = 0;
+          if (ffc2ii(keyindex, &ival, &tstatus) <= 0)     /*  test suffix  */
+          {
+             if (ival <= nend && ival >= nstart)
+             {
+                ffpsvc(card, svalue, comm, status);   /*  parse the value */
+                ffc2i(svalue, &value[ival-nstart], status);  /* convert */
+                if (ival - nstart + 1 > *nfound)
+                      *nfound = ival - nstart + 1;  /*  max found */ 
+
+                if (*status == VALUE_UNDEFINED)
+                {
+                    undefinedval = TRUE;
+                   *status = 0;  /* reset status to read remaining values */
+                }
+             }
+          }
+       }
+    }
+    if (undefinedval && (*status <= 0) )
+        *status = VALUE_UNDEFINED;  /* report at least 1 value undefined */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgknjj( fitsfile *fptr,    /* I - FITS file pointer                    */
+            const char *keyname,      /* I - root name of keywords to read        */
+            int  nstart,        /* I - starting index number                */
+            int  nmax,          /* I - maximum number of keywords to return */
+            LONGLONG *value,    /* O - array of keyword values              */
+            int  *nfound,       /* O - number of values that were returned  */
+            int  *status)       /* IO - error status                        */
+/*
+  Read (get) an indexed array of keywords with index numbers between
+  NSTART and (NSTART + NMAX -1) inclusive.  
+*/
+{
+    int nend, lenroot, ii, nkeys, mkeys, tstatus, undefinedval;
+    long ival;
+    char keyroot[FLEN_KEYWORD], keyindex[8], card[FLEN_CARD];
+    char svalue[FLEN_VALUE], comm[FLEN_COMMENT];
+
+    if (*status > 0)
+        return(*status);
+
+    *nfound = 0;
+    nend = nstart + nmax - 1;
+
+    keyroot[0] = '\0';
+    strncat(keyroot, keyname, 8);
+
+    lenroot = strlen(keyroot);
+    if (lenroot == 0 || lenroot > 7)     /* root must be 1 - 7 chars long */
+        return(*status);
+
+    for (ii=0; ii < lenroot; ii++)           /*  make sure upper case  */
+        keyroot[ii] = toupper(keyroot[ii]);
+
+    ffghps(fptr, &nkeys, &mkeys, status);  /*  get the number of keywords  */
+
+    ffmaky(fptr, 3, status);  /* move to 3rd keyword (skip 1st 2 keywords) */
+
+    undefinedval = FALSE;
+    for (ii=3; ii <= nkeys; ii++)  
+    {
+       if (ffgnky(fptr, card, status) > 0)     /*  get next keyword  */
+           return(*status);
+
+       if (strncmp(keyroot, card, lenroot) == 0)  /* see if keyword matches */
+       {
+          keyindex[0] = '\0';
+          strncat(keyindex, &card[lenroot], 8-lenroot);  /*  copy suffix */
+
+          tstatus = 0;
+          if (ffc2ii(keyindex, &ival, &tstatus) <= 0)     /*  test suffix  */
+          {
+             if (ival <= nend && ival >= nstart)
+             {
+                ffpsvc(card, svalue, comm, status);   /*  parse the value */
+                ffc2j(svalue, &value[ival-nstart], status);  /* convert */
+                if (ival - nstart + 1 > *nfound)
+                      *nfound = ival - nstart + 1;  /*  max found */ 
+
+                if (*status == VALUE_UNDEFINED)
+                {
+                    undefinedval = TRUE;
+                   *status = 0;  /* reset status to read remaining values */
+                }
+             }
+          }
+       }
+    }
+    if (undefinedval && (*status <= 0) )
+        *status = VALUE_UNDEFINED;  /* report at least 1 value undefined */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgkne( fitsfile *fptr,     /* I - FITS file pointer                    */
+            const char *keyname,      /* I - root name of keywords to read        */
+            int  nstart,        /* I - starting index number                */
+            int  nmax,          /* I - maximum number of keywords to return */
+            float *value,       /* O - array of keyword values              */
+            int  *nfound,       /* O - number of values that were returned  */
+            int  *status)       /* IO - error status                        */
+/*
+  Read (get) an indexed array of keywords with index numbers between
+  NSTART and (NSTART + NMAX -1) inclusive.  
+*/
+{
+    int nend, lenroot, ii, nkeys, mkeys, tstatus, undefinedval;
+    long ival;
+    char keyroot[FLEN_KEYWORD], keyindex[8], card[FLEN_CARD];
+    char svalue[FLEN_VALUE], comm[FLEN_COMMENT];
+
+    if (*status > 0)
+        return(*status);
+
+    *nfound = 0;
+    nend = nstart + nmax - 1;
+
+    keyroot[0] = '\0';
+    strncat(keyroot, keyname, 8);
+
+    lenroot = strlen(keyroot);
+    if (lenroot == 0 || lenroot > 7)     /*  root must be 1 - 7 chars long  */
+        return(*status);
+
+    for (ii=0; ii < lenroot; ii++)           /*  make sure upper case  */
+        keyroot[ii] = toupper(keyroot[ii]);
+
+    ffghps(fptr, &nkeys, &mkeys, status);  /*  get the number of keywords  */
+
+    ffmaky(fptr, 3, status);  /* move to 3rd keyword (skip 1st 2 keywords) */
+
+    undefinedval = FALSE;
+    for (ii=3; ii <= nkeys; ii++)  
+    {
+       if (ffgnky(fptr, card, status) > 0)     /*  get next keyword  */
+           return(*status);
+
+       if (strncmp(keyroot, card, lenroot) == 0)  /* see if keyword matches */
+       {
+          keyindex[0] = '\0';
+          strncat(keyindex, &card[lenroot], 8-lenroot);  /*  copy suffix */
+
+          tstatus = 0;
+          if (ffc2ii(keyindex, &ival, &tstatus) <= 0)     /*  test suffix  */
+          {
+             if (ival <= nend && ival >= nstart)
+             {
+                ffpsvc(card, svalue, comm, status);   /*  parse the value */
+                ffc2r(svalue, &value[ival-nstart], status); /* convert */
+                if (ival - nstart + 1 > *nfound)
+                      *nfound = ival - nstart + 1;  /*  max found */ 
+
+                if (*status == VALUE_UNDEFINED)
+                {
+                    undefinedval = TRUE;
+                   *status = 0;  /* reset status to read remaining values */
+                }
+             }
+          }
+       }
+    }
+    if (undefinedval && (*status <= 0) )
+        *status = VALUE_UNDEFINED;  /* report at least 1 value undefined */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgknd( fitsfile *fptr,     /* I - FITS file pointer                    */
+            const char *keyname,      /* I - root name of keywords to read        */
+            int  nstart,        /* I - starting index number                */
+            int  nmax,          /* I - maximum number of keywords to return */
+            double *value,      /* O - array of keyword values              */
+            int  *nfound,       /* O - number of values that were returned  */
+            int  *status)       /* IO - error status                        */
+/*
+  Read (get) an indexed array of keywords with index numbers between
+  NSTART and (NSTART + NMAX -1) inclusive.  
+*/
+{
+    int nend, lenroot, ii, nkeys, mkeys, tstatus, undefinedval;
+    long ival;
+    char keyroot[FLEN_KEYWORD], keyindex[8], card[FLEN_CARD];
+    char svalue[FLEN_VALUE], comm[FLEN_COMMENT];
+
+    if (*status > 0)
+        return(*status);
+
+    *nfound = 0;
+    nend = nstart + nmax - 1;
+
+    keyroot[0] = '\0';
+    strncat(keyroot, keyname, 8);
+
+    lenroot = strlen(keyroot);
+    if (lenroot == 0 || lenroot > 7)     /*  root must be 1 - 7 chars long  */
+        return(*status);
+
+    for (ii=0; ii < lenroot; ii++)           /*  make sure upper case  */
+        keyroot[ii] = toupper(keyroot[ii]);
+
+    ffghps(fptr, &nkeys, &mkeys, status);  /*  get the number of keywords  */
+
+    ffmaky(fptr, 3, status);  /* move to 3rd keyword (skip 1st 2 keywords) */
+
+    undefinedval = FALSE;
+    for (ii=3; ii <= nkeys; ii++)  
+    {
+       if (ffgnky(fptr, card, status) > 0)     /*  get next keyword  */
+           return(*status);
+
+       if (strncmp(keyroot, card, lenroot) == 0)   /* see if keyword matches */
+       {
+          keyindex[0] = '\0';
+          strncat(keyindex, &card[lenroot], 8-lenroot);  /*  copy suffix */
+
+          tstatus = 0;
+          if (ffc2ii(keyindex, &ival, &tstatus) <= 0)      /*  test suffix */
+          {
+             if (ival <= nend && ival >= nstart) /* is index within range? */
+             {
+                ffpsvc(card, svalue, comm, status);   /*  parse the value */
+                ffc2d(svalue, &value[ival-nstart], status); /* convert */
+                if (ival - nstart + 1 > *nfound)
+                      *nfound = ival - nstart + 1;  /*  max found */ 
+
+                if (*status == VALUE_UNDEFINED)
+                {
+                    undefinedval = TRUE;
+                   *status = 0;  /* reset status to read remaining values */
+                }
+             }
+          }
+       }
+    }
+    if (undefinedval && (*status <= 0) )
+        *status = VALUE_UNDEFINED;  /* report at least 1 value undefined */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgtdm(fitsfile *fptr,  /* I - FITS file pointer                        */
+           int colnum,      /* I - number of the column to read             */
+           int maxdim,      /* I - maximum no. of dimensions to read;       */
+           int *naxis,      /* O - number of axes in the data array         */
+           long naxes[],    /* O - length of each data axis                 */
+           int *status)     /* IO - error status                            */
+/*
+  read and parse the TDIMnnn keyword to get the dimensionality of a column
+*/
+{
+    int tstatus = 0;
+    char keyname[FLEN_KEYWORD], tdimstr[FLEN_VALUE];
+
+    if (*status > 0)
+        return(*status);
+
+    ffkeyn("TDIM", colnum, keyname, status);      /* construct keyword name */
+
+    ffgkys(fptr, keyname, tdimstr, NULL, &tstatus); /* try reading keyword */
+
+    ffdtdm(fptr, tdimstr, colnum, maxdim,naxis, naxes, status); /* decode it */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgtdmll(fitsfile *fptr,  /* I - FITS file pointer                      */
+           int colnum,      /* I - number of the column to read             */
+           int maxdim,      /* I - maximum no. of dimensions to read;       */
+           int *naxis,      /* O - number of axes in the data array         */
+           LONGLONG naxes[], /* O - length of each data axis                 */
+           int *status)     /* IO - error status                            */
+/*
+  read and parse the TDIMnnn keyword to get the dimensionality of a column
+*/
+{
+    int tstatus = 0;
+    char keyname[FLEN_KEYWORD], tdimstr[FLEN_VALUE];
+
+    if (*status > 0)
+        return(*status);
+
+    ffkeyn("TDIM", colnum, keyname, status);      /* construct keyword name */
+
+    ffgkys(fptr, keyname, tdimstr, NULL, &tstatus); /* try reading keyword */
+
+    ffdtdmll(fptr, tdimstr, colnum, maxdim,naxis, naxes, status); /* decode it */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffdtdm(fitsfile *fptr,  /* I - FITS file pointer                        */
+           char *tdimstr,   /* I - TDIMn keyword value string. e.g. (10,10) */
+           int colnum,      /* I - number of the column             */
+           int maxdim,      /* I - maximum no. of dimensions to read;       */
+           int *naxis,      /* O - number of axes in the data array         */
+           long naxes[],    /* O - length of each data axis                 */
+           int *status)     /* IO - error status                            */
+/*
+  decode the TDIMnnn keyword to get the dimensionality of a column.
+  Check that the value is legal and consistent with the TFORM value.
+*/
+{
+    long dimsize, totalpix = 1;
+    char *loc, *lastloc, message[81];
+    tcolumn *colptr;
+
+    if (*status > 0)
+        return(*status);
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    if (colnum < 1 || colnum > (fptr->Fptr)->tfield)
+        return(*status = BAD_COL_NUM);
+
+    colptr = (fptr->Fptr)->tableptr;   /* set pointer to the first column */
+    colptr += (colnum - 1);    /* increment to the correct column */
+
+    if (!tdimstr[0])   /* TDIMn keyword doesn't exist? */
+    {
+        *naxis = 1;                   /* default = 1 dimensional */
+        if (maxdim > 0)
+            naxes[0] = (long) colptr->trepeat; /* default length = repeat */
+    }
+    else
+    {
+        *naxis = 0;
+
+        loc = strchr(tdimstr, '(' );  /* find the opening quote */
+        if (!loc)
+        {
+            sprintf(message, "Illegal TDIM keyword value: %s", tdimstr);
+            return(*status = BAD_TDIM);
+        }
+
+        while (loc)
+        {
+            loc++;
+            dimsize = strtol(loc, &loc, 10);  /* read size of next dimension */
+            if (*naxis < maxdim)
+                naxes[*naxis] = dimsize;
+
+            if (dimsize < 0)
+            {
+                ffpmsg("one or more TDIM values are less than 0 (ffdtdm)");
+                ffpmsg(tdimstr);
+                return(*status = BAD_TDIM);
+            }
+
+            totalpix *= dimsize;
+            (*naxis)++;
+            lastloc = loc;
+            loc = strchr(loc, ',');  /* look for comma before next dimension */
+        }
+
+        loc = strchr(lastloc, ')' );  /* check for the closing quote */
+        if (!loc)
+        {
+            sprintf(message, "Illegal TDIM keyword value: %s", tdimstr);
+            return(*status = BAD_TDIM);
+        }
+
+        if ((colptr->tdatatype > 0) && ((long) colptr->trepeat != totalpix))
+        {
+          sprintf(message,
+          "column vector length, %ld, does not equal TDIMn array size, %ld",
+          (long) colptr->trepeat, totalpix);
+          ffpmsg(message);
+          ffpmsg(tdimstr);
+          return(*status = BAD_TDIM);
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffdtdmll(fitsfile *fptr,  /* I - FITS file pointer                        */
+           char *tdimstr,   /* I - TDIMn keyword value string. e.g. (10,10) */
+           int colnum,      /* I - number of the column             */
+           int maxdim,      /* I - maximum no. of dimensions to read;       */
+           int *naxis,      /* O - number of axes in the data array         */
+           LONGLONG naxes[],    /* O - length of each data axis                 */
+           int *status)     /* IO - error status                            */
+/*
+  decode the TDIMnnn keyword to get the dimensionality of a column.
+  Check that the value is legal and consistent with the TFORM value.
+*/
+{
+    LONGLONG dimsize;
+    LONGLONG totalpix = 1;
+    char *loc, *lastloc, message[81];
+    tcolumn *colptr;
+    double doublesize;
+
+    if (*status > 0)
+        return(*status);
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    if (colnum < 1 || colnum > (fptr->Fptr)->tfield)
+        return(*status = BAD_COL_NUM);
+
+    colptr = (fptr->Fptr)->tableptr;   /* set pointer to the first column */
+    colptr += (colnum - 1);    /* increment to the correct column */
+
+    if (!tdimstr[0])   /* TDIMn keyword doesn't exist? */
+    {
+        *naxis = 1;                   /* default = 1 dimensional */
+        if (maxdim > 0)
+            naxes[0] = colptr->trepeat; /* default length = repeat */
+    }
+    else
+    {
+        *naxis = 0;
+
+        loc = strchr(tdimstr, '(' );  /* find the opening quote */
+        if (!loc)
+        {
+            sprintf(message, "Illegal TDIM keyword value: %s", tdimstr);
+            return(*status = BAD_TDIM);
+        }
+
+        while (loc)
+        {
+            loc++;
+
+    /* Read value as a double because the string to 64-bit int function is  */
+    /* platform dependent (strtoll, strtol, _atoI64).  This still gives     */
+    /* about 48 bits of precision, which is plenty for this purpose.        */
+
+            doublesize = strtod(loc, &loc);
+            dimsize = (LONGLONG) (doublesize + 0.1);
+
+            if (*naxis < maxdim)
+                naxes[*naxis] = dimsize;
+
+            if (dimsize < 0)
+            {
+                ffpmsg("one or more TDIM values are less than 0 (ffdtdm)");
+                ffpmsg(tdimstr);
+                return(*status = BAD_TDIM);
+            }
+
+            totalpix *= dimsize;
+            (*naxis)++;
+            lastloc = loc;
+            loc = strchr(loc, ',');  /* look for comma before next dimension */
+        }
+
+        loc = strchr(lastloc, ')' );  /* check for the closing quote */
+        if (!loc)
+        {
+            sprintf(message, "Illegal TDIM keyword value: %s", tdimstr);
+            return(*status = BAD_TDIM);
+        }
+
+        if ((colptr->tdatatype > 0) && (colptr->trepeat != totalpix))
+        {
+          sprintf(message,
+          "column vector length, %.0f, does not equal TDIMn array size, %.0f",
+          (double) (colptr->trepeat), (double) totalpix);
+          ffpmsg(message);
+          ffpmsg(tdimstr);
+          return(*status = BAD_TDIM);
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffghpr(fitsfile *fptr,  /* I - FITS file pointer                        */
+           int maxdim,      /* I - maximum no. of dimensions to read;       */
+           int *simple,     /* O - does file conform to FITS standard? 1/0  */
+           int *bitpix,     /* O - number of bits per data value pixel      */
+           int *naxis,      /* O - number of axes in the data array         */
+           long naxes[],    /* O - length of each data axis                 */
+           long *pcount,    /* O - number of group parameters (usually 0)   */
+           long *gcount,    /* O - number of random groups (usually 1 or 0) */
+           int *extend,     /* O - may FITS file haave extensions?          */
+           int *status)     /* IO - error status                            */
+/*
+  Get keywords from the Header of the PRimary array:
+  Check that the keywords conform to the FITS standard and return the
+  parameters which determine the size and structure of the primary array
+  or IMAGE extension.
+*/
+{
+    int idummy, ii;
+    LONGLONG lldummy;
+    double ddummy;
+    LONGLONG tnaxes[99];
+
+    ffgphd(fptr, maxdim, simple, bitpix, naxis, tnaxes, pcount, gcount, extend,
+          &ddummy, &ddummy, &lldummy, &idummy, status);
+	  
+    if (naxis && naxes) {
+         for (ii = 0; (ii < *naxis) && (ii < maxdim); ii++)
+	     naxes[ii] = (long) tnaxes[ii];
+    } else if (naxes) {
+         for (ii = 0; ii < maxdim; ii++)
+	     naxes[ii] = (long) tnaxes[ii];
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffghprll(fitsfile *fptr,  /* I - FITS file pointer                        */
+           int maxdim,      /* I - maximum no. of dimensions to read;       */
+           int *simple,     /* O - does file conform to FITS standard? 1/0  */
+           int *bitpix,     /* O - number of bits per data value pixel      */
+           int *naxis,      /* O - number of axes in the data array         */
+           LONGLONG naxes[],    /* O - length of each data axis                 */
+           long *pcount,    /* O - number of group parameters (usually 0)   */
+           long *gcount,    /* O - number of random groups (usually 1 or 0) */
+           int *extend,     /* O - may FITS file haave extensions?          */
+           int *status)     /* IO - error status                            */
+/*
+  Get keywords from the Header of the PRimary array:
+  Check that the keywords conform to the FITS standard and return the
+  parameters which determine the size and structure of the primary array
+  or IMAGE extension.
+*/
+{
+    int idummy;
+    LONGLONG lldummy;
+    double ddummy;
+
+    ffgphd(fptr, maxdim, simple, bitpix, naxis, naxes, pcount, gcount, extend,
+          &ddummy, &ddummy, &lldummy, &idummy, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffghtb(fitsfile *fptr,  /* I - FITS file pointer                        */
+           int maxfield,    /* I - maximum no. of columns to read;          */
+           long *naxis1,    /* O - length of table row in bytes             */
+           long *naxis2,    /* O - number of rows in the table              */
+           int *tfields,    /* O - number of columns in the table           */
+           char **ttype,    /* O - name of each column                      */
+           long *tbcol,     /* O - byte offset in row to each column        */
+           char **tform,    /* O - value of TFORMn keyword for each column  */
+           char **tunit,    /* O - value of TUNITn keyword for each column  */
+           char *extnm,   /* O - value of EXTNAME keyword, if any         */
+           int *status)     /* IO - error status                            */
+/*
+  Get keywords from the Header of the ASCII TaBle:
+  Check that the keywords conform to the FITS standard and return the
+  parameters which describe the table.
+*/
+{
+    int ii, maxf, nfound, tstatus;
+    long fields;
+    char name[FLEN_KEYWORD], value[FLEN_VALUE], comm[FLEN_COMMENT];
+    char xtension[FLEN_VALUE], message[81];
+    LONGLONG llnaxis1, llnaxis2, pcount;
+
+    if (*status > 0)
+        return(*status);
+
+    /* read the first keyword of the extension */
+    ffgkyn(fptr, 1, name, value, comm, status);
+
+    if (!strcmp(name, "XTENSION"))
+    {
+            if (ffc2s(value, xtension, status) > 0)  /* get the value string */
+            {
+                ffpmsg("Bad value string for XTENSION keyword:");
+                ffpmsg(value);
+                return(*status);
+            }
+
+            /* allow the quoted string value to begin in any column and */
+            /* allow any number of trailing blanks before the closing quote */
+            if ( (value[0] != '\'')   ||  /* first char must be a quote */
+                 ( strcmp(xtension, "TABLE") ) )
+            {
+                sprintf(message,
+                "This is not a TABLE extension: %s", value);
+                ffpmsg(message);
+                return(*status = NOT_ATABLE);
+            }
+    }
+
+    else  /* error: 1st keyword of extension != XTENSION */
+    {
+        sprintf(message,
+        "First keyword of the extension is not XTENSION: %s", name);
+        ffpmsg(message);
+        return(*status = NO_XTENSION);
+    }
+
+    if (ffgttb(fptr, &llnaxis1, &llnaxis2, &pcount, &fields, status) > 0)
+        return(*status);
+
+    if (naxis1)
+       *naxis1 = (long) llnaxis1;
+
+    if (naxis2)
+       *naxis2 = (long) llnaxis2;
+
+    if (pcount != 0)
+    {
+       sprintf(message, "PCOUNT = %.0f is illegal in ASCII table; must = 0",
+               (double) pcount);
+       ffpmsg(message);
+       return(*status = BAD_PCOUNT);
+    }
+
+    if (tfields)
+       *tfields = fields;
+
+    if (maxfield < 0)
+        maxf = fields;
+    else
+        maxf = minvalue(maxfield, fields);
+
+    if (maxf > 0)
+    {
+        for (ii = 0; ii < maxf; ii++)
+        {   /* initialize optional keyword values */
+            if (ttype)
+                *ttype[ii] = '\0';   
+
+            if (tunit)
+                *tunit[ii] = '\0';
+        }
+
+   
+        if (ttype)
+            ffgkns(fptr, "TTYPE", 1, maxf, ttype, &nfound, status);
+
+        if (tunit)
+            ffgkns(fptr, "TUNIT", 1, maxf, tunit, &nfound, status);
+
+        if (*status > 0)
+            return(*status);
+
+        if (tbcol)
+        {
+            ffgknj(fptr, "TBCOL", 1, maxf, tbcol, &nfound, status);
+
+            if (*status > 0 || nfound != maxf)
+            {
+                ffpmsg(
+        "Required TBCOL keyword(s) not found in ASCII table header (ffghtb).");
+                return(*status = NO_TBCOL);
+            }
+        }
+
+        if (tform)
+        {
+            ffgkns(fptr, "TFORM", 1, maxf, tform, &nfound, status);
+
+            if (*status > 0 || nfound != maxf)
+            {
+                ffpmsg(
+        "Required TFORM keyword(s) not found in ASCII table header (ffghtb).");
+                return(*status = NO_TFORM);
+            }
+        }
+    }
+
+    if (extnm)
+    {
+        extnm[0] = '\0';
+
+        tstatus = *status;
+        ffgkys(fptr, "EXTNAME", extnm, comm, status);
+
+        if (*status == KEY_NO_EXIST)
+            *status = tstatus;  /* keyword not required, so ignore error */
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffghtbll(fitsfile *fptr, /* I - FITS file pointer                        */
+           int maxfield,    /* I - maximum no. of columns to read;          */
+           LONGLONG *naxis1, /* O - length of table row in bytes             */
+           LONGLONG *naxis2, /* O - number of rows in the table              */
+           int *tfields,    /* O - number of columns in the table           */
+           char **ttype,    /* O - name of each column                      */
+           LONGLONG *tbcol, /* O - byte offset in row to each column        */
+           char **tform,    /* O - value of TFORMn keyword for each column  */
+           char **tunit,    /* O - value of TUNITn keyword for each column  */
+           char *extnm,     /* O - value of EXTNAME keyword, if any         */
+           int *status)     /* IO - error status                            */
+/*
+  Get keywords from the Header of the ASCII TaBle:
+  Check that the keywords conform to the FITS standard and return the
+  parameters which describe the table.
+*/
+{
+    int ii, maxf, nfound, tstatus;
+    long fields;
+    char name[FLEN_KEYWORD], value[FLEN_VALUE], comm[FLEN_COMMENT];
+    char xtension[FLEN_VALUE], message[81];
+    LONGLONG llnaxis1, llnaxis2, pcount;
+
+    if (*status > 0)
+        return(*status);
+
+    /* read the first keyword of the extension */
+    ffgkyn(fptr, 1, name, value, comm, status);
+
+    if (!strcmp(name, "XTENSION"))
+    {
+            if (ffc2s(value, xtension, status) > 0)  /* get the value string */
+            {
+                ffpmsg("Bad value string for XTENSION keyword:");
+                ffpmsg(value);
+                return(*status);
+            }
+
+            /* allow the quoted string value to begin in any column and */
+            /* allow any number of trailing blanks before the closing quote */
+            if ( (value[0] != '\'')   ||  /* first char must be a quote */
+                 ( strcmp(xtension, "TABLE") ) )
+            {
+                sprintf(message,
+                "This is not a TABLE extension: %s", value);
+                ffpmsg(message);
+                return(*status = NOT_ATABLE);
+            }
+    }
+
+    else  /* error: 1st keyword of extension != XTENSION */
+    {
+        sprintf(message,
+        "First keyword of the extension is not XTENSION: %s", name);
+        ffpmsg(message);
+        return(*status = NO_XTENSION);
+    }
+
+    if (ffgttb(fptr, &llnaxis1, &llnaxis2, &pcount, &fields, status) > 0)
+        return(*status);
+
+    if (naxis1)
+       *naxis1 = llnaxis1;
+
+    if (naxis2)
+       *naxis2 = llnaxis2;
+
+    if (pcount != 0)
+    {
+       sprintf(message, "PCOUNT = %.0f is illegal in ASCII table; must = 0",
+             (double) pcount);
+       ffpmsg(message);
+       return(*status = BAD_PCOUNT);
+    }
+
+    if (tfields)
+       *tfields = fields;
+
+    if (maxfield < 0)
+        maxf = fields;
+    else
+        maxf = minvalue(maxfield, fields);
+
+    if (maxf > 0)
+    {
+        for (ii = 0; ii < maxf; ii++)
+        {   /* initialize optional keyword values */
+            if (ttype)
+                *ttype[ii] = '\0';   
+
+            if (tunit)
+                *tunit[ii] = '\0';
+        }
+
+   
+        if (ttype)
+            ffgkns(fptr, "TTYPE", 1, maxf, ttype, &nfound, status);
+
+        if (tunit)
+            ffgkns(fptr, "TUNIT", 1, maxf, tunit, &nfound, status);
+
+        if (*status > 0)
+            return(*status);
+
+        if (tbcol)
+        {
+            ffgknjj(fptr, "TBCOL", 1, maxf, tbcol, &nfound, status);
+
+            if (*status > 0 || nfound != maxf)
+            {
+                ffpmsg(
+        "Required TBCOL keyword(s) not found in ASCII table header (ffghtbll).");
+                return(*status = NO_TBCOL);
+            }
+        }
+
+        if (tform)
+        {
+            ffgkns(fptr, "TFORM", 1, maxf, tform, &nfound, status);
+
+            if (*status > 0 || nfound != maxf)
+            {
+                ffpmsg(
+        "Required TFORM keyword(s) not found in ASCII table header (ffghtbll).");
+                return(*status = NO_TFORM);
+            }
+        }
+    }
+
+    if (extnm)
+    {
+        extnm[0] = '\0';
+
+        tstatus = *status;
+        ffgkys(fptr, "EXTNAME", extnm, comm, status);
+
+        if (*status == KEY_NO_EXIST)
+            *status = tstatus;  /* keyword not required, so ignore error */
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffghbn(fitsfile *fptr,  /* I - FITS file pointer                        */
+           int maxfield,    /* I - maximum no. of columns to read;          */
+           long *naxis2,    /* O - number of rows in the table              */
+           int *tfields,    /* O - number of columns in the table           */
+           char **ttype,    /* O - name of each column                      */
+           char **tform,    /* O - TFORMn value for each column             */
+           char **tunit,    /* O - TUNITn value for each column             */
+           char *extnm,     /* O - value of EXTNAME keyword, if any         */
+           long *pcount,    /* O - value of PCOUNT keyword                  */
+           int *status)     /* IO - error status                            */
+/*
+  Get keywords from the Header of the BiNary table:
+  Check that the keywords conform to the FITS standard and return the
+  parameters which describe the table.
+*/
+{
+    int ii, maxf, nfound, tstatus;
+    long  fields;
+    char name[FLEN_KEYWORD], value[FLEN_VALUE], comm[FLEN_COMMENT];
+    char xtension[FLEN_VALUE], message[81];
+    LONGLONG naxis1ll, naxis2ll, pcountll;
+
+    if (*status > 0)
+        return(*status);
+
+    /* read the first keyword of the extension */
+    ffgkyn(fptr, 1, name, value, comm, status);
+
+    if (!strcmp(name, "XTENSION"))
+    {
+            if (ffc2s(value, xtension, status) > 0)  /* get the value string */
+            {
+                ffpmsg("Bad value string for XTENSION keyword:");
+                ffpmsg(value);
+                return(*status);
+            }
+
+            /* allow the quoted string value to begin in any column and */
+            /* allow any number of trailing blanks before the closing quote */
+            if ( (value[0] != '\'')   ||  /* first char must be a quote */
+                 ( strcmp(xtension, "BINTABLE") &&
+                   strcmp(xtension, "A3DTABLE") &&
+                   strcmp(xtension, "3DTABLE")
+                 ) )
+            {
+                sprintf(message,
+                "This is not a BINTABLE extension: %s", value);
+                ffpmsg(message);
+                return(*status = NOT_BTABLE);
+            }
+    }
+
+    else  /* error: 1st keyword of extension != XTENSION */
+    {
+        sprintf(message,
+        "First keyword of the extension is not XTENSION: %s", name);
+        ffpmsg(message);
+        return(*status = NO_XTENSION);
+    }
+
+    if (ffgttb(fptr, &naxis1ll, &naxis2ll, &pcountll, &fields, status) > 0)
+        return(*status);
+
+    if (naxis2)
+       *naxis2 = (long) naxis2ll;
+
+    if (pcount)
+       *pcount = (long) pcountll;
+
+    if (tfields)
+        *tfields = fields;
+
+    if (maxfield < 0)
+        maxf = fields;
+    else
+        maxf = minvalue(maxfield, fields);
+
+    if (maxf > 0)
+    {
+        for (ii = 0; ii < maxf; ii++)
+        {   /* initialize optional keyword values */
+            if (ttype)
+                *ttype[ii] = '\0';   
+
+            if (tunit)
+                *tunit[ii] = '\0';
+        }
+
+        if (ttype)
+            ffgkns(fptr, "TTYPE", 1, maxf, ttype, &nfound, status);
+
+        if (tunit)
+            ffgkns(fptr, "TUNIT", 1, maxf, tunit, &nfound, status);
+
+        if (*status > 0)
+            return(*status);
+
+        if (tform)
+        {
+            ffgkns(fptr, "TFORM", 1, maxf, tform, &nfound, status);
+
+            if (*status > 0 || nfound != maxf)
+            {
+                ffpmsg(
+        "Required TFORM keyword(s) not found in binary table header (ffghbn).");
+                return(*status = NO_TFORM);
+            }
+        }
+    }
+
+    if (extnm)
+    {
+        extnm[0] = '\0';
+
+        tstatus = *status;
+        ffgkys(fptr, "EXTNAME", extnm, comm, status);
+
+        if (*status == KEY_NO_EXIST)
+          *status = tstatus;  /* keyword not required, so ignore error */
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffghbnll(fitsfile *fptr,  /* I - FITS file pointer                        */
+           int maxfield,    /* I - maximum no. of columns to read;          */
+           LONGLONG *naxis2,    /* O - number of rows in the table              */
+           int *tfields,    /* O - number of columns in the table           */
+           char **ttype,    /* O - name of each column                      */
+           char **tform,    /* O - TFORMn value for each column             */
+           char **tunit,    /* O - TUNITn value for each column             */
+           char *extnm,     /* O - value of EXTNAME keyword, if any         */
+           LONGLONG *pcount,    /* O - value of PCOUNT keyword                  */
+           int *status)     /* IO - error status                            */
+/*
+  Get keywords from the Header of the BiNary table:
+  Check that the keywords conform to the FITS standard and return the
+  parameters which describe the table.
+*/
+{
+    int ii, maxf, nfound, tstatus;
+    long  fields;
+    char name[FLEN_KEYWORD], value[FLEN_VALUE], comm[FLEN_COMMENT];
+    char xtension[FLEN_VALUE], message[81];
+    LONGLONG naxis1ll, naxis2ll, pcountll;
+
+    if (*status > 0)
+        return(*status);
+
+    /* read the first keyword of the extension */
+    ffgkyn(fptr, 1, name, value, comm, status);
+
+    if (!strcmp(name, "XTENSION"))
+    {
+            if (ffc2s(value, xtension, status) > 0)  /* get the value string */
+            {
+                ffpmsg("Bad value string for XTENSION keyword:");
+                ffpmsg(value);
+                return(*status);
+            }
+
+            /* allow the quoted string value to begin in any column and */
+            /* allow any number of trailing blanks before the closing quote */
+            if ( (value[0] != '\'')   ||  /* first char must be a quote */
+                 ( strcmp(xtension, "BINTABLE") &&
+                   strcmp(xtension, "A3DTABLE") &&
+                   strcmp(xtension, "3DTABLE")
+                 ) )
+            {
+                sprintf(message,
+                "This is not a BINTABLE extension: %s", value);
+                ffpmsg(message);
+                return(*status = NOT_BTABLE);
+            }
+    }
+
+    else  /* error: 1st keyword of extension != XTENSION */
+    {
+        sprintf(message,
+        "First keyword of the extension is not XTENSION: %s", name);
+        ffpmsg(message);
+        return(*status = NO_XTENSION);
+    }
+
+    if (ffgttb(fptr, &naxis1ll, &naxis2ll, &pcountll, &fields, status) > 0)
+        return(*status);
+
+    if (naxis2)
+       *naxis2 = naxis2ll;
+
+    if (pcount)
+       *pcount = pcountll;
+
+    if (tfields)
+        *tfields = fields;
+
+    if (maxfield < 0)
+        maxf = fields;
+    else
+        maxf = minvalue(maxfield, fields);
+
+    if (maxf > 0)
+    {
+        for (ii = 0; ii < maxf; ii++)
+        {   /* initialize optional keyword values */
+            if (ttype)
+                *ttype[ii] = '\0';   
+
+            if (tunit)
+                *tunit[ii] = '\0';
+        }
+
+        if (ttype)
+            ffgkns(fptr, "TTYPE", 1, maxf, ttype, &nfound, status);
+
+        if (tunit)
+            ffgkns(fptr, "TUNIT", 1, maxf, tunit, &nfound, status);
+
+        if (*status > 0)
+            return(*status);
+
+        if (tform)
+        {
+            ffgkns(fptr, "TFORM", 1, maxf, tform, &nfound, status);
+
+            if (*status > 0 || nfound != maxf)
+            {
+                ffpmsg(
+        "Required TFORM keyword(s) not found in binary table header (ffghbn).");
+                return(*status = NO_TFORM);
+            }
+        }
+    }
+
+    if (extnm)
+    {
+        extnm[0] = '\0';
+
+        tstatus = *status;
+        ffgkys(fptr, "EXTNAME", extnm, comm, status);
+
+        if (*status == KEY_NO_EXIST)
+          *status = tstatus;  /* keyword not required, so ignore error */
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgphd(fitsfile *fptr,  /* I - FITS file pointer                        */
+           int maxdim,      /* I - maximum no. of dimensions to read;       */
+           int *simple,     /* O - does file conform to FITS standard? 1/0  */
+           int *bitpix,     /* O - number of bits per data value pixel      */
+           int *naxis,      /* O - number of axes in the data array         */
+           LONGLONG naxes[],    /* O - length of each data axis                 */
+           long *pcount,    /* O - number of group parameters (usually 0)   */
+           long *gcount,    /* O - number of random groups (usually 1 or 0) */
+           int *extend,     /* O - may FITS file haave extensions?          */
+           double *bscale,  /* O - array pixel linear scaling factor        */
+           double *bzero,   /* O - array pixel linear scaling zero point    */
+           LONGLONG *blank, /* O - value used to represent undefined pixels */
+           int *nspace,     /* O - number of blank keywords prior to END    */
+           int *status)     /* IO - error status                            */
+{
+/*
+  Get the Primary HeaDer parameters.  Check that the keywords conform to
+  the FITS standard and return the parameters which determine the size and
+  structure of the primary array or IMAGE extension.
+*/
+    int unknown, found_end, tstatus, ii, nextkey, namelen;
+    long longbitpix, longnaxis;
+    LONGLONG axislen;
+    char message[FLEN_ERRMSG], keyword[FLEN_KEYWORD];
+    char card[FLEN_CARD];
+    char name[FLEN_KEYWORD], value[FLEN_VALUE], comm[FLEN_COMMENT];
+    char xtension[FLEN_VALUE];
+
+    if (*status > 0)
+        return(*status);
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    if (simple)
+       *simple = 1;
+
+    unknown = 0;
+
+    /*--------------------------------------------------------------------*/
+    /*  Get 1st keyword of HDU and test whether it is SIMPLE or XTENSION  */
+    /*--------------------------------------------------------------------*/
+    ffgkyn(fptr, 1, name, value, comm, status);
+
+    if ((fptr->Fptr)->curhdu == 0) /* Is this the beginning of the FITS file? */
+    {
+        if (!strcmp(name, "SIMPLE"))
+        {
+            if (value[0] == 'F')
+            {
+                if (simple)
+                    *simple=0;          /* not a simple FITS file */
+            }
+            else if (value[0] != 'T')
+                return(*status = BAD_SIMPLE);
+        }
+
+        else
+        {
+            sprintf(message,
+                   "First keyword of the file is not SIMPLE: %s", name);
+            ffpmsg(message);
+            return(*status = NO_SIMPLE);
+        }
+    }
+
+    else    /* not beginning of the file, so presumably an IMAGE extension */
+    {       /* or it could be a compressed image in a binary table */
+
+        if (!strcmp(name, "XTENSION"))
+        {
+            if (ffc2s(value, xtension, status) > 0)  /* get the value string */
+            {
+                ffpmsg("Bad value string for XTENSION keyword:");
+                ffpmsg(value);
+                return(*status);
+            }
+
+            /* allow the quoted string value to begin in any column and */
+            /* allow any number of trailing blanks before the closing quote */
+            if ( (value[0] != '\'')   ||  /* first char must be a quote */
+                  ( strcmp(xtension, "IMAGE")  &&
+                    strcmp(xtension, "IUEIMAGE") ) )
+            {
+                unknown = 1;  /* unknown type of extension; press on anyway */
+                sprintf(message,
+                   "This is not an IMAGE extension: %s", value);
+                ffpmsg(message);
+            }
+        }
+
+        else  /* error: 1st keyword of extension != XTENSION */
+        {
+            sprintf(message,
+            "First keyword of the extension is not XTENSION: %s", name);
+            ffpmsg(message);
+            return(*status = NO_XTENSION);
+        }
+    }
+
+    if (unknown && (fptr->Fptr)->compressimg)
+    {
+        /* this is a compressed image, so read ZBITPIX, ZNAXIS keywords */
+        unknown = 0;  /* reset flag */
+        ffxmsg(3, message); /* clear previous spurious error message */
+
+        if (bitpix)
+        {
+            ffgidt(fptr, bitpix, status); /* get bitpix value */
+
+            if (*status > 0)
+            {
+                ffpmsg("Error reading BITPIX value of compressed image");
+                return(*status);
+            }
+        }
+
+        if (naxis)
+        {
+            ffgidm(fptr, naxis, status); /* get NAXIS value */
+
+            if (*status > 0)
+            {
+                ffpmsg("Error reading NAXIS value of compressed image");
+                return(*status);
+            }
+        }
+
+        if (naxes)
+        {
+            ffgiszll(fptr, maxdim, naxes, status);  /* get NAXISn value */
+
+            if (*status > 0)
+            {
+                ffpmsg("Error reading NAXISn values of compressed image");
+                return(*status);
+            }
+        }
+
+        nextkey = 9; /* skip required table keywords in the following search */
+    }
+    else
+    {
+
+        /*----------------------------------------------------------------*/
+        /*  Get 2nd keyword;  test whether it is BITPIX with legal value  */
+        /*----------------------------------------------------------------*/
+        ffgkyn(fptr, 2, name, value, comm, status);  /* BITPIX = 2nd keyword */
+
+        if (strcmp(name, "BITPIX"))
+        {
+            sprintf(message,
+            "Second keyword of the extension is not BITPIX: %s", name);
+            ffpmsg(message);
+            return(*status = NO_BITPIX);
+        }
+
+        if (ffc2ii(value,  &longbitpix, status) > 0)
+        {
+            sprintf(message,
+            "Value of BITPIX keyword is not an integer: %s", value);
+            ffpmsg(message);
+            return(*status = BAD_BITPIX);
+        }
+        else if (longbitpix != BYTE_IMG && longbitpix != SHORT_IMG &&
+             longbitpix != LONG_IMG && longbitpix != LONGLONG_IMG &&
+             longbitpix != FLOAT_IMG && longbitpix != DOUBLE_IMG)
+        {
+            sprintf(message,
+            "Illegal value for BITPIX keyword: %s", value);
+            ffpmsg(message);
+            return(*status = BAD_BITPIX);
+        }
+        if (bitpix)
+            *bitpix = longbitpix;  /* do explicit type conversion */
+
+        /*---------------------------------------------------------------*/
+        /*  Get 3rd keyword;  test whether it is NAXIS with legal value  */
+        /*---------------------------------------------------------------*/
+        ffgtkn(fptr, 3, "NAXIS",  &longnaxis, status);
+
+        if (*status == BAD_ORDER)
+            return(*status = NO_NAXIS);
+        else if (*status == NOT_POS_INT || longnaxis > 999)
+        {
+            sprintf(message,"NAXIS = %ld is illegal", longnaxis);
+            ffpmsg(message);
+            return(*status = BAD_NAXIS);
+        }
+        else
+            if (naxis)
+                 *naxis = longnaxis;  /* do explicit type conversion */
+
+        /*---------------------------------------------------------*/
+        /*  Get the next NAXISn keywords and test for legal values */
+        /*---------------------------------------------------------*/
+        for (ii=0, nextkey=4; ii < longnaxis; ii++, nextkey++)
+        {
+            ffkeyn("NAXIS", ii+1, keyword, status);
+            ffgtknjj(fptr, 4+ii, keyword, &axislen, status);
+
+            if (*status == BAD_ORDER)
+                return(*status = NO_NAXES);
+            else if (*status == NOT_POS_INT)
+                return(*status = BAD_NAXES);
+            else if (ii < maxdim)
+                if (naxes)
+                    naxes[ii] = axislen;
+        }
+    }
+
+    /*---------------------------------------------------------*/
+    /*  now look for other keywords of interest:               */
+    /*  BSCALE, BZERO, BLANK, PCOUNT, GCOUNT, EXTEND, and END  */
+    /*---------------------------------------------------------*/
+
+    /*  initialize default values in case keyword is not present */
+    if (bscale)
+        *bscale = 1.0;
+    if (bzero)
+        *bzero  = 0.0;
+    if (pcount)
+        *pcount = 0;
+    if (gcount)
+        *gcount = 1;
+    if (extend)
+        *extend = 0;
+    if (blank)
+      *blank = NULL_UNDEFINED; /* no default null value for BITPIX=8,16,32 */
+
+    *nspace = 0;
+    found_end = 0;
+    tstatus = *status;
+
+    for (; !found_end; nextkey++)  
+    {
+      /* get next keyword */
+      /* don't use ffgkyn here because it trys to parse the card to read */
+      /* the value string, thus failing to read the file just because of */
+      /* minor syntax errors in optional keywords.                       */
+
+      if (ffgrec(fptr, nextkey, card, status) > 0 )  /* get the 80-byte card */
+      {
+        if (*status == KEY_OUT_BOUNDS)
+        {
+          found_end = 1;  /* simply hit the end of the header */
+          *status = tstatus;  /* reset error status */
+        }
+        else          
+        {
+          ffpmsg("Failed to find the END keyword in header (ffgphd).");
+        }
+      }
+      else /* got the next keyword without error */
+      {
+        ffgknm(card, name, &namelen, status); /* get the keyword name */
+
+        if (fftrec(name, status) > 0)  /* test keyword name; catches no END */
+        {
+          sprintf(message,
+              "Name of keyword no. %d contains illegal character(s): %s",
+              nextkey, name);
+          ffpmsg(message);
+
+          if (nextkey % 36 == 0) /* test if at beginning of 36-card record */
+            ffpmsg("  (This may indicate a missing END keyword).");
+        }
+
+        if (!strcmp(name, "BSCALE") && bscale)
+        {
+            *nspace = 0;  /* reset count of blank keywords */
+            ffpsvc(card, value, comm, status); /* parse value and comment */
+
+            if (ffc2dd(value, bscale, status) > 0) /* convert to double */
+            {
+                /* reset error status and continue, but still issue warning */
+                *status = tstatus;
+                *bscale = 1.0;
+
+                sprintf(message,
+                "Error reading BSCALE keyword value as a double: %s", value);
+                ffpmsg(message);
+            }
+        }
+
+        else if (!strcmp(name, "BZERO") && bzero)
+        {
+            *nspace = 0;  /* reset count of blank keywords */
+            ffpsvc(card, value, comm, status); /* parse value and comment */
+
+            if (ffc2dd(value, bzero, status) > 0) /* convert to double */
+            {
+                /* reset error status and continue, but still issue warning */
+                *status = tstatus;
+                *bzero = 0.0;
+
+                sprintf(message,
+                "Error reading BZERO keyword value as a double: %s", value);
+                ffpmsg(message);
+            }
+        }
+
+        else if (!strcmp(name, "BLANK") && blank)
+        {
+            *nspace = 0;  /* reset count of blank keywords */
+            ffpsvc(card, value, comm, status); /* parse value and comment */
+
+            if (ffc2jj(value, blank, status) > 0) /* convert to LONGLONG */
+            {
+                /* reset error status and continue, but still issue warning */
+                *status = tstatus;
+                *blank = NULL_UNDEFINED;
+
+                sprintf(message,
+                "Error reading BLANK keyword value as an integer: %s", value);
+                ffpmsg(message);
+            }
+        }
+
+        else if (!strcmp(name, "PCOUNT") && pcount)
+        {
+            *nspace = 0;  /* reset count of blank keywords */
+            ffpsvc(card, value, comm, status); /* parse value and comment */
+
+            if (ffc2ii(value, pcount, status) > 0) /* convert to long */
+            {
+                sprintf(message,
+                "Error reading PCOUNT keyword value as an integer: %s", value);
+                ffpmsg(message);
+            }
+        }
+
+        else if (!strcmp(name, "GCOUNT") && gcount)
+        {
+            *nspace = 0;  /* reset count of blank keywords */
+            ffpsvc(card, value, comm, status); /* parse value and comment */
+
+            if (ffc2ii(value, gcount, status) > 0) /* convert to long */
+            {
+                sprintf(message,
+                "Error reading GCOUNT keyword value as an integer: %s", value);
+                ffpmsg(message);
+            }
+        }
+
+        else if (!strcmp(name, "EXTEND") && extend)
+        {
+            *nspace = 0;  /* reset count of blank keywords */
+            ffpsvc(card, value, comm, status); /* parse value and comment */
+
+            if (ffc2ll(value, extend, status) > 0) /* convert to logical */
+            {
+                /* reset error status and continue, but still issue warning */
+                *status = tstatus;
+                *extend = 0;
+
+                sprintf(message,
+                "Error reading EXTEND keyword value as a logical: %s", value);
+                ffpmsg(message);
+            }
+        }
+
+        else if (!strcmp(name, "END"))
+            found_end = 1;
+
+        else if (!card[0] )
+            *nspace = *nspace + 1;  /* this is a blank card in the header */
+
+        else
+            *nspace = 0;  /* reset count of blank keywords immediately
+                            before the END keyword to zero   */
+      }
+
+      if (*status > 0)  /* exit on error after writing error message */
+      {
+        if ((fptr->Fptr)->curhdu == 0)
+            ffpmsg(
+            "Failed to read the required primary array header keywords.");
+        else
+            ffpmsg(
+            "Failed to read the required image extension header keywords.");
+
+        return(*status);
+      }
+    }
+
+    if (unknown)
+       *status = NOT_IMAGE;
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgttb(fitsfile *fptr,      /* I - FITS file pointer*/
+           LONGLONG *rowlen,        /* O - length of a table row, in bytes */
+           LONGLONG *nrows,         /* O - number of rows in the table */
+           LONGLONG *pcount,    /* O - value of PCOUNT keyword */
+           long *tfields,       /* O - number of fields in the table */
+           int *status)         /* IO - error status    */
+{
+/*
+  Get and Test TaBle;
+  Test that this is a legal ASCII or binary table and get some keyword values.
+  We assume that the calling routine has already tested the 1st keyword
+  of the extension to ensure that this is really a table extension.
+*/
+    if (*status > 0)
+        return(*status);
+
+    if (fftkyn(fptr, 2, "BITPIX", "8", status) == BAD_ORDER) /* 2nd keyword */
+        return(*status = NO_BITPIX);  /* keyword not BITPIX */
+    else if (*status == NOT_POS_INT)
+        return(*status = BAD_BITPIX); /* value != 8 */
+
+    if (fftkyn(fptr, 3, "NAXIS", "2", status) == BAD_ORDER) /* 3rd keyword */
+        return(*status = NO_NAXIS);  /* keyword not NAXIS */
+    else if (*status == NOT_POS_INT)
+        return(*status = BAD_NAXIS); /* value != 2 */
+
+    if (ffgtknjj(fptr, 4, "NAXIS1", rowlen, status) == BAD_ORDER) /* 4th key */
+        return(*status = NO_NAXES);  /* keyword not NAXIS1 */
+    else if (*status == NOT_POS_INT)
+        return(*status == BAD_NAXES); /* bad NAXIS1 value */
+
+    if (ffgtknjj(fptr, 5, "NAXIS2", nrows, status) == BAD_ORDER) /* 5th key */
+        return(*status = NO_NAXES);  /* keyword not NAXIS2 */
+    else if (*status == NOT_POS_INT)
+        return(*status == BAD_NAXES); /* bad NAXIS2 value */
+
+    if (ffgtknjj(fptr, 6, "PCOUNT", pcount, status) == BAD_ORDER) /* 6th key */
+        return(*status = NO_PCOUNT);  /* keyword not PCOUNT */
+    else if (*status == NOT_POS_INT)
+        return(*status = BAD_PCOUNT); /* bad PCOUNT value */
+
+    if (fftkyn(fptr, 7, "GCOUNT", "1", status) == BAD_ORDER) /* 7th keyword */
+        return(*status = NO_GCOUNT);  /* keyword not GCOUNT */
+    else if (*status == NOT_POS_INT)
+        return(*status = BAD_GCOUNT); /* value != 1 */
+
+    if (ffgtkn(fptr, 8, "TFIELDS", tfields, status) == BAD_ORDER) /* 8th key*/
+        return(*status = NO_TFIELDS);  /* keyword not TFIELDS */
+    else if (*status == NOT_POS_INT || *tfields > 999)
+        return(*status == BAD_TFIELDS); /* bad TFIELDS value */
+
+
+    if (*status > 0)
+       ffpmsg(
+       "Error reading required keywords in the table header (FTGTTB).");
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgtkn(fitsfile *fptr,  /* I - FITS file pointer              */
+           int numkey,      /* I - number of the keyword to read  */
+           char *name,      /* I - expected name of the keyword   */
+           long *value,     /* O - integer value of the keyword   */
+           int *status)     /* IO - error status                  */
+{
+/*
+  test that keyword number NUMKEY has the expected name and get the
+  integer value of the keyword.  Return an error if the keyword
+  name does not match the input name, or if the value of the
+  keyword is not a positive integer.
+*/
+    char keyname[FLEN_KEYWORD], valuestring[FLEN_VALUE];
+    char comm[FLEN_COMMENT], message[FLEN_ERRMSG];
+   
+    if (*status > 0)
+        return(*status);
+    
+    keyname[0] = '\0';
+    valuestring[0] = '\0';
+
+    if (ffgkyn(fptr, numkey, keyname, valuestring, comm, status) <= 0)
+    {
+        if (strcmp(keyname, name) )
+            *status = BAD_ORDER;  /* incorrect keyword name */
+
+        else
+        {
+            ffc2ii(valuestring, value, status);  /* convert to integer */
+
+            if (*status > 0 || *value < 0 )
+               *status = NOT_POS_INT;
+        }
+
+        if (*status > 0)
+        {
+            sprintf(message,
+              "ffgtkn found unexpected keyword or value for keyword no. %d.",
+              numkey);
+            ffpmsg(message);
+
+            sprintf(message,
+              " Expected positive integer keyword %s, but instead", name);
+            ffpmsg(message);
+
+            sprintf(message,
+              " found keyword %s with value %s", keyname, valuestring);
+            ffpmsg(message);
+        }
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgtknjj(fitsfile *fptr,  /* I - FITS file pointer              */
+           int numkey,      /* I - number of the keyword to read  */
+           char *name,      /* I - expected name of the keyword   */
+           LONGLONG *value, /* O - integer value of the keyword   */
+           int *status)     /* IO - error status                  */
+{
+/*
+  test that keyword number NUMKEY has the expected name and get the
+  integer value of the keyword.  Return an error if the keyword
+  name does not match the input name, or if the value of the
+  keyword is not a positive integer.
+*/
+    char keyname[FLEN_KEYWORD], valuestring[FLEN_VALUE];
+    char comm[FLEN_COMMENT], message[FLEN_ERRMSG];
+   
+    if (*status > 0)
+        return(*status);
+    
+    keyname[0] = '\0';
+    valuestring[0] = '\0';
+
+    if (ffgkyn(fptr, numkey, keyname, valuestring, comm, status) <= 0)
+    {
+        if (strcmp(keyname, name) )
+            *status = BAD_ORDER;  /* incorrect keyword name */
+
+        else
+        {
+            ffc2jj(valuestring, value, status);  /* convert to integer */
+
+            if (*status > 0 || *value < 0 )
+               *status = NOT_POS_INT;
+        }
+
+        if (*status > 0)
+        {
+            sprintf(message,
+              "ffgtknjj found unexpected keyword or value for keyword no. %d.",
+              numkey);
+            ffpmsg(message);
+
+            sprintf(message,
+              " Expected positive integer keyword %s, but instead", name);
+            ffpmsg(message);
+
+            sprintf(message,
+              " found keyword %s with value %s", keyname, valuestring);
+            ffpmsg(message);
+        }
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fftkyn(fitsfile *fptr,  /* I - FITS file pointer              */
+           int numkey,      /* I - number of the keyword to read  */
+           char *name,      /* I - expected name of the keyword   */
+           char *value,     /* I - expected value of the keyword  */
+           int *status)     /* IO - error status                  */
+{
+/*
+  test that keyword number NUMKEY has the expected name and the
+  expected value string.
+*/
+    char keyname[FLEN_KEYWORD], valuestring[FLEN_VALUE];
+    char comm[FLEN_COMMENT], message[FLEN_ERRMSG];
+   
+    if (*status > 0)
+        return(*status);
+    
+    keyname[0] = '\0';
+    valuestring[0] = '\0';
+
+    if (ffgkyn(fptr, numkey, keyname, valuestring, comm, status) <= 0)
+    {
+        if (strcmp(keyname, name) )
+            *status = BAD_ORDER;  /* incorrect keyword name */
+
+        if (strcmp(value, valuestring) )
+            *status = NOT_POS_INT;  /* incorrect keyword value */
+    }
+
+    if (*status > 0)
+    {
+        sprintf(message,
+          "fftkyn found unexpected keyword or value for keyword no. %d.",
+          numkey);
+        ffpmsg(message);
+
+        sprintf(message,
+          " Expected keyword %s with value %s, but", name, value);
+        ffpmsg(message);
+
+        sprintf(message,
+          " found keyword %s with value %s", keyname, valuestring);
+        ffpmsg(message);
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffh2st(fitsfile *fptr,   /* I - FITS file pointer           */
+           char **header,    /* O - returned header string      */
+           int  *status)     /* IO - error status               */
+
+/*
+  read header keywords into a long string of chars.  This routine allocates
+  memory for the string, so the calling routine must eventually free the
+  memory when it is not needed any more.
+*/
+{
+    int nkeys;
+    long nrec;
+    LONGLONG headstart;
+
+    if (*status > 0)
+        return(*status);
+
+    /* get number of keywords in the header (doesn't include END) */
+    if (ffghsp(fptr, &nkeys, NULL, status) > 0)
+        return(*status);
+
+    nrec = (nkeys / 36 + 1);
+
+    /* allocate memory for all the keywords (multiple of 2880 bytes) */
+    *header = (char *) calloc ( nrec * 2880 + 1, 1);
+    if (!(*header))
+    {
+         *status = MEMORY_ALLOCATION;
+         ffpmsg("failed to allocate memory to hold all the header keywords");
+         return(*status);
+    }
+
+    ffghadll(fptr, &headstart, NULL, NULL, status); /* get header address */
+    ffmbyt(fptr, headstart, REPORT_EOF, status);   /* move to header */
+    ffgbyt(fptr, nrec * 2880, *header, status);     /* copy header */
+    *(*header + (nrec * 2880)) = '\0';
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffhdr2str( fitsfile *fptr,  /* I - FITS file pointer                    */
+            int exclude_comm,   /* I - if TRUE, exclude commentary keywords */
+            char **exclist,     /* I - list of excluded keyword names       */
+            int nexc,           /* I - number of names in exclist           */
+            char **header,      /* O - returned header string               */
+            int *nkeys,         /* O - returned number of 80-char keywords  */
+            int  *status)       /* IO - error status                        */
+/*
+  read header keywords into a long string of chars.  This routine allocates
+  memory for the string, so the calling routine must eventually free the
+  memory when it is not needed any more.  If exclude_comm is TRUE, then all 
+  the COMMENT, HISTORY, and <blank> keywords will be excluded from the output
+  string of keywords.  Any other list of keywords to be excluded may be
+  specified with the exclist parameter.
+*/
+{
+    int casesn, match, exact, totkeys;
+    long ii, jj;
+    char keybuf[162], keyname[FLEN_KEYWORD], *headptr;
+
+    *nkeys = 0;
+
+    if (*status > 0)
+        return(*status);
+
+    /* get number of keywords in the header (doesn't include END) */
+    if (ffghsp(fptr, &totkeys, NULL, status) > 0)
+        return(*status);
+
+    /* allocate memory for all the keywords */
+    /* (will reallocate it later to minimize the memory size) */
+    
+    *header = (char *) calloc ( (totkeys + 1) * 80 + 1, 1);
+    if (!(*header))
+    {
+         *status = MEMORY_ALLOCATION;
+         ffpmsg("failed to allocate memory to hold all the header keywords");
+         return(*status);
+    }
+
+    headptr = *header;
+    casesn = FALSE;
+
+    /* read every keyword */
+    for (ii = 1; ii <= totkeys; ii++) 
+    {
+        ffgrec(fptr, ii, keybuf, status);
+        /* pad record with blanks so that it is at least 80 chars long */
+        strcat(keybuf,
+    "                                                                                ");
+
+        keyname[0] = '\0';
+        strncat(keyname, keybuf, 8); /* copy the keyword name */
+        
+        if (exclude_comm)
+        {
+            if (!FSTRCMP("COMMENT ", keyname) ||
+                !FSTRCMP("HISTORY ", keyname) ||
+                !FSTRCMP("        ", keyname) )
+              continue;  /* skip this commentary keyword */
+        }
+
+        /* does keyword match any names in the exclusion list? */
+        for (jj = 0; jj < nexc; jj++ )
+        {
+            ffcmps(exclist[jj], keyname, casesn, &match, &exact);
+                 if (match)
+                     break;
+        }
+
+        if (jj == nexc)
+        {
+            /* not in exclusion list, add this keyword to the string */
+            strcpy(headptr, keybuf);
+            headptr += 80;
+            (*nkeys)++;
+        }
+    }
+
+    /* add the END keyword */
+    strcpy(headptr,
+    "END                                                                             ");
+    headptr += 80;
+    (*nkeys)++;
+
+    *headptr = '\0';   /* terminate the header string */
+    /* minimize the allocated memory */
+    *header = (char *) realloc(*header, (*nkeys *80) + 1);  
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffcnvthdr2str( fitsfile *fptr,  /* I - FITS file pointer                    */
+            int exclude_comm,   /* I - if TRUE, exclude commentary keywords */
+            char **exclist,     /* I - list of excluded keyword names       */
+            int nexc,           /* I - number of names in exclist           */
+            char **header,      /* O - returned header string               */
+            int *nkeys,         /* O - returned number of 80-char keywords  */
+            int  *status)       /* IO - error status                        */
+/*
+  Same as ffhdr2str, except that if the input HDU is a tile compressed image
+  (stored in a binary table) then it will first convert that header back
+  to that of a normal uncompressed FITS image before concatenating the header
+  keyword records.
+*/
+{
+    fitsfile *tempfptr;
+    
+    if (*status > 0)
+        return(*status);
+
+    if (fits_is_compressed_image(fptr, status) )
+    {
+        /* this is a tile compressed image, so need to make an uncompressed */
+	/* copy of the image header in memory before concatenating the keywords */
+        if (fits_create_file(&tempfptr, "mem://", status) > 0) {
+	    return(*status);
+	}
+
+	if (fits_img_decompress_header(fptr, tempfptr, status) > 0) {
+	    fits_delete_file(tempfptr, status);
+	    return(*status);
+	}
+
+	ffhdr2str(tempfptr, exclude_comm, exclist, nexc, header, nkeys, status);
+	fits_close_file(tempfptr, status);
+
+    } else {
+        ffhdr2str(fptr, exclude_comm, exclist, nexc, header, nkeys, status);
+    }
+
+    return(*status);
+}
diff --git a/src/plugins/cfitsio/group.c b/src/plugins/cfitsio/group.c
new file mode 100644
index 0000000..2883e72
--- /dev/null
+++ b/src/plugins/cfitsio/group.c
@@ -0,0 +1,6463 @@
+/*  This file, group.c, contains the grouping convention suport routines.  */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+/*                                                                         */
+/*  The group.c module of CFITSIO was written by Donald G. Jennings of     */
+/*  the INTEGRAL Science Data Centre (ISDC) under NASA contract task       */
+/*  66002J6. The above copyright laws apply. Copyright guidelines of The   */
+/*  University of Geneva might also apply.                                 */
+
+/*  The following routines are designed to create, read, and manipulate    */
+/*  FITS Grouping Tables as defined in the FITS Grouping Convention paper  */
+/*  by Jennings, Pence, Folk and Schlesinger. The development of the       */
+/*  grouping structure was partially funded under the NASA AISRP Program.  */ 
+    
+#include "fitsio2.h"
+#include "group.h"
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#if defined(WIN32) || defined(__WIN32__)
+#include <direct.h>   /* defines the getcwd function on Windows PCs */
+#endif
+
+#if defined(unix) || defined(__unix__)  || defined(__unix)
+#include <unistd.h>  /* needed for getcwd prototype on unix machines */
+#endif
+
+#define HEX_ESCAPE '%'
+
+/*---------------------------------------------------------------------------
+ Change record:
+
+D. Jennings, 18/06/98, version 1.0 of group module delivered to B. Pence for
+                       integration into CFITSIO 2.005
+
+D. Jennings, 17/11/98, fixed bug in ffgtcpr(). Now use fits_find_nextkey()
+                       correctly and insert auxiliary keyword records 
+		       directly before the TTYPE1 keyword in the copied
+		       group table.
+
+D. Jennings, 22/01/99, ffgmop() now looks for relative file paths when 
+                       the MEMBER_LOCATION information is given in a 
+		       grouping table.
+
+D. Jennings, 01/02/99, ffgtop() now looks for relatve file paths when 
+                       the GRPLCn keyword value is supplied in the member
+		       HDU header.
+
+D. Jennings, 01/02/99, ffgtam() now trys to construct relative file paths
+                       from the member's file to the group table's file
+		       (and visa versa) when both the member's file and
+		       group table file are of access type FILE://.
+
+D. Jennings, 05/05/99, removed the ffgtcn() function; made obsolete by
+                       fits_get_url().
+
+D. Jennings, 05/05/99, updated entire module to handle partial URLs and
+                       absolute URLs more robustly. Host dependent directory
+		       paths are now converted to true URLs before being
+		       read from/written to grouping tables.
+
+D. Jennings, 05/05/99, added the following new functions (note, none of these
+                       are directly callable by the application)
+
+		       int fits_path2url()
+		       int fits_url2path()
+		       int fits_get_cwd()
+		       int fits_get_url()
+		       int fits_clean_url()
+		       int fits_relurl2url()
+		       int fits_encode_url()
+		       int fits_unencode_url()
+		       int fits_is_url_absolute()
+
+-----------------------------------------------------------------------------*/
+
+/*---------------------------------------------------------------------------*/
+int ffgtcr(fitsfile *fptr,      /* FITS file pointer                         */
+	   char    *grpname,    /* name of the grouping table                */
+	   int      grouptype,  /* code specifying the type of
+				   grouping table information:
+				   GT_ID_ALL_URI  0 ==> defualt (all columns)
+				   GT_ID_REF      1 ==> ID by reference
+				   GT_ID_POS      2 ==> ID by position
+				   GT_ID_ALL      3 ==> ID by ref. and position
+				   GT_ID_REF_URI 11 ==> (1) + URI info 
+				   GT_ID_POS_URI 12 ==> (2) + URI info       */
+	   int      *status    )/* return status code                        */
+
+/* 
+   create a grouping table at the end of the current FITS file. This
+   function makes the last HDU in the file the CHDU, then calls the
+   fits_insert_group() function to actually create the new grouping table.
+*/
+
+{
+  int hdutype;
+  int hdunum;
+
+
+  if(*status != 0) return(*status);
+
+
+  *status = fits_get_num_hdus(fptr,&hdunum,status);
+
+  /* If hdunum is 0 then we are at the beginning of the file and
+     we actually haven't closed the first header yet, so don't do
+     anything more */
+
+  if (0 != hdunum) {
+
+      *status = fits_movabs_hdu(fptr,hdunum,&hdutype,status);
+  }
+
+  /* Now, the whole point of the above two fits_ calls was to get to
+     the end of file.  Let's ignore errors at this point and keep
+     going since any error is likely to mean that we are already at the 
+     EOF, or the file is fatally corrupted.  If we are at the EOF then
+     the next fits_ call will be ok.  If it's corrupted then the
+     next call will fail, but that's not big deal at this point.
+  */
+
+  if (0 != *status ) *status = 0;
+
+  *status = fits_insert_group(fptr,grpname,grouptype,status);
+
+  return(*status);
+}
+
+/*---------------------------------------------------------------------------*/
+int ffgtis(fitsfile *fptr,      /* FITS file pointer                         */
+	   char    *grpname,    /* name of the grouping table                */
+	   int      grouptype,  /* code specifying the type of
+				   grouping table information:
+				   GT_ID_ALL_URI  0 ==> defualt (all columns)
+				   GT_ID_REF      1 ==> ID by reference
+				   GT_ID_POS      2 ==> ID by position
+				   GT_ID_ALL      3 ==> ID by ref. and position
+				   GT_ID_REF_URI 11 ==> (1) + URI info 
+				   GT_ID_POS_URI 12 ==> (2) + URI info       */
+	   int      *status)     /* return status code                       */
+	   
+/* 
+   insert a grouping table just after the current HDU of the current FITS file.
+   This is the same as fits_create_group() only it allows the user to select
+   the place within the FITS file to add the grouping table.
+*/
+
+{
+
+  int tfields  = 0;
+  int hdunum   = 0;
+  int hdutype  = 0;
+  int extver;
+  int i;
+  
+  long pcount  = 0;
+
+  char *ttype[6];
+  char *tform[6];
+
+  char ttypeBuff[102];  
+  char tformBuff[54];  
+
+  char  extname[] = "GROUPING";
+  char  keyword[FLEN_KEYWORD];
+  char  keyvalue[FLEN_VALUE];
+  char  comment[FLEN_COMMENT];
+    
+  do
+    {
+
+      /* set up the ttype and tform character buffers */
+
+      for(i = 0; i < 6; ++i)
+	{
+	  ttype[i] = ttypeBuff+(i*17);
+	  tform[i] = tformBuff+(i*9);
+	}
+
+      /* define the columns required according to the grouptype parameter */
+
+      *status = ffgtdc(grouptype,0,0,0,0,0,0,ttype,tform,&tfields,status);
+
+      /* create the grouping table using the columns defined above */
+
+      *status = fits_insert_btbl(fptr,0,tfields,ttype,tform,NULL,
+				 NULL,pcount,status);
+
+      if(*status != 0) continue;
+
+      /*
+	 retrieve the hdu position of the new grouping table for
+	 future use
+      */
+
+      fits_get_hdu_num(fptr,&hdunum);
+
+      /*
+	 add the EXTNAME and EXTVER keywords to the HDU just after the 
+	 TFIELDS keyword; for now the EXTVER value is set to 0, it will be 
+	 set to the correct value later on
+      */
+
+      fits_read_keyword(fptr,"TFIELDS",keyvalue,comment,status);
+
+      fits_insert_key_str(fptr,"EXTNAME",extname,
+			  "HDU contains a Grouping Table",status);
+      fits_insert_key_lng(fptr,"EXTVER",0,"Grouping Table vers. (this file)",
+			  status);
+
+      /* 
+	 if the grpname parameter value was defined (Non NULL and non zero
+	 length) then add the GRPNAME keyword and value
+      */
+
+      if(grpname != NULL && strlen(grpname) > 0)
+	fits_insert_key_str(fptr,"GRPNAME",grpname,"Grouping Table name",
+			    status);
+
+      /* 
+	 add the TNULL keywords and values for each integer column defined;
+	 integer null values are zero (0) for the MEMBER_POSITION and 
+	 MEMBER_VERSION columns.
+      */
+
+      for(i = 0; i < tfields && *status == 0; ++i)
+	{	  
+	  if(strcasecmp(ttype[i],"MEMBER_POSITION") == 0 ||
+	     strcasecmp(ttype[i],"MEMBER_VERSION")  == 0)
+	    {
+	      sprintf(keyword,"TFORM%d",i+1);
+	      *status = fits_read_key_str(fptr,keyword,keyvalue,comment,
+					  status);
+	 
+	      sprintf(keyword,"TNULL%d",i+1);
+
+	      *status = fits_insert_key_lng(fptr,keyword,0,"Column Null Value",
+					    status);
+	    }
+	}
+
+      /*
+	 determine the correct EXTVER value for the new grouping table
+	 by finding the highest numbered grouping table EXTVER value
+	 the currently exists
+      */
+
+      for(extver = 1;
+	  (fits_movnam_hdu(fptr,ANY_HDU,"GROUPING",extver,status)) == 0; 
+	  ++extver);
+
+      if(*status == BAD_HDU_NUM) *status = 0;
+
+      /*
+	 move back to the new grouping table HDU and update the EXTVER
+	 keyword value
+      */
+
+      fits_movabs_hdu(fptr,hdunum,&hdutype,status);
+
+      fits_modify_key_lng(fptr,"EXTVER",extver,"&",status);
+
+    }while(0);
+
+
+  return(*status);
+}
+
+/*---------------------------------------------------------------------------*/
+int ffgtch(fitsfile *gfptr,     /* FITS pointer to group                     */
+	   int       grouptype, /* code specifying the type of
+				   grouping table information:
+				   GT_ID_ALL_URI  0 ==> defualt (all columns)
+				   GT_ID_REF      1 ==> ID by reference
+				   GT_ID_POS      2 ==> ID by position
+				   GT_ID_ALL      3 ==> ID by ref. and position
+				   GT_ID_REF_URI 11 ==> (1) + URI info 
+				   GT_ID_POS_URI 12 ==> (2) + URI info       */
+	   int      *status)     /* return status code                       */
+
+
+/* 
+   Change the grouping table structure of the grouping table pointed to by
+   gfptr. The grouptype code specifies the new structure of the table. This
+   operation only adds or removes grouping table columns, it does not add
+   or delete group members (i.e., table rows). If the grouping table already
+   has the desired structure then no operations are performed and function   
+   simply returns with a (0) success status code. If the requested structure
+   change creates new grouping table columns, then the column values for all
+   existing members will be filled with the appropriate null values.
+*/
+
+{
+  int xtensionCol, extnameCol, extverCol, positionCol, locationCol, uriCol;
+  int ncols    = 0;
+  int colnum   = 0;
+  int nrows    = 0;
+  int grptype  = 0;
+  int i,j;
+
+  long intNull  = 0;
+  long tfields  = 0;
+  
+  char *tform[6];
+  char *ttype[6];
+
+  unsigned char  charNull[1] = {'\0'};
+
+  char ttypeBuff[102];  
+  char tformBuff[54];  
+
+  char  keyword[FLEN_KEYWORD];
+  char  keyvalue[FLEN_VALUE];
+  char  comment[FLEN_COMMENT];
+
+
+  if(*status != 0) return(*status);
+
+  do
+    {
+      /* set up the ttype and tform character buffers */
+
+      for(i = 0; i < 6; ++i)
+	{
+	  ttype[i] = ttypeBuff+(i*17);
+	  tform[i] = tformBuff+(i*9);
+	}
+
+      /* retrieve positions of all Grouping table reserved columns */
+
+      *status = ffgtgc(gfptr,&xtensionCol,&extnameCol,&extverCol,&positionCol,
+		       &locationCol,&uriCol,&grptype,status);
+
+      if(*status != 0) continue;
+
+      /* determine the total number of grouping table columns */
+
+      *status = fits_read_key_lng(gfptr,"TFIELDS",&tfields,comment,status);
+
+      /* define grouping table columns to be added to the configuration */
+
+      *status = ffgtdc(grouptype,xtensionCol,extnameCol,extverCol,positionCol,
+		       locationCol,uriCol,ttype,tform,&ncols,status);
+
+      /*
+	delete any grouping tables columns that exist but do not belong to
+	new desired configuration; note that we delete before creating new
+	columns for (file size) efficiency reasons
+      */
+
+      switch(grouptype)
+	{
+
+	case GT_ID_ALL_URI:
+
+	  /* no columns to be deleted in this case */
+
+	  break;
+
+	case GT_ID_REF:
+
+	  if(positionCol != 0) 
+	    {
+	      *status = fits_delete_col(gfptr,positionCol,status);
+	      --tfields;
+	      if(uriCol      > positionCol)  --uriCol;
+	      if(locationCol > positionCol) --locationCol;
+	    }
+	  if(uriCol      != 0)
+	    { 
+	    *status = fits_delete_col(gfptr,uriCol,status);
+	      --tfields;
+	      if(locationCol > uriCol) --locationCol;
+	    }
+	  if(locationCol != 0) 
+	    *status = fits_delete_col(gfptr,locationCol,status);
+
+	  break;
+
+	case  GT_ID_POS:
+
+	  if(xtensionCol != 0) 
+	    {
+	      *status = fits_delete_col(gfptr,xtensionCol,status);
+	      --tfields;
+	      if(extnameCol  > xtensionCol)  --extnameCol;
+	      if(extverCol   > xtensionCol)  --extverCol;
+	      if(uriCol      > xtensionCol)  --uriCol;
+	      if(locationCol > xtensionCol)  --locationCol;
+	    }
+	  if(extnameCol  != 0) 
+	    {
+	      *status = fits_delete_col(gfptr,extnameCol,status);
+	      --tfields;
+	      if(extverCol   > extnameCol)  --extverCol;
+	      if(uriCol      > extnameCol)  --uriCol;
+	      if(locationCol > extnameCol)  --locationCol;
+	    }
+	  if(extverCol   != 0)
+	    { 
+	      *status = fits_delete_col(gfptr,extverCol,status);
+	      --tfields;
+	      if(uriCol      > extverCol)  --uriCol;
+	      if(locationCol > extverCol)  --locationCol;
+	    }
+	  if(uriCol      != 0)
+	    { 
+	      *status = fits_delete_col(gfptr,uriCol,status);
+	      --tfields;
+	      if(locationCol > uriCol)  --locationCol;
+	    }
+	  if(locationCol != 0)
+	    { 
+	      *status = fits_delete_col(gfptr,locationCol,status);
+	      --tfields;
+	    }
+	  
+	  break;
+
+	case  GT_ID_ALL:
+
+	  if(uriCol      != 0) 
+	    {
+	      *status = fits_delete_col(gfptr,uriCol,status);
+	      --tfields;
+	      if(locationCol > uriCol)  --locationCol;
+	    }
+	  if(locationCol != 0)
+	    { 
+	      *status = fits_delete_col(gfptr,locationCol,status);
+	      --tfields;
+	    }
+
+	  break;
+
+	case GT_ID_REF_URI:
+
+	  if(positionCol != 0)
+	    { 
+	      *status = fits_delete_col(gfptr,positionCol,status);
+	      --tfields;
+	    }
+
+	  break;
+
+	case  GT_ID_POS_URI:
+
+	  if(xtensionCol != 0) 
+	    {
+	      *status = fits_delete_col(gfptr,xtensionCol,status);
+	      --tfields;
+	      if(extnameCol > xtensionCol)  --extnameCol;
+	      if(extverCol  > xtensionCol)  --extverCol;
+	    }
+	  if(extnameCol  != 0)
+	    { 
+	      *status = fits_delete_col(gfptr,extnameCol,status);
+	      --tfields;
+	      if(extverCol > extnameCol)  --extverCol;
+	    }
+	  if(extverCol   != 0)
+	    { 
+	      *status = fits_delete_col(gfptr,extverCol,status);
+	      --tfields;
+	    }
+
+	  break;
+
+	default:
+
+	  *status = BAD_OPTION;
+	  ffpmsg("Invalid value for grouptype parameter specified (ffgtch)");
+	  break;
+
+	}
+
+      /*
+	add all the new grouping table columns that were not there
+	previously but are called for by the grouptype parameter
+      */
+
+      for(i = 0; i < ncols && *status == 0; ++i)
+	*status = fits_insert_col(gfptr,tfields+i+1,ttype[i],tform[i],status);
+
+      /* 
+	 add the TNULL keywords and values for each new integer column defined;
+	 integer null values are zero (0) for the MEMBER_POSITION and 
+	 MEMBER_VERSION columns. Insert a null ("/0") into each new string
+	 column defined: MEMBER_XTENSION, MEMBER_NAME, MEMBER_URI_TYPE and
+	 MEMBER_LOCATION. Note that by convention a null string is the
+	 TNULL value for character fields so no TNULL is required.
+      */
+
+      for(i = 0; i < ncols && *status == 0; ++i)
+	{	  
+	  if(strcasecmp(ttype[i],"MEMBER_POSITION") == 0 ||
+	     strcasecmp(ttype[i],"MEMBER_VERSION")  == 0)
+	    {
+	      /* col contains int data; set TNULL and insert 0 for each col */
+
+	      *status = fits_get_colnum(gfptr,CASESEN,ttype[i],&colnum,
+					status);
+	      
+	      sprintf(keyword,"TFORM%d",colnum);
+
+	      *status = fits_read_key_str(gfptr,keyword,keyvalue,comment,
+					  status);
+	 
+	      sprintf(keyword,"TNULL%d",colnum);
+
+	      *status = fits_insert_key_lng(gfptr,keyword,0,
+					    "Column Null Value",status);
+
+	      for(j = 1; j <= nrows && *status == 0; ++j)
+		*status = fits_write_col_lng(gfptr,colnum,j,1,1,&intNull,
+					     status);
+	    }
+	  else if(strcasecmp(ttype[i],"MEMBER_XTENSION") == 0 ||
+		  strcasecmp(ttype[i],"MEMBER_NAME")     == 0 ||
+		  strcasecmp(ttype[i],"MEMBER_URI_TYPE") == 0 ||
+		  strcasecmp(ttype[i],"MEMBER_LOCATION") == 0)
+	    {
+
+	      /* new col contains character data; insert NULLs into each col */
+
+	      *status = fits_get_colnum(gfptr,CASESEN,ttype[i],&colnum,
+					status);
+
+	      for(j = 1; j <= nrows && *status == 0; ++j)
+	    /* WILL THIS WORK FOR VAR LENTH CHAR COLS??????*/
+		*status = fits_write_col_byt(gfptr,colnum,j,1,1,charNull,
+					     status);
+	    }
+	}
+
+    }while(0);
+
+  return(*status);
+}
+
+/*---------------------------------------------------------------------------*/
+int ffgtrm(fitsfile *gfptr,  /* FITS file pointer to group                   */
+	   int       rmopt,  /* code specifying if member
+				elements are to be deleted:
+				OPT_RM_GPT ==> remove only group table
+				OPT_RM_ALL ==> recursively remove members
+				and their members (if groups)                */
+	   int      *status) /* return status code                           */
+	    
+/*
+  remove a grouping table, and optionally all its members. Any groups 
+  containing the grouping table are updated, and all members (if not 
+  deleted) have their GRPIDn and GRPLCn keywords updated accordingly. 
+  If the (deleted) members are members of another grouping table then those
+  tables are also updated. The CHDU of the FITS file pointed to by gfptr must 
+  be positioned to the grouping table to be deleted.
+*/
+
+{
+  int hdutype;
+
+  long i;
+  long nmembers = 0;
+
+  HDUtracker HDU;
+  
+
+  if(*status != 0) return(*status);
+
+  /*
+     remove the grouping table depending upon the rmopt parameter
+  */
+
+  switch(rmopt)
+    {
+
+    case OPT_RM_GPT:
+
+      /*
+	 for this option, the grouping table is deleted, but the member
+	 HDUs remain; in this case we only have to remove each member from
+	 the grouping table by calling fits_remove_member() with the
+	 OPT_RM_ENTRY option
+      */
+
+      /* get the number of members contained by this table */
+
+      *status = fits_get_num_members(gfptr,&nmembers,status);
+
+      /* loop over all grouping table members and remove them */
+
+      for(i = nmembers; i > 0 && *status == 0; --i)
+	*status = fits_remove_member(gfptr,i,OPT_RM_ENTRY,status);
+      
+	break;
+
+    case OPT_RM_ALL:
+
+      /*
+	for this option the entire Group is deleted -- this includes all
+	members and their members (if grouping tables themselves). Call 
+	the recursive form of this function to perform the removal.
+      */
+
+      /* add the current grouping table to the HDUtracker struct */
+
+      HDU.nHDU = 0;
+
+      *status = fftsad(gfptr,&HDU,NULL,NULL);
+
+      /* call the recursive group remove function */
+
+      *status = ffgtrmr(gfptr,&HDU,status);
+
+      /* free the memory allocated to the HDUtracker struct */
+
+      for(i = 0; i < HDU.nHDU; ++i)
+	{
+	  free(HDU.filename[i]);
+	  free(HDU.newFilename[i]);
+	}
+
+      break;
+
+    default:
+      
+      *status = BAD_OPTION;
+      ffpmsg("Invalid value for the rmopt parameter specified (ffgtrm)");
+      break;
+
+     }
+
+  /*
+     if all went well then unlink and delete the grouping table HDU
+  */
+
+  *status = ffgmul(gfptr,0,status);
+
+  *status = fits_delete_hdu(gfptr,&hdutype,status);
+      
+  return(*status);
+}
+
+/*---------------------------------------------------------------------------*/
+int ffgtcp(fitsfile *infptr,  /* input FITS file pointer                     */
+	   fitsfile *outfptr, /* output FITS file pointer                    */
+	   int        cpopt,  /* code specifying copy options:
+				OPT_GCP_GPT (0) ==> copy only grouping table
+				OPT_GCP_ALL (2) ==> recusrively copy members 
+				                    and their members (if 
+						    groups)                  */
+	   int      *status)  /* return status code                          */
+
+/*
+  copy a grouping table, and optionally all its members, to a new FITS file.
+  If the cpopt is set to OPT_GCP_GPT (copy grouping table only) then the 
+  existing members have their GRPIDn and GRPLCn keywords updated to reflect 
+  the existance of the new group, since they now belong to another group. If 
+  cpopt is set to OPT_GCP_ALL (copy grouping table and members recursively) 
+  then the original members are not updated; the new grouping table is 
+  modified to include only the copied member HDUs and not the original members.
+
+  Note that the recursive version of this function, ffgtcpr(), is called
+  to perform the group table copy. In the case of cpopt == OPT_GCP_GPT
+  ffgtcpr() does not actually use recursion.
+*/
+
+{
+  int i;
+
+  HDUtracker HDU;
+
+
+  if(*status != 0) return(*status);
+
+  /* make sure infptr and outfptr are not the same pointer */
+
+  if(infptr == outfptr) *status = IDENTICAL_POINTERS;
+  else
+    {
+
+      /* initialize the HDUtracker struct */
+      
+      HDU.nHDU = 0;
+      
+      *status = fftsad(infptr,&HDU,NULL,NULL);
+      
+      /* 
+	 call the recursive form of this function to copy the grouping table. 
+	 If the cpopt is OPT_GCP_GPT then there is actually no recursion
+	 performed
+      */
+
+      *status = ffgtcpr(infptr,outfptr,cpopt,&HDU,status);
+  
+      /* free memory allocated for the HDUtracker struct */
+
+      for(i = 0; i < HDU.nHDU; ++i) 
+	{
+	  free(HDU.filename[i]);
+	  free(HDU.newFilename[i]);
+	}
+    }
+
+  return(*status);
+}
+
+/*---------------------------------------------------------------------------*/
+int ffgtmg(fitsfile *infptr,  /* FITS file ptr to source grouping table      */
+	   fitsfile *outfptr, /* FITS file ptr to target grouping table      */
+	   int       mgopt,   /* code specifying merge options:
+				 OPT_MRG_COPY (0) ==> copy members to target
+				                      group, leaving source 
+						      group in place
+				 OPT_MRG_MOV  (1) ==> move members to target
+				                      group, source group is
+						      deleted after merge    */
+	   int      *status)   /* return status code                         */
+     
+
+/*
+  merge two grouping tables by combining their members into a single table. 
+  The source grouping table must be the CHDU of the fitsfile pointed to by 
+  infptr, and the target grouping table must be the CHDU of the fitsfile to by 
+  outfptr. All members of the source grouping table shall be copied to the
+  target grouping table. If the mgopt parameter is OPT_MRG_COPY then the source
+  grouping table continues to exist after the merge. If the mgopt parameter
+  is OPT_MRG_MOV then the source grouping table is deleted after the merge, 
+  and all member HDUs are updated accordingly.
+*/
+{
+  long i ;
+  long nmembers = 0;
+
+  fitsfile *tmpfptr = NULL;
+
+
+  if(*status != 0) return(*status);
+
+  do
+    {
+
+      *status = fits_get_num_members(infptr,&nmembers,status);
+
+      for(i = 1; i <= nmembers && *status == 0; ++i)
+	{
+	  *status = fits_open_member(infptr,i,&tmpfptr,status);
+	  *status = fits_add_group_member(outfptr,tmpfptr,0,status);
+
+	  if(*status == HDU_ALREADY_MEMBER) *status = 0;
+
+	  if(tmpfptr != NULL)
+	    {
+	      fits_close_file(tmpfptr,status);
+	      tmpfptr = NULL;
+	    }
+	}
+
+      if(*status != 0) continue;
+
+      if(mgopt == OPT_MRG_MOV) 
+	*status = fits_remove_group(infptr,OPT_RM_GPT,status);
+
+    }while(0);
+
+  if(tmpfptr != NULL)
+    {
+      fits_close_file(tmpfptr,status);
+    }
+
+  return(*status);
+}
+
+/*---------------------------------------------------------------------------*/
+int ffgtcm(fitsfile *gfptr,  /* FITS file pointer to grouping table          */
+	   int       cmopt,  /* code specifying compact options
+				OPT_CMT_MBR      (1) ==> compact only direct 
+			                                 members (if groups)
+				OPT_CMT_MBR_DEL (11) ==> (1) + delete all 
+				                         compacted groups    */
+	   int      *status) /* return status code                           */
+    
+/*
+  "Compact" a group pointed to by the FITS file pointer gfptr. This 
+  is achieved by flattening the tree structure of a group and its 
+  (grouping table) members. All members HDUs of a grouping table which is 
+  itself a member of the grouping table gfptr are added to gfptr. Optionally,
+  the grouping tables which are "compacted" are deleted. If the grouping 
+  table contains no members that are themselves grouping tables then this 
+  function performs a NOOP.
+*/
+
+{
+  long i;
+  long nmembers = 0;
+
+  char keyvalue[FLEN_VALUE];
+  char comment[FLEN_COMMENT];
+
+  fitsfile *mfptr = NULL;
+
+
+  if(*status != 0) return(*status);
+
+  do
+    {
+      if(cmopt != OPT_CMT_MBR && cmopt != OPT_CMT_MBR_DEL)
+	{
+	  *status = BAD_OPTION;
+	  ffpmsg("Invalid value for cmopt parameter specified (ffgtcm)");
+	  continue;
+	}
+
+      /* reteive the number of grouping table members */
+
+      *status = fits_get_num_members(gfptr,&nmembers,status);
+
+      /*
+	loop over all the grouping table members; if the member is a 
+	grouping table then merge its members with the parent grouping 
+	table 
+      */
+
+      for(i = 1; i <= nmembers && *status == 0; ++i)
+	{
+	  *status = fits_open_member(gfptr,i,&mfptr,status);
+
+	  if(*status != 0) continue;
+
+	  *status = fits_read_key_str(mfptr,"EXTNAME",keyvalue,comment,status);
+
+	  /* if no EXTNAME keyword then cannot be a grouping table */
+
+	  if(*status == KEY_NO_EXIST) 
+	    {
+	      *status = 0;
+	      continue;
+	    }
+	  prepare_keyvalue(keyvalue);
+
+	  if(*status != 0) continue;
+
+	  /* if EXTNAME == "GROUPING" then process member as grouping table */
+
+	  if(strcasecmp(keyvalue,"GROUPING") == 0)
+	    {
+	      /* merge the member (grouping table) into the grouping table */
+
+	      *status = fits_merge_groups(mfptr,gfptr,OPT_MRG_COPY,status);
+
+	      *status = fits_close_file(mfptr,status);
+	      mfptr = NULL;
+
+	      /* 
+		 remove the member from the grouping table now that all of
+		 its members have been transferred; if cmopt is set to
+		 OPT_CMT_MBR_DEL then remove and delete the member
+	      */
+
+	      if(cmopt == OPT_CMT_MBR)
+		*status = fits_remove_member(gfptr,i,OPT_RM_ENTRY,status);
+	      else
+		*status = fits_remove_member(gfptr,i,OPT_RM_MBR,status);
+	    }
+	  else
+	    {
+	      /* not a grouping table; just close the opened member */
+
+	      *status = fits_close_file(mfptr,status);
+	      mfptr = NULL;
+	    }
+	}
+
+    }while(0);
+
+  return(*status);
+}
+
+/*--------------------------------------------------------------------------*/
+int ffgtvf(fitsfile *gfptr,       /* FITS file pointer to group             */
+	   long     *firstfailed, /* Member ID (if positive) of first failed
+				     member HDU verify check or GRPID index
+				     (if negitive) of first failed group
+				     link verify check.                     */
+	   int      *status)      /* return status code                     */
+
+/*
+ check the integrity of a grouping table to make sure that all group members 
+ are accessible and all the links to other grouping tables are valid. The
+ firstfailed parameter returns the member ID of the first member HDU to fail
+ verification if positive or the first group link to fail if negative; 
+ otherwise firstfailed contains a return value of 0.
+*/
+
+{
+  long i;
+  long nmembers = 0;
+  long ngroups  = 0;
+
+  char errstr[FLEN_VALUE];
+
+  fitsfile *fptr = NULL;
+
+
+  if(*status != 0) return(*status);
+
+  *firstfailed = 0;
+
+  do
+    {
+      /*
+	attempt to open all the members of the grouping table. We stop
+	at the first member which cannot be opened (which implies that it
+	cannot be located)
+      */
+
+      *status = fits_get_num_members(gfptr,&nmembers,status);
+
+      for(i = 1; i <= nmembers && *status == 0; ++i)
+	{
+	  *status = fits_open_member(gfptr,i,&fptr,status);
+	  fits_close_file(fptr,status);
+	}
+
+      /*
+	if the status is non-zero from the above loop then record the
+	member index that caused the error
+      */
+
+      if(*status != 0)
+	{
+	  *firstfailed = i;
+	  sprintf(errstr,"Group table verify failed for member %ld (ffgtvf)",
+		  i);
+	  ffpmsg(errstr);
+	  continue;
+	}
+
+      /*
+	attempt to open all the groups linked to this grouping table. We stop
+	at the first group which cannot be opened (which implies that it
+	cannot be located)
+      */
+
+      *status = fits_get_num_groups(gfptr,&ngroups,status);
+
+      for(i = 1; i <= ngroups && *status == 0; ++i)
+	{
+	  *status = fits_open_group(gfptr,i,&fptr,status);
+	  fits_close_file(fptr,status);
+	}
+
+      /*
+	if the status from the above loop is non-zero, then record the
+	GRPIDn index of the group that caused the failure
+      */
+
+      if(*status != 0)
+	{
+	  *firstfailed = -1*i;
+	  sprintf(errstr,
+		  "Group table verify failed for GRPID index %ld (ffgtvf)",i);
+	  ffpmsg(errstr);
+	  continue;
+	}
+
+    }while(0);
+
+  return(*status);
+}
+
+/*---------------------------------------------------------------------------*/
+int ffgtop(fitsfile *mfptr,  /* FITS file pointer to the member HDU          */
+	   int       grpid,  /* group ID (GRPIDn index) within member HDU    */
+	   fitsfile **gfptr, /* FITS file pointer to grouping table HDU      */
+	   int      *status) /* return status code                           */
+
+/*
+  open the grouping table that contains the member HDU. The member HDU must
+  be the CHDU of the FITS file pointed to by mfptr, and the grouping table
+  is identified by the Nth index number of the GRPIDn keywords specified in 
+  the member HDU's header. The fitsfile gfptr pointer is positioned with the
+  appropriate FITS file with the grouping table as the CHDU. If the group
+  grouping table resides in a file other than the member then an attempt
+  is first made to open the file readwrite, and failing that readonly.
+ 
+  Note that it is possible for the GRPIDn/GRPLCn keywords in a member 
+  header to be non-continuous, e.g., GRPID1, GRPID2, GRPID5, GRPID6. In 
+  such cases, the grpid index value specified in the function call shall
+  identify the (grpid)th GRPID value. In the above example, if grpid == 3,
+  then the group specified by GRPID5 would be opened.
+*/
+{
+  int i;
+  int found;
+
+  long ngroups   = 0;
+  long grpExtver = 0;
+
+  char keyword[FLEN_KEYWORD];
+  char keyvalue[FLEN_FILENAME];
+  char *tkeyvalue;
+  char location[FLEN_FILENAME];
+  char location1[FLEN_FILENAME];
+  char location2[FLEN_FILENAME];
+  char comment[FLEN_COMMENT];
+
+  char *url[2];
+
+
+  if(*status != 0) return(*status);
+
+  do
+    {
+      /* set the grouping table pointer to NULL for error checking later */
+
+      *gfptr = NULL;
+
+      /*
+	make sure that the group ID requested is valid ==> cannot be
+	larger than the number of GRPIDn keywords in the member HDU header
+      */
+
+      *status = fits_get_num_groups(mfptr,&ngroups,status);
+
+      if(grpid > ngroups)
+	{
+	  *status = BAD_GROUP_ID;
+	  sprintf(comment,
+		  "GRPID index %d larger total GRPID keywords %ld (ffgtop)",
+		  grpid,ngroups);
+	  ffpmsg(comment);
+	  continue;
+	}
+
+      /*
+	find the (grpid)th group that the member HDU belongs to and read
+	the value of the GRPID(grpid) keyword; fits_get_num_groups()
+	automatically re-enumerates the GRPIDn/GRPLCn keywords to fill in
+	any gaps
+      */
+
+      sprintf(keyword,"GRPID%d",grpid);
+
+      *status = fits_read_key_lng(mfptr,keyword,&grpExtver,comment,status);
+
+      if(*status != 0) continue;
+
+      /*
+	if the value of the GRPIDn keyword is positive then the member is
+	in the same FITS file as the grouping table and we only have to
+	reopen the current FITS file. Else the member and grouping table
+	HDUs reside in different files and another FITS file must be opened
+	as specified by the corresponding GRPLCn keyword
+	
+	The DO WHILE loop only executes once and is used to control the
+	file opening logic.
+      */
+
+      do
+	{
+	  if(grpExtver > 0) 
+	    {
+	      /*
+		the member resides in the same file as the grouping
+		 table, so just reopen the grouping table file
+	      */
+
+	      *status = fits_reopen_file(mfptr,gfptr,status);
+	      continue;
+	    }
+
+	  else if(grpExtver == 0)
+	    {
+	      /* a GRPIDn value of zero (0) is undefined */
+
+	      *status = BAD_GROUP_ID;
+	      sprintf(comment,"Invalid value of %ld for GRPID%d (ffgtop)",
+		      grpExtver,grpid);
+	      ffpmsg(comment);
+	      continue;
+	    }
+
+	  /* 
+	     The GRPLCn keyword value is negative, which implies that
+	     the grouping table must reside in another FITS file;
+	     search for the corresponding GRPLCn keyword 
+	  */
+	  
+	  /* set the grpExtver value positive */
+  
+	  grpExtver = -1*grpExtver;
+
+	  /* read the GRPLCn keyword value */
+
+	  sprintf(keyword,"GRPLC%d",grpid);
+	  /* SPR 1738 */
+	  *status = fits_read_key_longstr(mfptr,keyword,&tkeyvalue,comment,
+				      status);
+	  if (0 == *status) {
+	    strcpy(keyvalue,tkeyvalue);
+	    free(tkeyvalue);
+	  }
+	  
+
+	  /* if the GRPLCn keyword was not found then there is a problem */
+
+	  if(*status == KEY_NO_EXIST)
+	    {
+	      *status = BAD_GROUP_ID;
+
+	      sprintf(comment,"Cannot find GRPLC%d keyword (ffgtop)",
+		      grpid);
+	      ffpmsg(comment);
+
+	      continue;
+	    }
+
+	  prepare_keyvalue(keyvalue);
+
+	  /*
+	    if the GRPLCn keyword value specifies an absolute URL then
+	    try to open the file; we cannot attempt any relative URL
+	    or host-dependent file path reconstruction
+	  */
+
+	  if(fits_is_url_absolute(keyvalue))
+	    {
+	      ffpmsg("Try to open group table file as absolute URL (ffgtop)");
+
+	      *status = fits_open_file(gfptr,keyvalue,READWRITE,status);
+
+	      /* if the open was successful then continue */
+
+	      if(*status == 0) continue;
+
+	      /* if READWRITE failed then try opening it READONLY */
+
+	      ffpmsg("OK, try open group table file as READONLY (ffgtop)");
+	      
+	      *status = 0;
+	      *status = fits_open_file(gfptr,keyvalue,READONLY,status);
+
+	      /* continue regardless of the outcome */
+
+	      continue;
+	    }
+
+	  /*
+	    see if the URL gives a file path that is absolute on the
+	    host machine 
+	  */
+
+	  *status = fits_url2path(keyvalue,location1,status);
+
+	  *status = fits_open_file(gfptr,location1,READWRITE,status);
+
+	  /* if the file opened then continue */
+
+	  if(*status == 0) continue;
+
+	  /* if READWRITE failed then try opening it READONLY */
+
+	  ffpmsg("OK, try open group table file as READONLY (ffgtop)");
+	  
+	  *status = 0;
+	  *status = fits_open_file(gfptr,location1,READONLY,status);
+
+	  /* if the file opened then continue */
+
+	  if(*status == 0) continue;
+
+	  /*
+	    the grouping table location given by GRPLCn must specify a 
+	    relative URL. We assume that this URL is relative to the 
+	    member HDU's FITS file. Try to construct a full URL location 
+	    for the grouping table's FITS file and then open it
+	  */
+
+	  *status = 0;
+		  
+	  /* retrieve the URL information for the member HDU's file */
+		  
+	  url[0] = location1; url[1] = location2;
+		  
+	  *status = fits_get_url(mfptr,url[0],url[1],NULL,NULL,NULL,status);
+
+	  /*
+	    It is possible that the member HDU file has an initial
+	    URL it was opened with and a real URL that the file actually
+	    exists at (e.g., an HTTP accessed file copied to a local
+	    file). For each possible URL try to construct a
+	  */
+		  
+	  for(i = 0, found = 0, *gfptr = NULL; i < 2 && !found; ++i)
+	    {
+	      
+	      /* the url string could be empty */
+	      
+	      if(*url[i] == 0) continue;
+	      
+	      /* 
+		 create a full URL from the partial and the member
+		 HDU file URL
+	      */
+	      
+	      *status = fits_relurl2url(url[i],keyvalue,location,status);
+	      
+	      /* if an error occured then contniue */
+	      
+	      if(*status != 0) 
+		{
+		  *status = 0;
+		  continue;
+		}
+	      
+	      /*
+		if the location does not specify an access method
+		then turn it into a host dependent path
+	      */
+
+	      if(! fits_is_url_absolute(location))
+		{
+		  *status = fits_url2path(location,url[i],status);
+		  strcpy(location,url[i]);
+		}
+	      
+	      /* try to open the grouping table file READWRITE */
+	      
+	      *status = fits_open_file(gfptr,location,READWRITE,status);
+	      
+	      if(*status != 0)
+		{    
+		  /* try to open the grouping table file READONLY */
+		  
+		  ffpmsg("opening file as READWRITE failed (ffgtop)");
+		  ffpmsg("OK, try to open file as READONLY (ffgtop)");
+		  *status = 0;
+		  *status = fits_open_file(gfptr,location,READONLY,status);
+		}
+	      
+	      /* either set the found flag or reset the status flag */
+	      
+	      if(*status == 0) 
+		found = 1;
+	      else
+		*status = 0;
+	    }
+
+	}while(0); /* end of file opening loop */
+
+      /* if an error occured with the file opening then exit */
+
+      if(*status != 0) continue;
+  
+      if(*gfptr == NULL)
+	{
+	  ffpmsg("Cannot open or find grouping table FITS file (ffgtop)");
+	  *status = GROUP_NOT_FOUND;
+	  continue;
+	}
+
+      /* search for the grouping table in its FITS file */
+
+      *status = fits_movnam_hdu(*gfptr,ANY_HDU,"GROUPING",(int)grpExtver,
+				status);
+
+      if(*status != 0) *status = GROUP_NOT_FOUND;
+
+    }while(0);
+
+  if(*status != 0 && *gfptr != NULL) 
+    {
+      fits_close_file(*gfptr,status);
+      *gfptr = NULL;
+    }
+
+  return(*status);
+}
+/*---------------------------------------------------------------------------*/
+int ffgtam(fitsfile *gfptr,   /* FITS file pointer to grouping table HDU     */
+	   fitsfile *mfptr,   /* FITS file pointer to member HDU             */
+	   int       hdupos,  /* member HDU position IF in the same file as
+			         the grouping table AND mfptr == NULL        */
+	   int      *status)  /* return status code                          */
+ 
+/*
+  add a member HDU to an existing grouping table. The fitsfile pointer gfptr
+  must be positioned with the grouping table as the CHDU. The member HDU
+  may either be identifed with the fitsfile *mfptr (which must be positioned
+  to the member HDU) or the hdupos parameter (the HDU number of the member 
+  HDU) if both reside in the same FITS file. The hdupos value is only used
+  if the mfptr parameter has a value of NULL (0). The new member HDU shall 
+  have the appropriate GRPIDn and GRPLCn keywords created in its header.
+
+  Note that if the member HDU to be added to the grouping table is already
+  a member of the group then it will not be added a sceond time.
+*/
+
+{
+  int xtensionCol,extnameCol,extverCol,positionCol,locationCol,uriCol;
+  int memberPosition = 0;
+  int grptype        = 0;
+  int hdutype        = 0;
+  int useLocation    = 0;
+  int nkeys          = 6;
+  int found;
+  int i;
+
+  int memberIOstate;
+  int groupIOstate;
+  int iomode;
+
+  long memberExtver = 0;
+  long groupExtver  = 0;
+  long memberID     = 0;
+  long nmembers     = 0;
+  long ngroups      = 0;
+  long grpid        = 0;
+
+  char memberAccess1[FLEN_VALUE];
+  char memberAccess2[FLEN_VALUE];
+  char memberFileName[FLEN_FILENAME];
+  char memberLocation[FLEN_FILENAME];
+  char grplc[FLEN_FILENAME];
+  char *tgrplc;
+  char memberHDUtype[FLEN_VALUE];
+  char memberExtname[FLEN_VALUE];
+  char memberURI[] = "URL";
+
+  char groupAccess1[FLEN_VALUE];
+  char groupAccess2[FLEN_VALUE];
+  char groupFileName[FLEN_FILENAME];
+  char groupLocation[FLEN_FILENAME];
+  char tmprootname[FLEN_FILENAME], grootname[FLEN_FILENAME];
+  char cwd[FLEN_FILENAME];
+
+  char *keys[] = {"GRPNAME","EXTVER","EXTNAME","TFIELDS","GCOUNT","EXTEND"};
+  char *tmpPtr[1];
+
+  char keyword[FLEN_KEYWORD];
+  char card[FLEN_CARD];
+
+  unsigned char charNull[]  = {'\0'};
+
+  fitsfile *tmpfptr = NULL;
+
+  int parentStatus = 0;
+
+  if(*status != 0) return(*status);
+
+  do
+    {
+      /*
+	make sure the grouping table can be modified before proceeding
+      */
+
+      fits_file_mode(gfptr,&iomode,status);
+
+      if(iomode != READWRITE)
+	{
+	  ffpmsg("cannot modify grouping table (ffgtam)");
+	  *status = BAD_GROUP_ATTACH;
+	  continue;
+	}
+
+      /*
+	 if the calling function supplied the HDU position of the member
+	 HDU instead of fitsfile pointer then get a fitsfile pointer
+      */
+
+      if(mfptr == NULL)
+	{
+	  *status = fits_reopen_file(gfptr,&tmpfptr,status);
+	  *status = fits_movabs_hdu(tmpfptr,hdupos,&hdutype,status);
+
+	  if(*status != 0) continue;
+	}
+      else
+	tmpfptr = mfptr;
+
+      /*
+	 determine all the information about the member HDU that will
+	 be needed later; note that we establish the default values for
+	 all information values that are not explicitly found
+      */
+
+      *status = fits_read_key_str(tmpfptr,"XTENSION",memberHDUtype,card,
+				  status);
+
+      if(*status == KEY_NO_EXIST) 
+	{
+	  strcpy(memberHDUtype,"PRIMARY");
+	  *status = 0;
+	}
+      prepare_keyvalue(memberHDUtype);
+
+      *status = fits_read_key_lng(tmpfptr,"EXTVER",&memberExtver,card,status);
+
+      if(*status == KEY_NO_EXIST) 
+	{
+	  memberExtver = 1;
+	  *status      = 0;
+	}
+
+      *status = fits_read_key_str(tmpfptr,"EXTNAME",memberExtname,card,
+				  status);
+
+      if(*status == KEY_NO_EXIST) 
+	{
+	  memberExtname[0] = 0;
+	  *status          = 0;
+	}
+      prepare_keyvalue(memberExtname);
+
+      fits_get_hdu_num(tmpfptr,&memberPosition);
+
+      /*
+	Determine if the member HDU's FITS file location needs to be
+	taken into account when building its grouping table reference
+
+	If the member location needs to be used (==> grouping table and member
+	HDU reside in different files) then create an appropriate URL for
+	the member HDU's file and grouping table's file. Note that the logic
+	for this is rather complicated
+      */
+
+      /* SPR 3463, don't do this 
+	 if(tmpfptr->Fptr == gfptr->Fptr)
+	 {  */
+	  /*
+	    member HDU and grouping table reside in the same file, no need
+	    to use the location information */
+	  
+      /* printf ("same file\n");
+	   
+	   useLocation     = 0;
+	   memberIOstate   = 1;
+	   *memberFileName = 0;
+	}
+      else
+      { */ 
+	  /*
+	     the member HDU and grouping table FITS file location information 
+	     must be used.
+
+	     First determine the correct driver and file name for the group
+	     table and member HDU files. If either are disk files then
+	     construct an absolute file path for them. Finally, if both are
+	     disk files construct relative file paths from the group(member)
+	     file to the member(group) file.
+
+	  */
+
+	  /* set the USELOCATION flag to true */
+
+	  useLocation = 1;
+
+	  /* 
+	     get the location, access type and iostate (RO, RW) of the
+	     member HDU file
+	  */
+
+	  *status = fits_get_url(tmpfptr,memberFileName,memberLocation,
+				 memberAccess1,memberAccess2,&memberIOstate,
+				 status);
+
+	  /*
+	     if the memberFileName string is empty then use the values of
+	     the memberLocation string. This corresponds to a file where
+	     the "real" file is a temporary memory file, and we must assume
+	     the the application really wants the original file to be the
+	     group member
+	   */
+
+	  if(strlen(memberFileName) == 0)
+	    {
+	      strcpy(memberFileName,memberLocation);
+	      strcpy(memberAccess1,memberAccess2);
+	    }
+
+	  /* 
+	     get the location, access type and iostate (RO, RW) of the
+	     grouping table file
+	  */
+
+	  *status = fits_get_url(gfptr,groupFileName,groupLocation,
+				 groupAccess1,groupAccess2,&groupIOstate,
+				 status);
+	  
+	  if(*status != 0) continue;
+
+	  /*
+	    the grouping table file must be writable to continue
+	  */
+
+	  if(groupIOstate == 0)
+	    {
+	      ffpmsg("cannot modify grouping table (ffgtam)");
+	      *status = BAD_GROUP_ATTACH;
+	      continue;
+	    }
+
+	  /*
+	    determine how to construct the resulting URLs for the member and
+	    group files
+	  */
+
+	  if(strcasecmp(groupAccess1,"file://")  &&
+	                                   strcasecmp(memberAccess1,"file://"))
+	    {
+              *cwd = 0;
+	      /* 
+		 nothing to do in this case; both the member and group files
+		 must be of an access type that already gives valid URLs;
+		 i.e., URLs that we can pass directly to the file drivers
+	      */
+	    }
+	  else
+	    {
+	      /*
+		 retrieve the Current Working Directory as a Unix-like
+		 URL standard string
+	      */
+
+	      *status = fits_get_cwd(cwd,status);
+
+	      /*
+		 create full file path for the member HDU FITS file URL
+		 if it is of access type file://
+	      */
+	      
+	      if(strcasecmp(memberAccess1,"file://") == 0)
+		{
+		  if(*memberFileName == '/')
+		    {
+		      strcpy(memberLocation,memberFileName);
+		    }
+		  else
+		    {
+		      strcpy(memberLocation,cwd);
+		      strcat(memberLocation,"/");
+		      strcat(memberLocation,memberFileName);
+		    }
+		  
+		  *status = fits_clean_url(memberLocation,memberFileName,
+					   status);
+		}
+
+	      /*
+		 create full file path for the grouping table HDU FITS file URL
+		 if it is of access type file://
+	      */
+
+	      if(strcasecmp(groupAccess1,"file://") == 0)
+		{
+		  if(*groupFileName == '/')
+		    {
+		      strcpy(groupLocation,groupFileName);
+		    }
+		  else
+		    {
+		      strcpy(groupLocation,cwd);
+		      strcat(groupLocation,"/");
+		      strcat(groupLocation,groupFileName);
+		    }
+		  
+		  *status = fits_clean_url(groupLocation,groupFileName,status);
+		}
+
+	      /*
+		if both the member and group files are disk files then 
+		create a relative path (relative URL) strings with 
+		respect to the grouping table's file and the grouping table's 
+		file with respect to the member HDU's file
+	      */
+	      
+	      if(strcasecmp(groupAccess1,"file://") == 0 &&
+		                      strcasecmp(memberAccess1,"file://") == 0)
+		{
+		  fits_url2relurl(memberFileName,groupFileName,
+				                  groupLocation,status);
+		  fits_url2relurl(groupFileName,memberFileName,
+				                  memberLocation,status);
+
+		  /*
+		     copy the resulting partial URL strings to the
+		     memberFileName and groupFileName variables for latter
+		     use in the function
+		   */
+		    
+		  strcpy(memberFileName,memberLocation);
+		  strcpy(groupFileName,groupLocation);		  
+		}
+	    }
+	  /* beo done */
+	  /* }  */
+      
+
+      /* retrieve the grouping table's EXTVER value */
+
+      *status = fits_read_key_lng(gfptr,"EXTVER",&groupExtver,card,status);
+
+      /* 
+	 if useLocation is true then make the group EXTVER value negative
+	 for the subsequent GRPIDn/GRPLCn matching
+      */
+      /* SPR 3463 change test;  WDP added test for same filename */
+      /* Now, if either the Fptr values are the same, or the root filenames
+         are the same, then assume these refer to the same file.
+      */
+      fits_parse_rootname(tmpfptr->Fptr->filename, tmprootname, status);
+      fits_parse_rootname(gfptr->Fptr->filename, grootname, status);
+
+      if((tmpfptr->Fptr != gfptr->Fptr) && 
+          strncmp(tmprootname, grootname, FLEN_FILENAME))
+	   groupExtver = -1*groupExtver;
+
+      /* retrieve the number of group members */
+
+      *status = fits_get_num_members(gfptr,&nmembers,status);
+	      
+    do {
+
+      /*
+	 make sure the member HDU is not already an entry in the
+	 grouping table before adding it
+      */
+
+      *status = ffgmf(gfptr,memberHDUtype,memberExtname,memberExtver,
+		      memberPosition,memberFileName,&memberID,status);
+
+      if(*status == MEMBER_NOT_FOUND) *status = 0;
+      else if(*status == 0)
+	{  
+	  parentStatus = HDU_ALREADY_MEMBER;
+    ffpmsg("Specified HDU is already a member of the Grouping table (ffgtam)");
+	  continue;
+	}
+      else continue;
+
+      /*
+	 if the member HDU is not already recorded in the grouping table
+	 then add it 
+      */
+
+      /* add a new row to the grouping table */
+
+      *status = fits_insert_rows(gfptr,nmembers,1,status);
+      ++nmembers;
+
+      /* retrieve the grouping table column IDs and structure type */
+
+      *status = ffgtgc(gfptr,&xtensionCol,&extnameCol,&extverCol,&positionCol,
+		       &locationCol,&uriCol,&grptype,status);
+
+      /* fill in the member HDU data in the new grouping table row */
+
+      *tmpPtr = memberHDUtype; 
+
+      if(xtensionCol != 0)
+	fits_write_col_str(gfptr,xtensionCol,nmembers,1,1,tmpPtr,status);
+
+      *tmpPtr = memberExtname; 
+
+      if(extnameCol  != 0)
+	{
+	  if(strlen(memberExtname) != 0)
+	    fits_write_col_str(gfptr,extnameCol,nmembers,1,1,tmpPtr,status);
+	  else
+	    /* WILL THIS WORK FOR VAR LENTH CHAR COLS??????*/
+	    fits_write_col_byt(gfptr,extnameCol,nmembers,1,1,charNull,status);
+	}
+
+      if(extverCol   != 0)
+	fits_write_col_lng(gfptr,extverCol,nmembers,1,1,&memberExtver,
+			   status);
+
+      if(positionCol != 0)
+	fits_write_col_int(gfptr,positionCol,nmembers,1,1,
+			   &memberPosition,status);
+
+      *tmpPtr = memberFileName; 
+
+      if(locationCol != 0)
+	{
+	  /* Change the test for SPR 3463 */
+	  /* Now, if either the Fptr values are the same, or the root filenames
+	     are the same, then assume these refer to the same file.
+	  */
+	  fits_parse_rootname(tmpfptr->Fptr->filename, tmprootname, status);
+	  fits_parse_rootname(gfptr->Fptr->filename, grootname, status);
+
+	  if((tmpfptr->Fptr != gfptr->Fptr) && 
+	          strncmp(tmprootname, grootname, FLEN_FILENAME))
+	    fits_write_col_str(gfptr,locationCol,nmembers,1,1,tmpPtr,status);
+	  else
+	    /* WILL THIS WORK FOR VAR LENTH CHAR COLS??????*/
+	    fits_write_col_byt(gfptr,locationCol,nmembers,1,1,charNull,status);
+	}
+
+      *tmpPtr = memberURI;
+
+      if(uriCol      != 0)
+	{
+
+	  /* Change the test for SPR 3463 */
+	  /* Now, if either the Fptr values are the same, or the root filenames
+	     are the same, then assume these refer to the same file.
+	  */
+	  fits_parse_rootname(tmpfptr->Fptr->filename, tmprootname, status);
+	  fits_parse_rootname(gfptr->Fptr->filename, grootname, status);
+
+	  if((tmpfptr->Fptr != gfptr->Fptr) && 
+	          strncmp(tmprootname, grootname, FLEN_FILENAME))
+	    fits_write_col_str(gfptr,uriCol,nmembers,1,1,tmpPtr,status);
+	  else
+	    /* WILL THIS WORK FOR VAR LENTH CHAR COLS??????*/
+	    fits_write_col_byt(gfptr,uriCol,nmembers,1,1,charNull,status);
+	}
+    } while(0);
+
+      if(0 != *status) continue;
+      /*
+	 add GRPIDn/GRPLCn keywords to the member HDU header to link
+	 it to the grouing table if the they do not already exist and
+	 the member file is RW
+      */
+
+      fits_file_mode(tmpfptr,&iomode,status);
+ 
+     if(memberIOstate == 0 || iomode != READWRITE) 
+	{
+	  ffpmsg("cannot add GRPID/LC keywords to member HDU: (ffgtam)");
+	  ffpmsg(memberFileName);
+	  continue;
+	}
+
+      *status = fits_get_num_groups(tmpfptr,&ngroups,status);
+
+      /* 
+	 look for the GRPID/LC keywords in the member HDU; if the keywords
+	 for the back-link to the grouping table already exist then no
+	 need to add them again
+       */
+
+      for(i = 1, found = 0; i <= ngroups && !found && *status == 0; ++i)
+	{
+	  sprintf(keyword,"GRPID%d",(int)ngroups);
+	  *status = fits_read_key_lng(tmpfptr,keyword,&grpid,card,status);
+
+	  if(grpid == groupExtver)
+	    {
+	      if(grpid < 0)
+		{
+
+		  /* have to make sure the GRPLCn keyword matches too */
+
+		  sprintf(keyword,"GRPLC%d",(int)ngroups);
+		  /* SPR 1738 */
+		  *status = fits_read_key_longstr(mfptr,keyword,&tgrplc,card,
+						  status);
+		  if (0 == *status) {
+		    strcpy(grplc,tgrplc);
+		    free(tgrplc);
+		  }
+		  
+		  /*
+		     always compare files using absolute paths
+                     the presence of a non-empty cwd indicates
+                     that the file names may require conversion
+                     to absolute paths
+                  */
+
+                  if(0 < strlen(cwd)) {
+                    /* temp buffer for use in assembling abs. path(s) */
+                    char tmp[FLEN_FILENAME];
+
+                    /* make grplc absolute if necessary */
+                    if(!fits_is_url_absolute(grplc)) {
+		      fits_path2url(grplc,groupLocation,status);
+
+		      if(groupLocation[0] != '/')
+			{
+			  strcpy(tmp, cwd);
+			  strcat(tmp,"/");
+			  strcat(tmp,groupLocation);
+			  fits_clean_url(tmp,grplc,status);
+			}
+                    }
+
+                    /* make groupFileName absolute if necessary */
+                    if(!fits_is_url_absolute(groupFileName)) {
+		      fits_path2url(groupFileName,groupLocation,status);
+
+		      if(groupLocation[0] != '/')
+			{
+			  strcpy(tmp, cwd);
+			  strcat(tmp,"/");
+			  strcat(tmp,groupLocation);
+                          /*
+                             note: use groupLocation (which is not used
+                             below this block), to store the absolute
+                             file name instead of using groupFileName.
+                             The latter may be needed unaltered if the
+                             GRPLC is written below
+                          */
+
+			  fits_clean_url(tmp,groupLocation,status);
+			}
+                    }
+                  }
+		  /*
+		    see if the grplc value and the group file name match
+		  */
+
+		  if(strcmp(grplc,groupLocation) == 0) found = 1;
+		}
+	      else
+		{
+		  /* the match is found with GRPIDn alone */
+		  found = 1;
+		}
+	    }
+	}
+
+      /*
+	 if FOUND is true then no need to continue
+      */
+
+      if(found)
+	{
+	  ffpmsg("HDU already has GRPID/LC keywords for group table (ffgtam)");
+	  continue;
+	}
+
+      /*
+	 add the GRPID/LC keywords to the member header for this grouping
+	 table
+	 
+	 If NGROUPS == 0 then we must position the header pointer to the
+	 record where we want to insert the GRPID/LC keywords (the pointer
+	 is already correctly positioned if the above search loop activiated)
+      */
+
+      if(ngroups == 0)
+	{
+	  /* 
+	     no GRPIDn/GRPLCn keywords currently exist in header so try
+	     to position the header pointer to a desirable position
+	  */
+	  
+	  for(i = 0, *status = KEY_NO_EXIST; 
+	                       i < nkeys && *status == KEY_NO_EXIST; ++i)
+	    {
+	      *status = 0;
+	      *status = fits_read_card(tmpfptr,keys[i],card,status);
+	    }
+	      
+	  /* all else fails: move write pointer to end of header */
+	      
+	  if(*status == KEY_NO_EXIST)
+	    {
+	      *status = 0;
+	      fits_get_hdrspace(tmpfptr,&nkeys,&i,status);
+	      ffgrec(tmpfptr,nkeys,card,status);
+	    }
+	  
+	  /* any other error status then abort */
+	  
+	  if(*status != 0) continue;
+	}
+      
+      /* 
+	 now that the header pointer is positioned for the GRPID/LC 
+	 keyword insertion increment the number of group links counter for 
+	 the member HDU 
+      */
+
+      ++ngroups;
+
+      /*
+	 if the member HDU and grouping table reside in the same FITS file
+	 then there is no need to add a GRPLCn keyword
+      */
+      /* SPR 3463 change test */
+      /* Now, if either the Fptr values are the same, or the root filenames
+	 are the same, then assume these refer to the same file.
+      */
+      fits_parse_rootname(tmpfptr->Fptr->filename, tmprootname, status);
+      fits_parse_rootname(gfptr->Fptr->filename, grootname, status);
+
+      if((tmpfptr->Fptr == gfptr->Fptr) || 
+	          strncmp(tmprootname, grootname, FLEN_FILENAME) == 0)
+	{
+	  /* add the GRPIDn keyword only */
+
+	  sprintf(keyword,"GRPID%d",(int)ngroups);
+	  fits_insert_key_lng(tmpfptr,keyword,groupExtver,
+			      "EXTVER of Group containing this HDU",status);
+	}
+      else 
+	{
+	  /* add the GRPIDn and GRPLCn keywords */
+
+	  sprintf(keyword,"GRPID%d",(int)ngroups);
+	  fits_insert_key_lng(tmpfptr,keyword,groupExtver,
+			      "EXTVER of Group containing this HDU",status);
+
+	  sprintf(keyword,"GRPLC%d",(int)ngroups);
+	  /* SPR 1738 */
+	  fits_insert_key_longstr(tmpfptr,keyword,groupFileName,
+			      "URL of file containing Group",status);
+	  fits_write_key_longwarn(tmpfptr,status);
+
+	}
+
+    }while(0);
+
+  /* close the tmpfptr pointer if it was opened in this function */
+
+  if(mfptr == NULL)
+    {
+      *status = fits_close_file(tmpfptr,status);
+    }
+
+  *status = 0 == *status ? parentStatus : *status;
+
+  return(*status);
+}
+
+/*---------------------------------------------------------------------------*/
+int ffgtnm(fitsfile *gfptr,    /* FITS file pointer to grouping table        */
+	   long     *nmembers, /* member count  of the groping table         */
+	   int      *status)   /* return status code                         */
+
+/*
+  return the number of member HDUs in a grouping table. The fitsfile pointer
+  gfptr must be positioned with the grouping table as the CHDU. The number
+  of grouping table member HDUs is just the NAXIS2 value of the grouping
+  table.
+*/
+
+{
+  char keyvalue[FLEN_VALUE];
+  char comment[FLEN_COMMENT];
+  
+
+  if(*status != 0) return(*status);
+
+  *status = fits_read_keyword(gfptr,"EXTNAME",keyvalue,comment,status);
+  
+  if(*status == KEY_NO_EXIST)
+    *status = NOT_GROUP_TABLE;
+  else
+    {
+      prepare_keyvalue(keyvalue);
+
+      if(strcasecmp(keyvalue,"GROUPING") != 0)
+	{
+	  *status = NOT_GROUP_TABLE;
+	  ffpmsg("Specified HDU is not a Grouping table (ffgtnm)");
+	}
+
+      *status = fits_read_key_lng(gfptr,"NAXIS2",nmembers,comment,status);
+    }
+
+  return(*status);
+}
+
+/*--------------------------------------------------------------------------*/
+int ffgmng(fitsfile *mfptr,   /* FITS file pointer to member HDU            */
+	   long     *ngroups, /* total number of groups linked to HDU       */
+	   int      *status)  /* return status code                         */
+
+/*
+  return the number of groups to which a HDU belongs, as defined by the number
+  of GRPIDn/GRPLCn keyword records that appear in the HDU header. The 
+  fitsfile pointer mfptr must be positioned with the member HDU as the CHDU. 
+  Each time this function is called, the indicies of the GRPIDn/GRPLCn
+  keywords are checked to make sure they are continuous (ie no gaps) and
+  are re-enumerated to eliminate gaps if gaps are found to be present.
+*/
+
+{
+  int offset;
+  int index;
+  int newIndex;
+  int i;
+  
+  long grpid;
+
+  char *inclist[] = {"GRPID#"};
+  char keyword[FLEN_KEYWORD];
+  char newKeyword[FLEN_KEYWORD];
+  char card[FLEN_CARD];
+  char comment[FLEN_COMMENT];
+  char *tkeyvalue;
+
+  if(*status != 0) return(*status);
+
+  *ngroups = 0;
+
+  /* reset the member HDU keyword counter to the beginning */
+
+  *status = ffgrec(mfptr,0,card,status);
+  
+  /*
+    search for the number of GRPIDn keywords in the member HDU header
+    and count them with the ngroups variable
+  */
+  
+  while(*status == 0)
+    {
+      /* read the next GRPIDn keyword in the series */
+
+      *status = fits_find_nextkey(mfptr,inclist,1,NULL,0,card,status);
+      
+      if(*status != 0) continue;
+      
+      ++(*ngroups);
+    }
+
+  if(*status == KEY_NO_EXIST) *status = 0;
+      
+  /*
+     read each GRPIDn/GRPLCn keyword and adjust their index values so that
+     there are no gaps in the index count
+  */
+
+  for(index = 1, offset = 0, i = 1; i <= *ngroups && *status == 0; ++index)
+    {	  
+      sprintf(keyword,"GRPID%d",index);
+
+      /* try to read the next GRPIDn keyword in the series */
+
+      *status = fits_read_key_lng(mfptr,keyword,&grpid,card,status);
+
+      /* if not found then increment the offset counter and continue */
+
+      if(*status == KEY_NO_EXIST) 
+	{
+	  *status = 0;
+	  ++offset;
+	}
+      else
+	{
+	  /* 
+	     increment the number_keys_found counter and see if the index
+	     of the keyword needs to be updated
+	  */
+
+	  ++i;
+
+	  if(offset > 0)
+	    {
+	      /* compute the new index for the GRPIDn/GRPLCn keywords */
+	      newIndex = index - offset;
+
+	      /* update the GRPIDn keyword index */
+
+	      sprintf(newKeyword,"GRPID%d",newIndex);
+	      fits_modify_name(mfptr,keyword,newKeyword,status);
+
+	      /* If present, update the GRPLCn keyword index */
+
+	      sprintf(keyword,"GRPLC%d",index);
+	      sprintf(newKeyword,"GRPLC%d",newIndex);
+	      /* SPR 1738 */
+	      *status = fits_read_key_longstr(mfptr,keyword,&tkeyvalue,comment,
+					      status);
+	      if (0 == *status) {
+		fits_delete_key(mfptr,keyword,status);
+		fits_insert_key_longstr(mfptr,newKeyword,tkeyvalue,comment,status);
+		fits_write_key_longwarn(mfptr,status);
+		free(tkeyvalue);
+	      }
+	      
+
+	      if(*status == KEY_NO_EXIST) *status = 0;
+	    }
+	}
+    }
+
+  return(*status);
+}
+
+/*---------------------------------------------------------------------------*/
+int ffgmop(fitsfile *gfptr,  /* FITS file pointer to grouping table          */
+	   long      member, /* member ID (row num) within grouping table    */
+	   fitsfile **mfptr, /* FITS file pointer to member HDU              */
+	   int      *status) /* return status code                           */
+
+/*
+  open a grouping table member, returning a pointer to the member's FITS file
+  with the CHDU set to the member HDU. The grouping table must be the CHDU of
+  the FITS file pointed to by gfptr. The member to open is identified by its
+  row number within the grouping table (first row/member == 1).
+
+  If the member resides in a FITS file different from the grouping
+  table the member file is first opened readwrite and if this fails then
+  it is opened readonly. For access type of FILE:// the member file is
+  searched for assuming (1) an absolute path is given, (2) a path relative
+  to the CWD is given, and (3) a path relative to the grouping table file
+  but not relative to the CWD is given. If all of these fail then the
+  error FILE_NOT_FOUND is returned.
+*/
+
+{
+  int xtensionCol,extnameCol,extverCol,positionCol,locationCol,uriCol;
+  int grptype,hdutype;
+  int dummy;
+
+  long hdupos = 0;
+  long extver = 0;
+
+  char  xtension[FLEN_VALUE];
+  char  extname[FLEN_VALUE];
+  char  uri[FLEN_VALUE];
+  char  grpLocation1[FLEN_FILENAME];
+  char  grpLocation2[FLEN_FILENAME];
+  char  mbrLocation1[FLEN_FILENAME];
+  char  mbrLocation2[FLEN_FILENAME];
+  char  mbrLocation3[FLEN_FILENAME];
+  char  cwd[FLEN_FILENAME];
+  char  card[FLEN_CARD];
+  char  nstr[] = {'\0'};
+  char *tmpPtr[1];
+
+
+  if(*status != 0) return(*status);
+
+  do
+    {
+      /*
+	retrieve the Grouping Convention reserved column positions within
+	the grouping table
+      */
+
+      *status = ffgtgc(gfptr,&xtensionCol,&extnameCol,&extverCol,&positionCol,
+		       &locationCol,&uriCol,&grptype,status);
+
+      if(*status != 0) continue;
+
+      /*
+	 extract the member information from grouping table
+      */
+
+      tmpPtr[0] = xtension;
+
+      if(xtensionCol != 0)
+	{
+
+	  *status = fits_read_col_str(gfptr,xtensionCol,member,1,1,nstr,
+				      tmpPtr,&dummy,status);
+
+	  /* convert the xtension string to a hdutype code */
+
+	  if(strcasecmp(xtension,"PRIMARY")       == 0) hdutype = IMAGE_HDU; 
+	  else if(strcasecmp(xtension,"IMAGE")    == 0) hdutype = IMAGE_HDU; 
+	  else if(strcasecmp(xtension,"TABLE")    == 0) hdutype = ASCII_TBL; 
+	  else if(strcasecmp(xtension,"BINTABLE") == 0) hdutype = BINARY_TBL; 
+	  else hdutype = ANY_HDU; 
+	}
+
+      tmpPtr[0] = extname;
+
+      if(extnameCol  != 0)
+	  *status = fits_read_col_str(gfptr,extnameCol,member,1,1,nstr,
+				      tmpPtr,&dummy,status);
+
+      if(extverCol   != 0)
+	  *status = fits_read_col_lng(gfptr,extverCol,member,1,1,0,
+				      (long*)&extver,&dummy,status);
+
+      if(positionCol != 0)
+	  *status = fits_read_col_lng(gfptr,positionCol,member,1,1,0,
+				      (long*)&hdupos,&dummy,status);
+
+      tmpPtr[0] = mbrLocation1;
+
+      if(locationCol != 0)
+	*status = fits_read_col_str(gfptr,locationCol,member,1,1,nstr,
+				    tmpPtr,&dummy,status);
+      tmpPtr[0] = uri;
+
+      if(uriCol != 0)
+	*status = fits_read_col_str(gfptr,uriCol,member,1,1,nstr,
+				    tmpPtr,&dummy,status);
+
+      if(*status != 0) continue;
+
+      /* 
+	 decide what FITS file the member HDU resides in and open the file
+	 using the fitsfile* pointer mfptr; note that this logic is rather
+	 complicated and is based primiarly upon if a URL specifier is given
+	 for the member file in the grouping table
+      */
+
+      switch(grptype)
+	{
+
+	case GT_ID_POS:
+	case GT_ID_REF:
+	case GT_ID_ALL:
+
+	  /*
+	     no location information is given so we must assume that the
+	     member HDU resides in the same FITS file as the grouping table;
+	     if the grouping table was incorrectly constructed then this
+	     assumption will be false, but there is nothing to be done about
+	     it at this point
+	  */
+
+	  *status = fits_reopen_file(gfptr,mfptr,status);
+	  
+	  break;
+
+	case GT_ID_REF_URI:
+	case GT_ID_POS_URI:
+	case GT_ID_ALL_URI:
+
+	  /*
+	    The member location column exists. Determine if the member 
+	    resides in the same file as the grouping table or in a
+	    separate file; open the member file in either case
+	  */
+
+	  if(strlen(mbrLocation1) == 0)
+	    {
+	      /*
+		 since no location information was given we must assume
+		 that the member is in the same FITS file as the grouping
+		 table
+	      */
+
+	      *status = fits_reopen_file(gfptr,mfptr,status);
+	    }
+	  else
+	    {
+	      /*
+		make sure the location specifiation is "URL"; we cannot
+		decode any other URI types at this time
+	      */
+
+	      if(strcasecmp(uri,"URL") != 0)
+		{
+		  *status = FILE_NOT_OPENED;
+		  sprintf(card,
+		  "Cannot open member HDU file with URI type %s (ffgmop)",
+			  uri);
+		  ffpmsg(card);
+
+		  continue;
+		}
+
+	      /*
+		The location string for the member is not NULL, so it 
+		does not necessially reside in the same FITS file as the
+		grouping table. 
+
+		Three cases are attempted for opening the member's file
+		in the following order:
+
+		1. The URL given for the member's file is absolute (i.e.,
+		access method supplied); try to open the member
+
+		2. The URL given for the member's file is not absolute but
+		is an absolute file path; try to open the member as a file
+		after the file path is converted to a host-dependent form
+
+		3. The URL given for the member's file is not absolute
+	        and is given as a relative path to the location of the 
+		grouping table's file. Create an absolute URL using the 
+		grouping table's file URL and try to open the member.
+		
+		If all three cases fail then an error is returned. In each
+		case the file is first opened in read/write mode and failing
+		that readonly mode.
+		
+		The following DO loop is only used as a mechanism to break
+		(continue) when the proper file opening method is found
+	       */
+
+	      do
+		{
+		  /*
+		     CASE 1:
+
+		     See if the member URL is absolute (i.e., includes a
+		     access directive) and if so open the file
+		   */
+
+		  if(fits_is_url_absolute(mbrLocation1))
+		    {
+		      /*
+			 the URL must specify an access method, which 
+			 implies that its an absolute reference
+			 
+			 regardless of the access method, pass the whole
+			 URL to the open function for processing
+		       */
+		      
+		      ffpmsg("member URL is absolute, try open R/W (ffgmop)");
+
+		      *status = fits_open_file(mfptr,mbrLocation1,READWRITE,
+					       status);
+
+		      if(*status == 0) continue;
+
+		      *status = 0;
+
+		      /* 
+			 now try to open file using full URL specs in 
+			 readonly mode 
+		      */ 
+
+		      ffpmsg("OK, now try to open read-only (ffgmop)");
+
+		      *status = fits_open_file(mfptr,mbrLocation1,READONLY,
+					       status);
+
+		      /* break from DO loop regardless of status */
+
+		      continue;
+		    }
+
+		  /*
+		     CASE 2:
+
+		     If we got this far then the member URL location 
+		     has no access type ==> FILE:// Try to open the member 
+		     file using the URL as is, i.e., assume that it is given 
+		     as absolute, if it starts with a '/' character
+		   */
+
+		  ffpmsg("Member URL is of type FILE (ffgmop)");
+
+		  if(*mbrLocation1 == '/')
+		    {
+		      ffpmsg("Member URL specifies abs file path (ffgmop)");
+
+		      /* 
+			 convert the URL path to a host dependent path
+		      */
+
+		      *status = fits_url2path(mbrLocation1,mbrLocation2,
+					      status);
+
+		      ffpmsg("Try to open member URL in R/W mode (ffgmop)");
+
+		      *status = fits_open_file(mfptr,mbrLocation2,READWRITE,
+					       status);
+
+		      if(*status == 0) continue;
+
+		      *status = 0;
+
+		      /* 
+			 now try to open file using the URL as an absolute 
+			 path in readonly mode 
+		      */
+ 
+		      ffpmsg("OK, now try to open read-only (ffgmop)");
+
+		      *status = fits_open_file(mfptr,mbrLocation2,READONLY,
+					       status);
+
+		      /* break from the Do loop regardless of the status */
+
+		      continue;
+		    }
+		  
+		  /* 
+		     CASE 3:
+
+		     If we got this far then the URL does not specify an
+		     absoulte file path or URL with access method. Since 
+		     the path to the group table's file is (obviously) valid 
+		     for the CWD, create a full location string for the
+		     member HDU using the grouping table URL as a basis
+
+		     The only problem is that the grouping table file might
+		     have two URLs, the original one used to open it and
+		     the one that points to the real file being accessed
+		     (i.e., a file accessed via HTTP but transferred to a
+		     local disk file). Have to attempt to build a URL to
+		     the member HDU file using both of these URLs if
+		     defined.
+		  */
+
+		  ffpmsg("Try to open member file as relative URL (ffgmop)");
+
+		  /* get the URL information for the grouping table file */
+
+		  *status = fits_get_url(gfptr,grpLocation1,grpLocation2,
+					 NULL,NULL,NULL,status);
+
+		  /* 
+		     if the "real" grouping table file URL is defined then
+		     build a full url for the member HDU file using it
+		     and try to open the member HDU file
+		  */
+
+		  if(*grpLocation1)
+		    {
+		      /* make sure the group location is absolute */
+
+		      if(! fits_is_url_absolute(grpLocation1) &&
+			                              *grpLocation1 != '/')
+			{
+			  fits_get_cwd(cwd,status);
+			  strcat(cwd,"/");
+			  strcat(cwd,grpLocation1);
+			  strcpy(grpLocation1,cwd);
+			}
+
+		      /* create a full URL for the member HDU file */
+
+		      *status = fits_relurl2url(grpLocation1,mbrLocation1,
+						mbrLocation2,status);
+
+		      if(*status != 0) continue;
+
+		      /*
+			if the URL does not have an access method given then
+			translate it into a host dependent file path
+		      */
+
+		      if(! fits_is_url_absolute(mbrLocation2))
+			{
+			  *status = fits_url2path(mbrLocation2,mbrLocation3,
+						  status);
+			  strcpy(mbrLocation2,mbrLocation3);
+			}
+
+		      /* try to open the member file READWRITE */
+
+		      *status = fits_open_file(mfptr,mbrLocation2,READWRITE,
+					       status);
+
+		      if(*status == 0) continue;
+
+		      *status = 0;
+		  
+		      /* now try to open in readonly mode */ 
+
+		      ffpmsg("now try to open file as READONLY (ffgmop)");
+
+		      *status = fits_open_file(mfptr,mbrLocation2,READONLY,
+					       status);
+
+		      if(*status == 0) continue;
+
+		      *status = 0;
+		    }
+
+		  /* 
+		     if we got this far then either the "real" grouping table
+		     file URL was not defined or all attempts to open the
+		     resulting member HDU file URL failed.
+
+		     if the "original" grouping table file URL is defined then
+		     build a full url for the member HDU file using it
+		     and try to open the member HDU file
+		  */
+
+		  if(*grpLocation2)
+		    {
+		      /* make sure the group location is absolute */
+
+		      if(! fits_is_url_absolute(grpLocation2) &&
+			                              *grpLocation2 != '/')
+			{
+			  fits_get_cwd(cwd,status);
+			  strcat(cwd,"/");
+			  strcat(cwd,grpLocation2);
+			  strcpy(grpLocation2,cwd);
+			}
+
+		      /* create an absolute URL for the member HDU file */
+
+		      *status = fits_relurl2url(grpLocation2,mbrLocation1,
+						mbrLocation2,status);
+		      if(*status != 0) continue;
+
+		      /*
+			if the URL does not have an access method given then
+			translate it into a host dependent file path
+		      */
+
+		      if(! fits_is_url_absolute(mbrLocation2))
+			{
+			  *status = fits_url2path(mbrLocation2,mbrLocation3,
+						  status);
+			  strcpy(mbrLocation2,mbrLocation3);
+			}
+
+		      /* try to open the member file READWRITE */
+
+		      *status = fits_open_file(mfptr,mbrLocation2,READWRITE,
+					       status);
+
+		      if(*status == 0) continue;
+
+		      *status = 0;
+		  
+		      /* now try to open in readonly mode */ 
+
+		      ffpmsg("now try to open file as READONLY (ffgmop)");
+
+		      *status = fits_open_file(mfptr,mbrLocation2,READONLY,
+					       status);
+
+		      if(*status == 0) continue;
+
+		      *status = 0;
+		    }
+
+		  /*
+		     if we got this far then the member HDU file could not
+		     be opened using any method. Log the error.
+		  */
+
+		  ffpmsg("Cannot open member HDU FITS file (ffgmop)");
+		  *status = MEMBER_NOT_FOUND;
+		  
+		}while(0);
+	    }
+
+	  break;
+
+	default:
+
+	  /* no default action */
+	  
+	  break;
+	}
+	  
+      if(*status != 0) continue;
+
+      /*
+	 attempt to locate the member HDU within its FITS file as determined
+	 and opened above
+      */
+
+      switch(grptype)
+	{
+
+	case GT_ID_POS:
+	case GT_ID_POS_URI:
+
+	  /*
+	    try to find the member hdu in the the FITS file pointed to
+	    by mfptr based upon its HDU posistion value. Note that is 
+	    impossible to verify if the HDU is actually the correct HDU due 
+	    to a lack of information.
+	  */
+	  
+	  *status = fits_movabs_hdu(*mfptr,(int)hdupos,&hdutype,status);
+
+	  break;
+
+	case GT_ID_REF:
+	case GT_ID_REF_URI:
+
+	  /*
+	     try to find the member hdu in the FITS file pointed to
+	     by mfptr based upon its XTENSION, EXTNAME and EXTVER keyword 
+	     values
+	  */
+
+	  *status = fits_movnam_hdu(*mfptr,hdutype,extname,extver,status);
+
+	  if(*status == BAD_HDU_NUM) 
+	    {
+	      *status = MEMBER_NOT_FOUND;
+	      ffpmsg("Cannot find specified member HDU (ffgmop)");
+	    }
+
+	  /*
+	     if the above function returned without error then the
+	     mfptr is pointed to the member HDU
+	  */
+
+	  break;
+
+	case GT_ID_ALL:
+	case GT_ID_ALL_URI:
+
+	  /*
+	     if the member entry has reference information then use it
+             (ID by reference is safer than ID by position) else use
+	     the position information
+	  */
+
+	  if(strlen(xtension) > 0 && strlen(extname) > 0 && extver > 0)
+	    {
+	      /* valid reference info exists so use it */
+	      
+	      /* try to find the member hdu in the grouping table's file */
+
+	      *status = fits_movnam_hdu(*mfptr,hdutype,extname,extver,status);
+
+	      if(*status == BAD_HDU_NUM) 
+		{
+		  *status = MEMBER_NOT_FOUND;
+		  ffpmsg("Cannot find specified member HDU (ffgmop)");
+		}
+	    }
+	  else
+	      {
+		  *status = fits_movabs_hdu(*mfptr,(int)hdupos,&hdutype,
+					    status);
+		  if(*status == END_OF_FILE) *status = MEMBER_NOT_FOUND;
+	      }
+
+	  /*
+	     if the above function returned without error then the
+	     mfptr is pointed to the member HDU
+	  */
+
+	  break;
+
+	default:
+
+	  /* no default action */
+
+	  break;
+	}
+      
+    }while(0);
+
+  if(*status != 0 && *mfptr != NULL) 
+    {
+      fits_close_file(*mfptr,status);
+    }
+
+  return(*status);
+}
+
+/*---------------------------------------------------------------------------*/
+int ffgmcp(fitsfile *gfptr,  /* FITS file pointer to group                   */
+	   fitsfile *mfptr,  /* FITS file pointer to new member
+				FITS file                                    */
+	   long      member, /* member ID (row num) within grouping table    */
+	   int       cpopt,  /* code specifying copy options:
+				OPT_MCP_ADD  (0) ==> add copied member to the
+ 				                     grouping table
+				OPT_MCP_NADD (1) ==> do not add member copy to
+				                     the grouping table
+				OPT_MCP_REPL (2) ==> replace current member
+				                     entry with member copy  */
+	   int      *status) /* return status code                           */
+	   
+/*
+  copy a member HDU of a grouping table to a new FITS file. The grouping table
+  must be the CHDU of the FITS file pointed to by gfptr. The copy of the
+  group member shall be appended to the end of the FITS file pointed to by
+  mfptr. If the cpopt parameter is set to OPT_MCP_ADD then the copy of the 
+  member is added to the grouping table as a new member, if OPT_MCP_NADD 
+  then the copied member is not added to the grouping table, and if 
+  OPT_MCP_REPL then the copied member is used to replace the original member.
+  The copied member HDU also has its EXTVER value updated so that its
+  combination of XTENSION, EXTNAME and EXVTER is unique within its new
+  FITS file.
+*/
+
+{
+  int numkeys = 0;
+  int keypos  = 0;
+  int hdunum  = 0;
+  int hdutype = 0;
+  int i;
+  
+  char *incList[] = {"GRPID#","GRPLC#"};
+  char  extname[FLEN_VALUE];
+  char  card[FLEN_CARD];
+  char  comment[FLEN_COMMENT];
+  char  keyname[FLEN_CARD];
+  char  value[FLEN_CARD];
+
+  fitsfile *tmpfptr = NULL;
+
+
+  if(*status != 0) return(*status);
+
+  do
+    {
+      /* open the member HDU to be copied */
+
+      *status = fits_open_member(gfptr,member,&tmpfptr,status);
+
+      if(*status != 0) continue;
+
+      /*
+	if the member is a grouping table then copy it with a call to
+	fits_copy_group() using the "copy only the grouping table" option
+
+	if it is not a grouping table then copy the hdu with fits_copy_hdu()
+	remove all GRPIDn and GRPLCn keywords, and update the EXTVER keyword
+	value
+      */
+
+      /* get the member HDU's EXTNAME value */
+
+      *status = fits_read_key_str(tmpfptr,"EXTNAME",extname,comment,status);
+
+      /* if no EXTNAME value was found then set the extname to a null string */
+
+      if(*status == KEY_NO_EXIST) 
+	{
+	  extname[0] = 0;
+	  *status    = 0;
+	}
+      else if(*status != 0) continue;
+
+      prepare_keyvalue(extname);
+
+      /* if a grouping table then copy with fits_copy_group() */
+
+      if(strcasecmp(extname,"GROUPING") == 0)
+	*status = fits_copy_group(tmpfptr,mfptr,OPT_GCP_GPT,status);
+      else
+	{
+	  /* copy the non-grouping table HDU the conventional way */
+
+	  *status = fits_copy_hdu(tmpfptr,mfptr,0,status);
+
+	  ffgrec(mfptr,0,card,status);
+
+	  /* delete all the GRPIDn and GRPLCn keywords in the copied HDU */
+
+	  while(*status == 0)
+	    {
+	      *status = fits_find_nextkey(mfptr,incList,2,NULL,0,card,status);
+	      *status = fits_get_hdrpos(mfptr,&numkeys,&keypos,status);  
+	      /* SPR 1738 */
+	      *status = fits_read_keyn(mfptr,keypos-1,keyname,value,
+				       comment,status);
+	      *status = fits_read_record(mfptr,keypos-1,card,status);
+	      *status = fits_delete_key(mfptr,keyname,status);
+	    }
+
+	  if(*status == KEY_NO_EXIST) *status = 0;
+	  if(*status != 0) continue;
+	}
+
+      /* 
+	 if the member HDU does not have an EXTNAME keyword then add one
+	 with a default value
+      */
+
+      if(strlen(extname) == 0)
+	{
+	  if(fits_get_hdu_num(tmpfptr,&hdunum) == 1)
+	    {
+	      strcpy(extname,"PRIMARY");
+	      *status = fits_write_key_str(mfptr,"EXTNAME",extname,
+					   "HDU was Formerly a Primary Array",
+					   status);
+	    }
+	  else
+	    {
+	      strcpy(extname,"DEFAULT");
+	      *status = fits_write_key_str(mfptr,"EXTNAME",extname,
+					   "default EXTNAME set by CFITSIO",
+					   status);
+	    }
+	}
+
+      /* 
+	 update the member HDU's EXTVER value (add it if not present)
+      */
+
+      fits_get_hdu_num(mfptr,&hdunum);
+      fits_get_hdu_type(mfptr,&hdutype,status);
+
+      /* set the EXTVER value to 0 for now */
+
+      *status = fits_modify_key_lng(mfptr,"EXTVER",0,NULL,status);
+
+      /* if the EXTVER keyword was not found then add it */
+
+      if(*status == KEY_NO_EXIST)
+	{
+	  *status = 0;
+	  *status = fits_read_key_str(mfptr,"EXTNAME",extname,comment,
+				      status);
+	  *status = fits_insert_key_lng(mfptr,"EXTVER",0,
+					"Extension version ID",status);
+	}
+
+      if(*status != 0) continue;
+
+      /* find the first available EXTVER value for the copied HDU */
+ 
+      for(i = 1; fits_movnam_hdu(mfptr,hdutype,extname,i,status) == 0; ++i);
+
+      *status = 0;
+
+      fits_movabs_hdu(mfptr,hdunum,&hdutype,status);
+
+      /* reset the copied member HDUs EXTVER value */
+
+      *status = fits_modify_key_lng(mfptr,"EXTVER",(long)i,NULL,status);    
+
+      /*
+	perform member copy operations that are dependent upon the cpopt
+	parameter value
+      */
+
+      switch(cpopt)
+	{
+	case OPT_MCP_ADD:
+
+	  /*
+	    add the copied member to the grouping table, leaving the
+	    entry for the original member in place
+	  */
+
+	  *status = fits_add_group_member(gfptr,mfptr,0,status);
+
+	  break;
+
+	case OPT_MCP_NADD:
+
+	  /*
+	    nothing to do for this copy option
+	  */
+
+	  break;
+
+	case OPT_MCP_REPL:
+
+	  /*
+	    remove the original member from the grouping table and add the
+	    copied member in its place
+	  */
+
+	  *status = fits_remove_member(gfptr,member,OPT_RM_ENTRY,status);
+	  *status = fits_add_group_member(gfptr,mfptr,0,status);
+
+	  break;
+
+	default:
+
+	  *status = BAD_OPTION;
+	  ffpmsg("Invalid value specified for the cmopt parameter (ffgmcp)");
+
+	  break;
+	}
+
+    }while(0);
+      
+  if(tmpfptr != NULL) 
+    {
+      fits_close_file(tmpfptr,status);
+    }
+
+  return(*status);
+}		     
+
+/*---------------------------------------------------------------------------*/
+int ffgmtf(fitsfile *infptr,   /* FITS file pointer to source grouping table */
+	   fitsfile *outfptr,  /* FITS file pointer to target grouping table */
+	   long      member,   /* member ID within source grouping table     */
+	   int       tfopt,    /* code specifying transfer opts:
+				  OPT_MCP_ADD (0) ==> copy member to dest.
+				  OPT_MCP_MOV (3) ==> move member to dest.   */
+	   int      *status)   /* return status code                         */
+
+/*
+  transfer a group member from one grouping table to another. The source
+  grouping table must be the CHDU of the fitsfile pointed to by infptr, and 
+  the destination grouping table must be the CHDU of the fitsfile to by 
+  outfptr. If the tfopt parameter is OPT_MCP_ADD then the member is made a 
+  member of the target group and remains a member of the source group. If
+  the tfopt parameter is OPT_MCP_MOV then the member is deleted from the 
+  source group after the transfer to the destination group. The member to be
+  transfered is identified by its row number within the source grouping table.
+*/
+
+{
+  fitsfile *mfptr = NULL;
+
+
+  if(*status != 0) return(*status);
+
+  if(tfopt != OPT_MCP_MOV && tfopt != OPT_MCP_ADD)
+    {
+      *status = BAD_OPTION;
+      ffpmsg("Invalid value specified for the tfopt parameter (ffgmtf)");
+    }
+  else
+    {
+      /* open the member of infptr to be transfered */
+
+      *status = fits_open_member(infptr,member,&mfptr,status);
+      
+      /* add the member to the outfptr grouping table */
+      
+      *status = fits_add_group_member(outfptr,mfptr,0,status);
+      
+      /* close the member HDU */
+      
+      *status = fits_close_file(mfptr,status);
+      
+      /* 
+	 if the tfopt is "move member" then remove it from the infptr 
+	 grouping table
+      */
+
+      if(tfopt == OPT_MCP_MOV)
+	*status = fits_remove_member(infptr,member,OPT_RM_ENTRY,status);
+    }
+  
+  return(*status);
+}
+
+/*---------------------------------------------------------------------------*/
+int ffgmrm(fitsfile *gfptr,  /* FITS file pointer to group table             */
+	   long      member, /* member ID (row num) in the group             */
+	   int       rmopt,  /* code specifying the delete option:
+				OPT_RM_ENTRY ==> delete the member entry
+				OPT_RM_MBR   ==> delete entry and member HDU */
+	   int      *status)  /* return status code                          */
+
+/*
+  remove a member HDU from a grouping table. The fitsfile pointer gfptr must
+  be positioned with the grouping table as the CHDU, and the member to 
+  delete is identified by its row number in the table (first member == 1).
+  The rmopt parameter determines if the member entry is deleted from the
+  grouping table (in which case GRPIDn and GRPLCn keywords in the member 
+  HDU's header shall be updated accordingly) or if the member HDU shall 
+  itself be removed from its FITS file.
+*/
+
+{
+  int found;
+  int hdutype   = 0;
+  int index;
+  int iomode    = 0;
+
+  long i;
+  long ngroups      = 0;
+  long nmembers     = 0;
+  long groupExtver  = 0;
+  long grpid        = 0;
+
+  char grpLocation1[FLEN_FILENAME];
+  char grpLocation2[FLEN_FILENAME];
+  char grpLocation3[FLEN_FILENAME];
+  char cwd[FLEN_FILENAME];
+  char keyword[FLEN_KEYWORD];
+  /* SPR 1738 This can now be longer */
+  char grplc[FLEN_FILENAME];
+  char *tgrplc;
+  char keyvalue[FLEN_VALUE];
+  char card[FLEN_CARD];
+  char *editLocation;
+  char mrootname[FLEN_FILENAME], grootname[FLEN_FILENAME];
+
+  fitsfile *mfptr  = NULL;
+
+
+  if(*status != 0) return(*status);
+
+  do
+    {
+      /*
+	make sure the grouping table can be modified before proceeding
+      */
+
+      fits_file_mode(gfptr,&iomode,status);
+
+      if(iomode != READWRITE)
+	{
+	  ffpmsg("cannot modify grouping table (ffgtam)");
+	  *status = BAD_GROUP_DETACH;
+	  continue;
+	}
+
+      /* open the group member to be deleted and get its IOstatus*/
+
+      *status = fits_open_member(gfptr,member,&mfptr,status);
+      *status = fits_file_mode(mfptr,&iomode,status);
+
+      /*
+	 if the member HDU is to be deleted then call fits_unlink_member()
+	 to remove it from all groups to which it belongs (including
+	 this one) and then delete it. Note that if the member is a
+	 grouping table then we have to recursively call fits_remove_member()
+	 for each member of the member before we delete the member itself.
+      */
+
+      if(rmopt == OPT_RM_MBR)
+	{
+	    /* cannot delete a PHDU */
+	    if(fits_get_hdu_num(mfptr,&hdutype) == 1)
+		{
+		    *status = BAD_HDU_NUM;
+		    continue;
+		}
+
+	  /* determine if the member HDU is itself a grouping table */
+
+	  *status = fits_read_key_str(mfptr,"EXTNAME",keyvalue,card,status);
+
+	  /* if no EXTNAME is found then the HDU cannot be a grouping table */ 
+
+	  if(*status == KEY_NO_EXIST) 
+	    {
+	      keyvalue[0] = 0;
+	      *status = 0;
+	    }
+	  prepare_keyvalue(keyvalue);
+
+	  /* Any other error is a reason to abort */
+
+	  if(*status != 0) continue;
+
+	  /* if the EXTNAME == GROUPING then the member is a grouping table */
+	  
+	  if(strcasecmp(keyvalue,"GROUPING") == 0)
+	    {
+	      /* remove each of the grouping table members */
+	      
+	      *status = fits_get_num_members(mfptr,&nmembers,status);
+	      
+	      for(i = nmembers; i > 0 && *status == 0; --i)
+		*status = fits_remove_member(mfptr,i,OPT_RM_ENTRY,status);
+	      
+	      if(*status != 0) continue;
+	    }
+
+	  /* unlink the member HDU from all groups that contain it */
+
+	  *status = ffgmul(mfptr,0,status);
+
+	  if(*status != 0) continue;
+ 
+	  /* reset the grouping table HDU struct */
+
+	  fits_set_hdustruc(gfptr,status);
+
+	  /* delete the member HDU */
+
+	  if(iomode != READONLY)
+	    *status = fits_delete_hdu(mfptr,&hdutype,status);
+	}
+      else if(rmopt == OPT_RM_ENTRY)
+	{
+	  /* 
+	     The member HDU is only to be removed as an entry from this
+	     grouping table. Actions are (1) find the GRPIDn/GRPLCn 
+	     keywords that link the member to the grouping table, (2)
+	     remove the GRPIDn/GRPLCn keyword from the member HDU header
+	     and (3) remove the member entry from the grouping table
+	  */
+
+	  /*
+	    there is no need to seach for and remove the GRPIDn/GRPLCn
+	    keywords from the member HDU if it has not been opened
+	    in READWRITE mode
+	  */
+
+	  if(iomode == READWRITE)
+	    {	  	      
+	      /* 
+		 determine the group EXTVER value of the grouping table; if
+		 the member HDU and grouping table HDU do not reside in the 
+		 same file then set the groupExtver value to its negative 
+	      */
+	      
+	      *status = fits_read_key_lng(gfptr,"EXTVER",&groupExtver,card,
+					  status);
+	      /* Now, if either the Fptr values are the same, or the root filenames
+	         are the same, then assume these refer to the same file.
+	      */
+	      fits_parse_rootname(mfptr->Fptr->filename, mrootname, status);
+	      fits_parse_rootname(gfptr->Fptr->filename, grootname, status);
+
+	      if((mfptr->Fptr != gfptr->Fptr) && 
+	          strncmp(mrootname, grootname, FLEN_FILENAME))
+                       groupExtver = -1*groupExtver;
+	      
+	      /*
+		retrieve the URLs for the grouping table; note that it is 
+		possible that the grouping table file has two URLs, the 
+		one used to open it and the "real" one pointing to the 
+		actual file being accessed
+	      */
+	      
+	      *status = fits_get_url(gfptr,grpLocation1,grpLocation2,NULL,
+				     NULL,NULL,status);
+	      
+	      if(*status != 0) continue;
+	      
+	      /*
+		if either of the group location strings specify a relative
+		file path then convert them into absolute file paths
+	      */
+
+	      *status = fits_get_cwd(cwd,status);
+	      
+	      if(*grpLocation1 != 0 && *grpLocation1 != '/' &&
+		 !fits_is_url_absolute(grpLocation1))
+		{
+		  strcpy(grpLocation3,cwd);
+		  strcat(grpLocation3,"/");
+		  strcat(grpLocation3,grpLocation1);
+		  fits_clean_url(grpLocation3,grpLocation1,status);
+		}
+	      
+	      if(*grpLocation2 != 0 && *grpLocation2 != '/' &&
+		 !fits_is_url_absolute(grpLocation2))
+		{
+		  strcpy(grpLocation3,cwd);
+		  strcat(grpLocation3,"/");
+		  strcat(grpLocation3,grpLocation2);
+		  fits_clean_url(grpLocation3,grpLocation2,status);
+		}
+	      
+	      /*
+		determine the number of groups to which the member HDU 
+		belongs
+	      */
+	      
+	      *status = fits_get_num_groups(mfptr,&ngroups,status);
+	      
+	      /* reset the HDU keyword position counter to the beginning */
+	      
+	      *status = ffgrec(mfptr,0,card,status);
+	      
+	      /*
+		loop over all the GRPIDn keywords in the member HDU header 
+		and find the appropriate GRPIDn and GRPLCn keywords that 
+		identify it as belonging to the group
+	      */
+	      
+	      for(index = 1, found = 0; index <= ngroups && *status == 0 && 
+		    !found; ++index)
+		{	  
+		  /* read the next GRPIDn keyword in the series */
+		  
+		  sprintf(keyword,"GRPID%d",index);
+		  
+		  *status = fits_read_key_lng(mfptr,keyword,&grpid,card,
+					      status);
+		  if(*status != 0) continue;
+		  
+		  /* 
+		     grpid value == group EXTVER value then we could have a 
+		     match
+		  */
+		  
+		  if(grpid == groupExtver && grpid > 0)
+		    {
+		      /*
+			if GRPID is positive then its a match because 
+			both the member HDU and grouping table HDU reside
+			in the same FITS file
+		      */
+		      
+		      found = index;
+		    }
+		  else if(grpid == groupExtver && grpid < 0)
+		    {
+		      /* 
+			 have to look at the GRPLCn value to determine a 
+			 match because the member HDU and grouping table 
+			 HDU reside in different FITS files
+		      */
+		      
+		      sprintf(keyword,"GRPLC%d",index);
+		      
+		      /* SPR 1738 */
+		      *status = fits_read_key_longstr(mfptr,keyword,&tgrplc,
+						      card, status);
+		      if (0 == *status) {
+			strcpy(grplc,tgrplc);
+			free(tgrplc);
+		      }
+		      		      
+		      if(*status == KEY_NO_EXIST)
+			{
+			  /* 
+			     no GRPLCn keyword value found ==> grouping
+			     convention not followed; nothing we can do 
+			     about it, so just continue
+			  */
+			  
+			  sprintf(card,"No GRPLC%d found for GRPID%d",
+				  index,index);
+			  ffpmsg(card);
+			  *status = 0;
+			  continue;
+			}
+		      else if (*status != 0) continue;
+		      
+		      /* construct the URL for the GRPLCn value */
+		      
+		      prepare_keyvalue(grplc);
+		      
+		      /*
+			if the grplc value specifies a relative path then
+			turn it into a absolute file path for comparison
+			purposes
+		      */
+		      
+		      if(*grplc != 0 && !fits_is_url_absolute(grplc) &&
+			 *grplc != '/')
+			{
+			    /* No, wrong, 
+			       strcpy(grpLocation3,cwd);
+			       should be */
+			    *status = fits_file_name(mfptr,grpLocation3,status);
+			    /* Remove everything after the last / */
+			    if (NULL != (editLocation = strrchr(grpLocation3,'/'))) {
+				*editLocation = '\0';
+			    }
+				
+			  strcat(grpLocation3,"/");
+			  strcat(grpLocation3,grplc);
+			  *status = fits_clean_url(grpLocation3,grplc,
+						   status);
+			}
+		      
+		      /*
+			if the absolute value of GRPIDn is equal to the
+			EXTVER value of the grouping table and (one of the 
+			possible two) grouping table file URL matches the
+			GRPLCn keyword value then we hava a match
+		      */
+		      
+		      if(strcmp(grplc,grpLocation1) == 0  || 
+			 strcmp(grplc,grpLocation2) == 0) 
+			found = index; 
+		    }
+		}
+
+	      /*
+		if found == 0 (false) after the above search then we assume 
+		that it is due to an inpromper updating of the GRPIDn and 
+		GRPLCn keywords in the member header ==> nothing to delete 
+		in the header. Else delete the GRPLCn and GRPIDn keywords 
+		that identify the member HDU with the group HDU and 
+		re-enumerate the remaining GRPIDn and GRPLCn keywords
+	      */
+
+	      if(found != 0)
+		{
+		  sprintf(keyword,"GRPID%d",found);
+		  *status = fits_delete_key(mfptr,keyword,status);
+		  
+		  sprintf(keyword,"GRPLC%d",found);
+		  *status = fits_delete_key(mfptr,keyword,status);
+		  
+		  *status = 0;
+		  
+		  /* call fits_get_num_groups() to re-enumerate the GRPIDn */
+		  
+		  *status = fits_get_num_groups(mfptr,&ngroups,status);
+		} 
+	    }
+
+	  /*
+	     finally, remove the member entry from the current grouping table
+	     pointed to by gfptr
+	  */
+
+	  *status = fits_delete_rows(gfptr,member,1,status);
+	}
+      else
+	{
+	  *status = BAD_OPTION;
+	  ffpmsg("Invalid value specified for the rmopt parameter (ffgmrm)");
+	}
+
+    }while(0);
+
+  if(mfptr != NULL) 
+    {
+      fits_close_file(mfptr,status);
+    }
+
+  return(*status);
+}
+
+/*---------------------------------------------------------------------------
+                 Grouping Table support functions
+  ---------------------------------------------------------------------------*/
+int ffgtgc(fitsfile *gfptr,  /* pointer to the grouping table                */
+	   int *xtensionCol, /* column ID of the MEMBER_XTENSION column      */
+	   int *extnameCol,  /* column ID of the MEMBER_NAME column          */
+	   int *extverCol,   /* column ID of the MEMBER_VERSION column       */
+	   int *positionCol, /* column ID of the MEMBER_POSITION column      */
+	   int *locationCol, /* column ID of the MEMBER_LOCATION column      */
+	   int *uriCol,      /* column ID of the MEMBER_URI_TYPE column      */
+	   int *grptype,     /* group structure type code specifying the
+				grouping table columns that are defined:
+				GT_ID_ALL_URI  (0) ==> all columns defined   
+				GT_ID_REF      (1) ==> reference cols only   
+				GT_ID_POS      (2) ==> position col only     
+				GT_ID_ALL      (3) ==> ref & pos cols        
+				GT_ID_REF_URI (11) ==> ref & loc cols        
+				GT_ID_POS_URI (12) ==> pos & loc cols        */
+	   int *status)      /* return status code                           */
+/*
+   examine the grouping table pointed to by gfptr and determine the column
+   index ID of each possible grouping column. If a column is not found then
+   an index of 0 is returned. the grptype parameter returns the structure
+   of the grouping table ==> what columns are defined.
+*/
+
+{
+
+  char keyvalue[FLEN_VALUE];
+  char comment[FLEN_COMMENT];
+
+
+  if(*status != 0) return(*status);
+
+  do
+    {
+      /*
+	if the HDU does not have an extname of "GROUPING" then it is not
+	a grouping table
+      */
+
+      *status = fits_read_key_str(gfptr,"EXTNAME",keyvalue,comment,status);
+  
+      if(*status == KEY_NO_EXIST) 
+	{
+	  *status = NOT_GROUP_TABLE;
+	  ffpmsg("Specified HDU is not a Grouping Table (ffgtgc)");
+	}
+      if(*status != 0) continue;
+
+      prepare_keyvalue(keyvalue);
+
+      if(strcasecmp(keyvalue,"GROUPING") != 0)
+	{
+	  *status = NOT_GROUP_TABLE;
+	  continue;
+	}
+
+      /*
+        search for the MEMBER_XTENSION, MEMBER_NAME, MEMBER_VERSION,
+	MEMBER_POSITION, MEMBER_LOCATION and MEMBER_URI_TYPE columns
+	and determine their column index ID
+      */
+
+      *status = fits_get_colnum(gfptr,CASESEN,"MEMBER_XTENSION",xtensionCol,
+				status);
+
+      if(*status == COL_NOT_FOUND)
+	{
+	  *status      = 0;
+ 	  *xtensionCol = 0;
+	}
+
+      if(*status != 0) continue;
+
+      *status = fits_get_colnum(gfptr,CASESEN,"MEMBER_NAME",extnameCol,status);
+
+      if(*status == COL_NOT_FOUND)
+	{
+	  *status     = 0;
+	  *extnameCol = 0;
+	}
+
+      if(*status != 0) continue;
+
+      *status = fits_get_colnum(gfptr,CASESEN,"MEMBER_VERSION",extverCol,
+				status);
+
+      if(*status == COL_NOT_FOUND)
+	{
+	  *status    = 0;
+	  *extverCol = 0;
+	}
+
+      if(*status != 0) continue;
+
+      *status = fits_get_colnum(gfptr,CASESEN,"MEMBER_POSITION",positionCol,
+				status);
+
+      if(*status == COL_NOT_FOUND)
+	{
+	  *status      = 0;
+	  *positionCol = 0;
+	}
+
+      if(*status != 0) continue;
+
+      *status = fits_get_colnum(gfptr,CASESEN,"MEMBER_LOCATION",locationCol,
+				status);
+
+      if(*status == COL_NOT_FOUND)
+	{
+	  *status      = 0;
+	  *locationCol = 0;
+	}
+
+      if(*status != 0) continue;
+
+      *status = fits_get_colnum(gfptr,CASESEN,"MEMBER_URI_TYPE",uriCol,
+				status);
+
+      if(*status == COL_NOT_FOUND)
+	{
+	  *status = 0;
+	  *uriCol = 0;
+	}
+
+      if(*status != 0) continue;
+
+      /*
+	 determine the type of grouping table structure used by this
+	 grouping table and record it in the grptype parameter
+      */
+
+      if(*xtensionCol && *extnameCol && *extverCol && *positionCol &&
+	 *locationCol && *uriCol) 
+	*grptype = GT_ID_ALL_URI;
+      
+      else if(*xtensionCol && *extnameCol && *extverCol &&
+	      *locationCol && *uriCol) 
+	*grptype = GT_ID_REF_URI;
+
+      else if(*xtensionCol && *extnameCol && *extverCol && *positionCol)
+	*grptype = GT_ID_ALL;
+      
+      else if(*xtensionCol && *extnameCol && *extverCol)
+	*grptype = GT_ID_REF;
+      
+      else if(*positionCol && *locationCol && *uriCol) 
+	*grptype = GT_ID_POS_URI;
+      
+      else if(*positionCol)
+	*grptype = GT_ID_POS;
+      
+      else
+	*status = NOT_GROUP_TABLE;
+      
+    }while(0);
+
+  /*
+    if the table contained more than one column with a reserved name then
+    this cannot be considered a vailid grouping table
+  */
+
+  if(*status == COL_NOT_UNIQUE) 
+    {
+      *status = NOT_GROUP_TABLE;
+      ffpmsg("Specified HDU has multipule Group table cols defined (ffgtgc)");
+    }
+
+  return(*status);
+}
+
+/*****************************************************************************/
+int ffgtdc(int   grouptype,     /* code specifying the type of
+				   grouping table information:
+				   GT_ID_ALL_URI  0 ==> defualt (all columns)
+				   GT_ID_REF      1 ==> ID by reference
+				   GT_ID_POS      2 ==> ID by position
+				   GT_ID_ALL      3 ==> ID by ref. and position
+				   GT_ID_REF_URI 11 ==> (1) + URI info 
+				   GT_ID_POS_URI 12 ==> (2) + URI info       */
+	   int   xtensioncol, /* does MEMBER_XTENSION already exist?         */
+	   int   extnamecol,  /* does MEMBER_NAME aleady exist?              */
+	   int   extvercol,   /* does MEMBER_VERSION already exist?          */
+	   int   positioncol, /* does MEMBER_POSITION already exist?         */
+	   int   locationcol, /* does MEMBER_LOCATION already exist?         */
+	   int   uricol,      /* does MEMBER_URI_TYPE aleardy exist?         */
+	   char *ttype[],     /* array of grouping table column TTYPE names
+				 to define (if *col var false)               */
+	   char *tform[],     /* array of grouping table column TFORM values
+				 to define (if*col variable false)           */
+	   int  *ncols,       /* number of TTYPE and TFORM values returned   */
+	   int  *status)      /* return status code                          */
+
+/*
+  create the TTYPE and TFORM values for the grouping table according to the
+  value of the grouptype parameter and the values of the *col flags. The
+  resulting TTYPE and TFORM are returned in ttype[] and tform[] respectively.
+  The number of TTYPE and TFORMs returned is given by ncols. Both the TTYPE[]
+  and TTFORM[] arrays must contain enough pre-allocated strings to hold
+  the returned information.
+*/
+
+{
+
+  int i = 0;
+
+  char  xtension[]  = "MEMBER_XTENSION";
+  char  xtenTform[] = "8A";
+  
+  char  name[]      = "MEMBER_NAME";
+  char  nameTform[] = "32A";
+
+  char  version[]   = "MEMBER_VERSION";
+  char  verTform[]  = "1J";
+  
+  char  position[]  = "MEMBER_POSITION";
+  char  posTform[]  = "1J";
+
+  char  URI[]       = "MEMBER_URI_TYPE";
+  char  URITform[]  = "3A";
+
+  char  location[]  = "MEMBER_LOCATION";
+  /* SPR 01720, move from 160A to 256A */
+  char  locTform[]  = "256A";
+
+
+  if(*status != 0) return(*status);
+
+  switch(grouptype)
+    {
+      
+    case GT_ID_ALL_URI:
+
+      if(xtensioncol == 0)
+	{
+	  strcpy(ttype[i],xtension);
+	  strcpy(tform[i],xtenTform);
+	  ++i;
+	}
+      if(extnamecol == 0)
+	{
+	  strcpy(ttype[i],name);
+	  strcpy(tform[i],nameTform);
+	  ++i;
+	}
+      if(extvercol == 0)
+	{
+	  strcpy(ttype[i],version);
+	  strcpy(tform[i],verTform);
+	  ++i;
+	}
+      if(positioncol == 0)
+	{
+	  strcpy(ttype[i],position);
+	  strcpy(tform[i],posTform);
+	  ++i;
+	}
+      if(locationcol == 0)
+	{
+	  strcpy(ttype[i],location);
+	  strcpy(tform[i],locTform);
+	  ++i;
+	}
+      if(uricol == 0)
+	{
+	  strcpy(ttype[i],URI);
+	  strcpy(tform[i],URITform);
+	  ++i;
+	}
+      break;
+      
+    case GT_ID_REF:
+      
+      if(xtensioncol == 0)
+	{
+	  strcpy(ttype[i],xtension);
+	  strcpy(tform[i],xtenTform);
+	  ++i;
+	}
+      if(extnamecol == 0)
+	{
+	  strcpy(ttype[i],name);
+	  strcpy(tform[i],nameTform);
+	  ++i;
+	}
+      if(extvercol == 0)
+	{
+	  strcpy(ttype[i],version);
+	  strcpy(tform[i],verTform);
+	  ++i;
+	}
+      break;
+      
+    case GT_ID_POS:
+      
+      if(positioncol == 0)
+	{
+	  strcpy(ttype[i],position);
+	  strcpy(tform[i],posTform);
+	  ++i;
+	}	  
+      break;
+      
+    case GT_ID_ALL:
+      
+      if(xtensioncol == 0)
+	{
+	  strcpy(ttype[i],xtension);
+	  strcpy(tform[i],xtenTform);
+	  ++i;
+	}
+      if(extnamecol == 0)
+	{
+	  strcpy(ttype[i],name);
+	  strcpy(tform[i],nameTform);
+	  ++i;
+	}
+      if(extvercol == 0)
+	{
+	  strcpy(ttype[i],version);
+	  strcpy(tform[i],verTform);
+	  ++i;
+	}
+      if(positioncol == 0)
+	{
+	  strcpy(ttype[i],position);
+	  strcpy(tform[i], posTform);
+	  ++i;
+	}	  
+      
+      break;
+      
+    case GT_ID_REF_URI:
+      
+      if(xtensioncol == 0)
+	{
+	  strcpy(ttype[i],xtension);
+	  strcpy(tform[i],xtenTform);
+	  ++i;
+	}
+      if(extnamecol == 0)
+	{
+	  strcpy(ttype[i],name);
+	  strcpy(tform[i],nameTform);
+	  ++i;
+	}
+      if(extvercol == 0)
+	{
+	  strcpy(ttype[i],version);
+	  strcpy(tform[i],verTform);
+	  ++i;
+	}
+      if(locationcol == 0)
+	{
+	  strcpy(ttype[i],location);
+	  strcpy(tform[i],locTform);
+	  ++i;
+	}
+      if(uricol == 0)
+	{
+	  strcpy(ttype[i],URI);
+	  strcpy(tform[i],URITform);
+	  ++i;
+	}
+      break;
+      
+    case GT_ID_POS_URI:
+      
+      if(positioncol == 0)
+	{
+	  strcpy(ttype[i],position);
+	  strcpy(tform[i],posTform);
+	  ++i;
+	}
+      if(locationcol == 0)
+	{
+	  strcpy(ttype[i],location);
+	  strcpy(tform[i],locTform);
+	  ++i;
+	}
+      if(uricol == 0)
+	{
+	  strcpy(ttype[i],URI);
+	  strcpy(tform[i],URITform);
+	  ++i;
+	}
+      break;
+      
+    default:
+      
+      *status = BAD_OPTION;
+      ffpmsg("Invalid value specified for the grouptype parameter (ffgtdc)");
+
+      break;
+
+    }
+
+  *ncols = i;
+  
+  return(*status);
+}
+
+/*****************************************************************************/
+int ffgmul(fitsfile *mfptr,   /* pointer to the grouping table member HDU    */
+           int       rmopt,   /* 0 ==> leave GRPIDn/GRPLCn keywords,
+				 1 ==> remove GRPIDn/GRPLCn keywords         */
+	   int      *status) /* return status code                          */
+
+/*
+   examine all the GRPIDn and GRPLCn keywords in the member HDUs header
+   and remove the member from the grouping tables referenced; This
+   effectively "unlinks" the member from all of its groups. The rmopt 
+   specifies if the GRPIDn/GRPLCn keywords are to be removed from the
+   member HDUs header after the unlinking.
+*/
+
+{
+  int memberPosition = 0;
+  int iomode;
+
+  long index;
+  long ngroups      = 0;
+  long memberExtver = 0;
+  long memberID     = 0;
+
+  char mbrLocation1[FLEN_FILENAME];
+  char mbrLocation2[FLEN_FILENAME];
+  char memberHDUtype[FLEN_VALUE];
+  char memberExtname[FLEN_VALUE];
+  char keyword[FLEN_KEYWORD];
+  char card[FLEN_CARD];
+
+  fitsfile *gfptr = NULL;
+
+
+  if(*status != 0) return(*status);
+
+  do
+    {
+      /* 
+	 determine location parameters of the member HDU; note that
+	 default values are supplied if the expected keywords are not
+	 found
+      */
+
+      *status = fits_read_key_str(mfptr,"XTENSION",memberHDUtype,card,status);
+
+      if(*status == KEY_NO_EXIST) 
+	{
+	  strcpy(memberHDUtype,"PRIMARY");
+	  *status = 0;
+	}
+      prepare_keyvalue(memberHDUtype);
+
+      *status = fits_read_key_lng(mfptr,"EXTVER",&memberExtver,card,status);
+
+      if(*status == KEY_NO_EXIST) 
+	{
+	  memberExtver = 1;
+	  *status      = 0;
+	}
+
+      *status = fits_read_key_str(mfptr,"EXTNAME",memberExtname,card,status);
+
+      if(*status == KEY_NO_EXIST) 
+	{
+	  memberExtname[0] = 0;
+	  *status          = 0;
+	}
+      prepare_keyvalue(memberExtname);
+
+      fits_get_hdu_num(mfptr,&memberPosition);
+
+      *status = fits_get_url(mfptr,mbrLocation1,mbrLocation2,NULL,NULL,
+			     NULL,status);
+
+      if(*status != 0) continue;
+
+      /*
+	 open each grouping table linked to this HDU and remove the member 
+	 from the grouping tables
+      */
+
+      *status = fits_get_num_groups(mfptr,&ngroups,status);
+
+      /* loop over each group linked to the member HDU */
+
+      for(index = 1; index <= ngroups && *status == 0; ++index)
+	{
+	  /* open the (index)th group linked to the member HDU */ 
+
+	  *status = fits_open_group(mfptr,index,&gfptr,status);
+
+	  /* if the group could not be opened then just skip it */
+
+	  if(*status != 0)
+	    {
+	      *status = 0;
+	      sprintf(card,"Cannot open the %dth group table (ffgmul)",
+		      (int)index);
+	      ffpmsg(card);
+	      continue;
+	    }
+
+	  /*
+	    make sure the grouping table can be modified before proceeding
+	  */
+	  
+	  fits_file_mode(gfptr,&iomode,status);
+
+	  if(iomode != READWRITE)
+	    {
+	      sprintf(card,"The %dth group cannot be modified (ffgtam)",
+		      (int)index);
+	      ffpmsg(card);
+	      continue;
+	    }
+
+	  /* 
+	     try to find the member's row within the grouping table; first 
+	     try using the member HDU file's "real" URL string then try
+	     using its originally opened URL string if either string exist
+	   */
+	     
+	  memberID = 0;
+ 
+	  if(strlen(mbrLocation1) != 0)
+	    {
+	      *status = ffgmf(gfptr,memberHDUtype,memberExtname,memberExtver,
+			      memberPosition,mbrLocation1,&memberID,status);
+	    }
+
+	  if(*status == MEMBER_NOT_FOUND && strlen(mbrLocation2) != 0)
+	    {
+	      *status = 0;
+	      *status = ffgmf(gfptr,memberHDUtype,memberExtname,memberExtver,
+			      memberPosition,mbrLocation2,&memberID,status);
+	    }
+
+	  /* if the member was found then delete it from the grouping table */
+
+	  if(*status == 0)
+	    *status = fits_delete_rows(gfptr,memberID,1,status);
+
+	  /*
+	     continue the loop over all member groups even if an error
+	     was generated
+	  */
+
+	  if(*status == MEMBER_NOT_FOUND)
+	    {
+	      ffpmsg("cannot locate member's entry in group table (ffgmul)");
+	    }
+	  *status = 0;
+
+	  /*
+	     close the file pointed to by gfptr if it is non NULL to
+	     prepare for the next loop iterration
+	  */
+
+	  if(gfptr != NULL)
+	    {
+	      fits_close_file(gfptr,status);
+	      gfptr = NULL;
+	    }
+	}
+
+      if(*status != 0) continue;
+
+      /*
+	 if rmopt is non-zero then find and delete the GRPIDn/GRPLCn 
+	 keywords from the member HDU header
+      */
+
+      if(rmopt != 0)
+	{
+	  fits_file_mode(mfptr,&iomode,status);
+
+	  if(iomode == READONLY)
+	    {
+	      ffpmsg("Cannot modify member HDU, opened READONLY (ffgmul)");
+	      continue;
+	    }
+
+	  /* delete all the GRPIDn/GRPLCn keywords */
+
+	  for(index = 1; index <= ngroups && *status == 0; ++index)
+	    {
+	      sprintf(keyword,"GRPID%d",(int)index);
+	      fits_delete_key(mfptr,keyword,status);
+	      
+	      sprintf(keyword,"GRPLC%d",(int)index);
+	      fits_delete_key(mfptr,keyword,status);
+
+	      if(*status == KEY_NO_EXIST) *status = 0;
+	    }
+	}
+    }while(0);
+
+  /* make sure the gfptr has been closed */
+
+  if(gfptr != NULL)
+    { 
+      fits_close_file(gfptr,status);
+    }
+
+return(*status);
+}
+
+/*--------------------------------------------------------------------------*/
+int ffgmf(fitsfile *gfptr, /* pointer to grouping table HDU to search       */
+	   char *xtension,  /* XTENSION value for member HDU                */
+	   char *extname,   /* EXTNAME value for member HDU                 */
+	   int   extver,    /* EXTVER value for member HDU                  */
+	   int   position,  /* HDU position value for member HDU            */
+	   char *location,  /* FITS file location value for member HDU      */
+	   long *member,    /* member HDU ID within group table (if found)  */
+	   int  *status)    /* return status code                           */
+
+/*
+   try to find the entry for the member HDU defined by the xtension, extname,
+   extver, position, and location parameters within the grouping table
+   pointed to by gfptr. If the member HDU is found then its ID (row number)
+   within the grouping table is returned in the member variable; if not
+   found then member is returned with a value of 0 and the status return
+   code will be set to MEMBER_NOT_FOUND.
+
+   Note that the member HDU postion information is used to obtain a member
+   match only if the grouping table type is GT_ID_POS_URI or GT_ID_POS. This
+   is because the position information can become invalid much more
+   easily then the reference information for a group member.
+*/
+
+{
+  int xtensionCol,extnameCol,extverCol,positionCol,locationCol,uriCol;
+  int mposition = 0;
+  int grptype;
+  int dummy;
+  int i;
+
+  long nmembers = 0;
+  long mextver  = 0;
+ 
+  char  charBuff1[FLEN_FILENAME];
+  char  charBuff2[FLEN_FILENAME];
+  char  tmpLocation[FLEN_FILENAME];
+  char  mbrLocation1[FLEN_FILENAME];
+  char  mbrLocation2[FLEN_FILENAME];
+  char  mbrLocation3[FLEN_FILENAME];
+  char  grpLocation1[FLEN_FILENAME];
+  char  grpLocation2[FLEN_FILENAME];
+  char  cwd[FLEN_FILENAME];
+
+  char  nstr[] = {'\0'};
+  char *tmpPtr[2];
+
+  if(*status != 0) return(*status);
+
+  *member = 0;
+
+  tmpPtr[0] = charBuff1;
+  tmpPtr[1] = charBuff2;
+
+
+  if(*status != 0) return(*status);
+
+  /*
+    if the passed LOCATION value is not an absolute URL then turn it
+    into an absolute path
+  */
+
+  if(location == NULL)
+    {
+      *tmpLocation = 0;
+    }
+
+  else if(*location == 0)
+    {
+      *tmpLocation = 0;
+    }
+
+  else if(!fits_is_url_absolute(location))
+    {
+      fits_path2url(location,tmpLocation,status);
+
+      if(*tmpLocation != '/')
+	{
+	  fits_get_cwd(cwd,status);
+	  strcat(cwd,"/");
+	  strcat(cwd,tmpLocation);
+	  fits_clean_url(cwd,tmpLocation,status);
+	}
+    }
+
+  else
+    strcpy(tmpLocation,location);
+
+  /*
+     retrieve the Grouping Convention reserved column positions within
+     the grouping table
+  */
+
+  *status = ffgtgc(gfptr,&xtensionCol,&extnameCol,&extverCol,&positionCol,
+		   &locationCol,&uriCol,&grptype,status);
+
+  /* retrieve the number of group members */
+
+  *status = fits_get_num_members(gfptr,&nmembers,status);
+	      
+  /* 
+     loop over all grouping table rows until the member HDU is found 
+  */
+
+  for(i = 1; i <= nmembers && *member == 0 && *status == 0; ++i)
+    {
+      if(xtensionCol != 0)
+	{
+	  fits_read_col_str(gfptr,xtensionCol,i,1,1,nstr,tmpPtr,&dummy,status);
+	  if(strcasecmp(tmpPtr[0],xtension) != 0) continue;
+	}
+	  
+      if(extnameCol  != 0)
+	{
+	  fits_read_col_str(gfptr,extnameCol,i,1,1,nstr,tmpPtr,&dummy,status);
+	  if(strcasecmp(tmpPtr[0],extname) != 0) continue;
+	}
+	  
+      if(extverCol   != 0)
+	{
+	  fits_read_col_lng(gfptr,extverCol,i,1,1,0,
+			    (long*)&mextver,&dummy,status);
+	  if(extver != mextver) continue;
+	}
+      
+      /* note we only use postionCol if we have to */
+
+      if(positionCol != 0 && 
+	            (grptype == GT_ID_POS || grptype == GT_ID_POS_URI))
+	{
+	  fits_read_col_int(gfptr,positionCol,i,1,1,0,
+			    &mposition,&dummy,status);
+	  if(position != mposition) continue;
+	}
+      
+      /*
+	if no location string was passed to the function then assume that
+	the calling application does not wish to use it as a comparision
+	critera ==> if we got this far then we have a match
+      */
+
+      if(location == NULL)
+	{
+	  ffpmsg("NULL Location string given ==> ingore location (ffgmf)");
+	  *member = i;
+	  continue;
+	}
+
+      /*
+	if the grouping table MEMBER_LOCATION column exists then read the
+	location URL for the member, else set the location string to
+	a zero-length string for subsequent comparisions
+      */
+
+      if(locationCol != 0)
+	{
+	  fits_read_col_str(gfptr,locationCol,i,1,1,nstr,tmpPtr,&dummy,status);
+	  strcpy(mbrLocation1,tmpPtr[0]);
+	  *mbrLocation2 = 0;
+	}
+      else
+	*mbrLocation1 = 0;
+
+      /* 
+	 if the member location string from the grouping table is zero 
+	 length (either implicitly or explicitly) then assume that the 
+	 member HDU is in the same file as the grouping table HDU; retrieve
+	 the possible URL values of the grouping table HDU file 
+       */
+
+      if(*mbrLocation1 == 0)
+	{
+	  /* retrieve the possible URLs of the grouping table file */
+	  *status = fits_get_url(gfptr,mbrLocation1,mbrLocation2,NULL,NULL,
+				 NULL,status);
+
+	  /* if non-NULL, make sure the first URL is absolute or a full path */
+	  if(*mbrLocation1 != 0 && !fits_is_url_absolute(mbrLocation1) &&
+	     *mbrLocation1 != '/')
+	    {
+	      fits_get_cwd(cwd,status);
+	      strcat(cwd,"/");
+	      strcat(cwd,mbrLocation1);
+	      fits_clean_url(cwd,mbrLocation1,status);
+	    }
+
+	  /* if non-NULL, make sure the first URL is absolute or a full path */
+	  if(*mbrLocation2 != 0 && !fits_is_url_absolute(mbrLocation2) &&
+	     *mbrLocation2 != '/')
+	    {
+	      fits_get_cwd(cwd,status);
+	      strcat(cwd,"/");
+	      strcat(cwd,mbrLocation2);
+	      fits_clean_url(cwd,mbrLocation2,status);
+	    }
+	}
+
+      /*
+	if the member location was specified, then make sure that it is
+	either an absolute URL or specifies a full path
+      */
+
+      else if(!fits_is_url_absolute(mbrLocation1) && *mbrLocation1 != '/')
+	{
+	  strcpy(mbrLocation2,mbrLocation1);
+
+	  /* get the possible URLs for the grouping table file */
+	  *status = fits_get_url(gfptr,grpLocation1,grpLocation2,NULL,NULL,
+				 NULL,status);
+	  
+	  if(*grpLocation1 != 0)
+	    {
+	      /* make sure the first grouping table URL is absolute */
+	      if(!fits_is_url_absolute(grpLocation1) && *grpLocation1 != '/')
+		{
+		  fits_get_cwd(cwd,status);
+		  strcat(cwd,"/");
+		  strcat(cwd,grpLocation1);
+		  fits_clean_url(cwd,grpLocation1,status);
+		}
+	      
+	      /* create an absoute URL for the member */
+
+	      fits_relurl2url(grpLocation1,mbrLocation1,mbrLocation3,status);
+	      
+	      /* 
+		 if URL construction succeeded then copy it to the
+		 first location string; else set the location string to 
+		 empty
+	      */
+
+	      if(*status == 0)
+		{
+		  strcpy(mbrLocation1,mbrLocation3);
+		}
+
+	      else if(*status == URL_PARSE_ERROR)
+		{
+		  *status       = 0;
+		  *mbrLocation1 = 0;
+		}
+	    }
+	  else
+	    *mbrLocation1 = 0;
+
+	  if(*grpLocation2 != 0)
+	    {
+	      /* make sure the second grouping table URL is absolute */
+	      if(!fits_is_url_absolute(grpLocation2) && *grpLocation2 != '/')
+		{
+		  fits_get_cwd(cwd,status);
+		  strcat(cwd,"/");
+		  strcat(cwd,grpLocation2);
+		  fits_clean_url(cwd,grpLocation2,status);
+		}
+	      
+	      /* create an absolute URL for the member */
+
+	      fits_relurl2url(grpLocation2,mbrLocation2,mbrLocation3,status);
+	      
+	      /* 
+		 if URL construction succeeded then copy it to the
+		 second location string; else set the location string to 
+		 empty
+	      */
+
+	      if(*status == 0)
+		{
+		  strcpy(mbrLocation2,mbrLocation3);
+		}
+
+	      else if(*status == URL_PARSE_ERROR)
+		{
+		  *status       = 0;
+		  *mbrLocation2 = 0;
+		}
+	    }
+	  else
+	    *mbrLocation2 = 0;
+	}
+
+      /*
+	compare the passed member HDU file location string with the
+	(possibly two) member location strings to see if there is a match
+       */
+
+      if(strcmp(mbrLocation1,tmpLocation) != 0 && 
+	 strcmp(mbrLocation2,tmpLocation) != 0   ) continue;
+  
+      /* if we made it this far then a match to the member HDU was found */
+      
+      *member = i;
+    }
+
+  /* if a match was not found then set the return status code */
+
+  if(*member == 0 && *status == 0) 
+    {
+      *status = MEMBER_NOT_FOUND;
+      ffpmsg("Cannot find specified member HDU (ffgmf)");
+    }
+
+  return(*status);
+}
+
+/*--------------------------------------------------------------------------
+                        Recursive Group Functions
+  --------------------------------------------------------------------------*/
+int ffgtrmr(fitsfile   *gfptr,  /* FITS file pointer to group               */
+	    HDUtracker *HDU,    /* list of processed HDUs                   */
+	    int        *status) /* return status code                       */
+	    
+/*
+  recursively remove a grouping table and all its members. Each member of
+  the grouping table pointed to by gfptr it processed. If the member is itself
+  a grouping table then ffgtrmr() is recursively called to process all
+  of its members. The HDUtracker struct *HDU is used to make sure a member
+  is not processed twice, thus avoiding an infinite loop (e.g., a grouping
+  table contains itself as a member).
+*/
+
+{
+  int i;
+  int hdutype;
+
+  long nmembers = 0;
+
+  char keyvalue[FLEN_VALUE];
+  char comment[FLEN_COMMENT];
+  
+  fitsfile *mfptr = NULL;
+
+
+  if(*status != 0) return(*status);
+
+  /* get the number of members contained by this grouping table */
+
+  *status = fits_get_num_members(gfptr,&nmembers,status);
+
+  /* loop over all group members and delete them */
+
+  for(i = nmembers; i > 0 && *status == 0; --i)
+    {
+      /* open the member HDU */
+
+      *status = fits_open_member(gfptr,i,&mfptr,status);
+
+      /* if the member cannot be opened then just skip it and continue */
+
+      if(*status == MEMBER_NOT_FOUND) 
+	{
+	  *status = 0;
+	  continue;
+	}
+
+      /* Any other error is a reason to abort */
+      
+      if(*status != 0) continue;
+
+      /* add the member HDU to the HDUtracker struct */
+
+      *status = fftsad(mfptr,HDU,NULL,NULL);
+
+      /* status == HDU_ALREADY_TRACKED ==> HDU has already been processed */
+
+      if(*status == HDU_ALREADY_TRACKED) 
+	{
+	  *status = 0;
+	  fits_close_file(mfptr,status);
+	  continue;
+	}
+      else if(*status != 0) continue;
+
+      /* determine if the member HDU is itself a grouping table */
+
+      *status = fits_read_key_str(mfptr,"EXTNAME",keyvalue,comment,status);
+
+      /* if no EXTNAME is found then the HDU cannot be a grouping table */ 
+
+      if(*status == KEY_NO_EXIST) 
+	{
+	  *status     = 0;
+	  keyvalue[0] = 0;
+	}
+      prepare_keyvalue(keyvalue);
+
+      /* Any other error is a reason to abort */
+      
+      if(*status != 0) continue;
+
+      /* 
+	 if the EXTNAME == GROUPING then the member is a grouping table 
+	 and we must call ffgtrmr() to process its members
+      */
+
+      if(strcasecmp(keyvalue,"GROUPING") == 0)
+	  *status = ffgtrmr(mfptr,HDU,status);  
+
+      /* 
+	 unlink all the grouping tables that contain this HDU as a member 
+	 and then delete the HDU (if not a PHDU)
+      */
+
+      if(fits_get_hdu_num(mfptr,&hdutype) == 1)
+	      *status = ffgmul(mfptr,1,status);
+      else
+	  {
+	      *status = ffgmul(mfptr,0,status);
+	      *status = fits_delete_hdu(mfptr,&hdutype,status);
+	  }
+
+      /* close the fitsfile pointer */
+
+      fits_close_file(mfptr,status);
+    }
+
+  return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgtcpr(fitsfile   *infptr,  /* input FITS file pointer                 */
+	    fitsfile   *outfptr, /* output FITS file pointer                */
+	    int         cpopt,   /* code specifying copy options:
+				    OPT_GCP_GPT (0) ==> cp only grouping table
+				    OPT_GCP_ALL (2) ==> recusrively copy 
+				    members and their members (if groups)   */
+	    HDUtracker *HDU,     /* list of already copied HDUs             */
+	    int        *status)  /* return status code                      */
+
+/*
+  copy a Group to a new FITS file. If the cpopt parameter is set to 
+  OPT_GCP_GPT (copy grouping table only) then the existing members have their 
+  GRPIDn and GRPLCn keywords updated to reflect the existance of the new group,
+  since they now belong to another group. If cpopt is set to OPT_GCP_ALL 
+  (copy grouping table and members recursively) then the original members are 
+  not updated; the new grouping table is modified to include only the copied 
+  member HDUs and not the original members.
+
+  Note that this function is recursive. When copt is OPT_GCP_ALL it will call
+  itself whenever a member HDU of the current grouping table is itself a
+  grouping table (i.e., EXTNAME = 'GROUPING').
+*/
+
+{
+
+  int i;
+  int nexclude     = 8;
+  int hdutype      = 0;
+  int groupHDUnum  = 0;
+  int numkeys      = 0;
+  int keypos       = 0;
+  int startSearch  = 0;
+  int newPosition  = 0;
+
+  long nmembers    = 0;
+  long tfields     = 0;
+  long newTfields  = 0;
+
+  char keyword[FLEN_KEYWORD];
+  char keyvalue[FLEN_VALUE];
+  char card[FLEN_CARD];
+  char comment[FLEN_CARD];
+  char *tkeyvalue;
+
+  char *includeList[] = {"*"};
+  char *excludeList[] = {"EXTNAME","EXTVER","GRPNAME","GRPID#","GRPLC#",
+			 "THEAP","TDIM#","T????#"};
+
+  fitsfile *mfptr = NULL;
+
+
+  if(*status != 0) return(*status);
+
+  do
+    {
+      /*
+	create a new grouping table in the FITS file pointed to by outptr
+      */
+
+      *status = fits_get_num_members(infptr,&nmembers,status);
+
+      *status = fits_read_key_str(infptr,"GRPNAME",keyvalue,card,status);
+
+      if(*status == KEY_NO_EXIST)
+	{
+	  keyvalue[0] = 0;
+	  *status     = 0;
+	}
+      prepare_keyvalue(keyvalue);
+
+      *status = fits_create_group(outfptr,keyvalue,GT_ID_ALL_URI,status);
+     
+      /* save the new grouping table's HDU position for future use */
+
+      fits_get_hdu_num(outfptr,&groupHDUnum);
+
+      /* update the HDUtracker struct with the grouping table's new position */
+      
+      *status = fftsud(infptr,HDU,groupHDUnum,NULL);
+
+      /*
+	Now populate the copied grouping table depending upon the 
+	copy option parameter value
+      */
+
+      switch(cpopt)
+	{
+
+	  /*
+	    for the "copy grouping table only" option we only have to
+	    add the members of the original grouping table to the new
+	    grouping table
+	  */
+
+	case OPT_GCP_GPT:
+
+	  for(i = 1; i <= nmembers && *status == 0; ++i)
+	    {
+	      *status = fits_open_member(infptr,i,&mfptr,status);
+	      *status = fits_add_group_member(outfptr,mfptr,0,status);
+
+	      fits_close_file(mfptr,status);
+	      mfptr = NULL;
+	    }
+
+	  break;
+
+	case OPT_GCP_ALL:
+      
+	  /*
+	    for the "copy the entire group" option
+ 	  */
+
+	  /* loop over all the grouping table members */
+
+	  for(i = 1; i <= nmembers && *status == 0; ++i)
+	    {
+	      /* open the ith member */
+
+	      *status = fits_open_member(infptr,i,&mfptr,status);
+
+	      if(*status != 0) continue;
+
+	      /* add it to the HDUtracker struct */
+
+	      *status = fftsad(mfptr,HDU,&newPosition,NULL);
+
+	      /* if already copied then just add the member to the group */
+
+	      if(*status == HDU_ALREADY_TRACKED)
+		{
+		  *status = 0;
+		  *status = fits_add_group_member(outfptr,NULL,newPosition,
+						  status);
+		  fits_close_file(mfptr,status);
+                  mfptr = NULL;
+		  continue;
+		}
+	      else if(*status != 0) continue;
+
+	      /* see if the member is a grouping table */
+
+	      *status = fits_read_key_str(mfptr,"EXTNAME",keyvalue,card,
+					  status);
+
+	      if(*status == KEY_NO_EXIST)
+		{
+		  keyvalue[0] = 0;
+		  *status     = 0;
+		}
+	      prepare_keyvalue(keyvalue);
+
+	      /*
+		if the member is a grouping table then copy it and all of
+		its members using ffgtcpr(), else copy it using
+		fits_copy_member(); the outptr will point to the newly
+		copied member upon return from both functions
+	      */
+
+	      if(strcasecmp(keyvalue,"GROUPING") == 0)
+		*status = ffgtcpr(mfptr,outfptr,OPT_GCP_ALL,HDU,status);
+	      else
+		*status = fits_copy_member(infptr,outfptr,i,OPT_MCP_NADD,
+					   status);
+
+	      /* retrieve the position of the newly copied member */
+
+	      fits_get_hdu_num(outfptr,&newPosition);
+
+	      /* update the HDUtracker struct with member's new position */
+	      
+	      if(strcasecmp(keyvalue,"GROUPING") != 0)
+		*status = fftsud(mfptr,HDU,newPosition,NULL);
+
+	      /* move the outfptr back to the copied grouping table HDU */
+
+	      *status = fits_movabs_hdu(outfptr,groupHDUnum,&hdutype,status);
+
+	      /* add the copied member HDU to the copied grouping table */
+
+	      *status = fits_add_group_member(outfptr,NULL,newPosition,status);
+
+	      /* close the mfptr pointer */
+
+	      fits_close_file(mfptr,status);
+	      mfptr = NULL;
+	    }
+
+	  break;
+
+	default:
+	  
+	  *status = BAD_OPTION;
+	  ffpmsg("Invalid value specified for cmopt parameter (ffgtcpr)");
+	  break;
+	}
+
+      if(*status != 0) continue; 
+
+      /* 
+	 reposition the outfptr to the grouping table so that the grouping
+	 table is the CHDU upon return to the calling function
+      */
+
+      fits_movabs_hdu(outfptr,groupHDUnum,&hdutype,status);
+
+      /*
+	 copy all auxiliary keyword records from the original grouping table
+	 to the new grouping table; they are copied in their original order
+	 and inserted just before the TTYPE1 keyword record
+      */
+
+      *status = fits_read_card(outfptr,"TTYPE1",card,status);
+      *status = fits_get_hdrpos(outfptr,&numkeys,&keypos,status);
+      --keypos;
+
+      startSearch = 8;
+
+      while(*status == 0)
+	{
+	  ffgrec(infptr,startSearch,card,status);
+
+	  *status = fits_find_nextkey(infptr,includeList,1,excludeList,
+				      nexclude,card,status);
+
+	  *status = fits_get_hdrpos(infptr,&numkeys,&startSearch,status);
+
+	  --startSearch;
+	  /* SPR 1738 */
+	  if (strncmp(card,"GRPLC",5)) {
+	    /* Not going to be a long string so we're ok */
+	    *status = fits_insert_record(outfptr,keypos,card,status);
+	  } else {
+	    /* We could have a long string */
+	    *status = fits_read_record(infptr,startSearch,card,status);
+	    card[9] = '\0';
+	    *status = fits_read_key_longstr(infptr,card,&tkeyvalue,comment,
+					    status);
+	    if (0 == *status) {
+	      fits_insert_key_longstr(outfptr,card,tkeyvalue,comment,status);
+	      fits_write_key_longwarn(outfptr,status);
+	      free(tkeyvalue);
+	    }
+	  }
+	  
+	  ++keypos;
+	}
+      
+	  
+      if(*status == KEY_NO_EXIST) 
+	*status = 0;
+      else if(*status != 0) continue;
+
+      /*
+	 search all the columns of the original grouping table and copy
+	 those to the new grouping table that were not part of the grouping
+	 convention. Note that is legal to have additional columns in a
+	 grouping table. Also note that the order of the columns may
+	 not be the same in the original and copied grouping table.
+      */
+
+      /* retrieve the number of columns in the original and new group tables */
+
+      *status = fits_read_key_lng(infptr,"TFIELDS",&tfields,card,status);
+      *status = fits_read_key_lng(outfptr,"TFIELDS",&newTfields,card,status);
+
+      for(i = 1; i <= tfields; ++i)
+	{
+	  sprintf(keyword,"TTYPE%d",i);
+	  *status = fits_read_key_str(infptr,keyword,keyvalue,card,status);
+	  
+	  if(*status == KEY_NO_EXIST)
+	    {
+	      *status = 0;
+              keyvalue[0] = 0;
+	    }
+	  prepare_keyvalue(keyvalue);
+
+	  if(strcasecmp(keyvalue,"MEMBER_XTENSION") != 0 &&
+	     strcasecmp(keyvalue,"MEMBER_NAME")     != 0 &&
+	     strcasecmp(keyvalue,"MEMBER_VERSION")  != 0 &&
+	     strcasecmp(keyvalue,"MEMBER_POSITION") != 0 &&
+	     strcasecmp(keyvalue,"MEMBER_LOCATION") != 0 &&
+	     strcasecmp(keyvalue,"MEMBER_URI_TYPE") != 0   )
+	    {
+ 
+	      /* SPR 3956, add at the end of the table */
+	      *status = fits_copy_col(infptr,outfptr,i,newTfields+1,1,status);
+	      ++newTfields;
+	    }
+	}
+
+    }while(0);
+
+  if(mfptr != NULL) 
+    {
+      fits_close_file(mfptr,status);
+    }
+
+  return(*status);
+}
+
+/*--------------------------------------------------------------------------
+                HDUtracker struct manipulation functions
+  --------------------------------------------------------------------------*/
+int fftsad(fitsfile   *mfptr,       /* pointer to an member HDU             */
+	   HDUtracker *HDU,         /* pointer to an HDU tracker struct     */
+	   int        *newPosition, /* new HDU position of the member HDU   */
+	   char       *newFileName) /* file containing member HDU           */
+
+/*
+  add an HDU to the HDUtracker struct pointed to by HDU. The HDU is only 
+  added if it does not already reside in the HDUtracker. If it already
+  resides in the HDUtracker then the new HDU postion and file name are
+  returned in  newPosition and newFileName (if != NULL)
+*/
+
+{
+  int i;
+  int hdunum;
+  int status = 0;
+
+  char filename1[FLEN_FILENAME];
+  char filename2[FLEN_FILENAME];
+
+  do
+    {
+      /* retrieve the HDU's position within the FITS file */
+
+      fits_get_hdu_num(mfptr,&hdunum);
+      
+      /* retrieve the HDU's file name */
+      
+      status = fits_file_name(mfptr,filename1,&status);
+      
+      /* parse the file name and construct the "standard" URL for it */
+      
+      status = ffrtnm(filename1,filename2,&status);
+      
+      /* 
+	 examine all the existing HDUs in the HDUtracker an see if this HDU
+	 has already been registered
+      */
+
+      for(i = 0; 
+       i < HDU->nHDU &&  !(HDU->position[i] == hdunum 
+			   && strcmp(HDU->filename[i],filename2) == 0);
+	  ++i);
+
+      if(i != HDU->nHDU) 
+	{
+	  status = HDU_ALREADY_TRACKED;
+	  if(newPosition != NULL) *newPosition = HDU->newPosition[i];
+	  if(newFileName != NULL) strcpy(newFileName,HDU->newFilename[i]);
+	  continue;
+	}
+
+      if(HDU->nHDU == MAX_HDU_TRACKER) 
+	{
+	  status = TOO_MANY_HDUS_TRACKED;
+	  continue;
+	}
+
+      HDU->filename[i] = (char*) malloc(FLEN_FILENAME * sizeof(char));
+
+      if(HDU->filename[i] == NULL)
+	{
+	  status = MEMORY_ALLOCATION;
+	  continue;
+	}
+
+      HDU->newFilename[i] = (char*) malloc(FLEN_FILENAME * sizeof(char));
+
+      if(HDU->newFilename[i] == NULL)
+	{
+	  status = MEMORY_ALLOCATION;
+	  free(HDU->filename[i]);
+	  continue;
+	}
+
+      HDU->position[i]    = hdunum;
+      HDU->newPosition[i] = hdunum;
+
+      strcpy(HDU->filename[i],filename2);
+      strcpy(HDU->newFilename[i],filename2);
+ 
+       ++(HDU->nHDU);
+
+    }while(0);
+
+  return(status);
+}
+/*--------------------------------------------------------------------------*/
+int fftsud(fitsfile   *mfptr,       /* pointer to an member HDU             */
+	   HDUtracker *HDU,         /* pointer to an HDU tracker struct     */
+	   int         newPosition, /* new HDU position of the member HDU   */
+	   char       *newFileName) /* file containing member HDU           */
+
+/*
+  update the HDU information in the HDUtracker struct pointed to by HDU. The 
+  HDU to update is pointed to by mfptr. If non-zero, the value of newPosition
+  is used to update the HDU->newPosition[] value for the mfptr, and if
+  non-NULL the newFileName value is used to update the HDU->newFilename[]
+  value for mfptr.
+*/
+
+{
+  int i;
+  int hdunum;
+  int status = 0;
+
+  char filename1[FLEN_FILENAME];
+  char filename2[FLEN_FILENAME];
+
+
+  /* retrieve the HDU's position within the FITS file */
+  
+  fits_get_hdu_num(mfptr,&hdunum);
+  
+  /* retrieve the HDU's file name */
+  
+  status = fits_file_name(mfptr,filename1,&status);
+  
+  /* parse the file name and construct the "standard" URL for it */
+      
+  status = ffrtnm(filename1,filename2,&status);
+
+  /* 
+     examine all the existing HDUs in the HDUtracker an see if this HDU
+     has already been registered
+  */
+
+  for(i = 0; i < HDU->nHDU && 
+      !(HDU->position[i] == hdunum && strcmp(HDU->filename[i],filename2) == 0);
+      ++i);
+
+  /* if previously registered then change newPosition and newFileName */
+
+  if(i != HDU->nHDU) 
+    {
+      if(newPosition  != 0) HDU->newPosition[i] = newPosition;
+      if(newFileName  != NULL) 
+	{
+	  strcpy(HDU->newFilename[i],newFileName);
+	}
+    }
+  else
+    status = MEMBER_NOT_FOUND;
+ 
+  return(status);
+}
+
+/*---------------------------------------------------------------------------*/
+
+void prepare_keyvalue(char *keyvalue) /* string containing keyword value     */
+
+/*
+  strip off all single quote characters "'" and blank spaces from a keyword
+  value retrieved via fits_read_key*() routines
+
+  this is necessary so that a standard comparision of keyword values may
+  be made
+*/
+
+{
+
+  int i;
+  int length;
+
+  /*
+    strip off any leading or trailing single quotes (`) and (') from
+    the keyword value
+  */
+
+  length = strlen(keyvalue) - 1;
+
+  if(keyvalue[0] == '\'' && keyvalue[length] == '\'')
+    {
+      for(i = 0; i < length - 1; ++i) keyvalue[i] = keyvalue[i+1];
+      keyvalue[length-1] = 0;
+    }
+  
+  /*
+    strip off any trailing blanks from the keyword value; note that if the
+    keyvalue consists of nothing but blanks then no blanks are stripped
+  */
+
+  length = strlen(keyvalue) - 1;
+
+  for(i = 0; i < length && keyvalue[i] == ' '; ++i);
+
+  if(i != length)
+    {
+      for(i = length; i >= 0 && keyvalue[i] == ' '; --i) keyvalue[i] = '\0';
+    }
+}
+
+/*---------------------------------------------------------------------------
+        Host dependent directory path to/from URL functions
+  --------------------------------------------------------------------------*/
+int fits_path2url(char *inpath,  /* input file path string                  */
+		  char *outpath, /* output file path string                 */
+		  int  *status)
+  /*
+     convert a file path into its Unix-style equivelent for URL 
+     purposes. Note that this process is platform dependent. This
+     function supports Unix, MSDOS/WIN32, VMS and Macintosh platforms. 
+     The plaform dependant code is conditionally compiled depending upon
+     the setting of the appropriate C preprocessor macros.
+   */
+{
+  char buff[FLEN_FILENAME];
+
+#if defined(WINNT) || defined(__WINNT__)
+
+  /*
+    Microsoft Windows NT case. We assume input file paths of the form:
+
+    //disk/path/filename
+
+     All path segments may be null, so that a single file name is the
+     simplist case.
+
+     The leading "//" becomes a single "/" if present. If no "//" is present,
+     then make sure the resulting URL path is relative, i.e., does not
+     begin with a "/". In other words, the only way that an absolute URL
+     file path may be generated is if the drive specification is given.
+  */
+
+  if(*status > 0) return(*status);
+
+  if(inpath[0] == '/')
+    {
+      strcpy(buff,inpath+1);
+    }
+  else
+    {
+      strcpy(buff,inpath);
+    }
+
+#elif defined(MSDOS) || defined(__WIN32__) || defined(WIN32)
+
+  /*
+     MSDOS or Microsoft windows/NT case. The assumed form of the
+     input path is:
+
+     disk:\path\filename
+
+     All path segments may be null, so that a single file name is the
+     simplist case.
+
+     All back-slashes '\' become slashes '/'; if the path starts with a
+     string of the form "X:" then it is replaced with "/X/"
+  */
+
+  int i,j,k;
+  int size;
+  if(*status > 0) return(*status);
+
+  for(i = 0, j = 0, size = strlen(inpath), buff[0] = 0; 
+                                           i < size; j = strlen(buff))
+    {
+      switch(inpath[i])
+	{
+
+	case ':':
+
+	  /*
+	     must be a disk desiginator; add a slash '/' at the start of
+	     outpath to designate that the path is absolute, then change
+	     the colon ':' to a slash '/'
+	   */
+
+	  for(k = j; k >= 0; --k) buff[k+1] = buff[k];
+	  buff[0] = '/';
+	  strcat(buff,"/");
+	  ++i;
+	  
+	  break;
+
+	case '\\':
+
+	  /* just replace the '\' with a '/' IF its not the first character */
+
+	  if(i != 0 && buff[(j == 0 ? 0 : j-1)] != '/')
+	    {
+	      buff[j] = '/';
+	      buff[j+1] = 0;
+	    }
+
+	  ++i;
+
+	  break;
+
+	default:
+
+	  /* copy the character from inpath to buff as is */
+
+	  buff[j]   = inpath[i];
+	  buff[j+1] = 0;
+	  ++i;
+
+	  break;
+	}
+    }
+
+#elif defined(VMS) || defined(vms) || defined(__vms)
+
+  /*
+     VMS case. Assumed format of the input path is:
+
+     node::disk:[path]filename.ext;version
+
+     Any part of the file path may be missing, so that in the simplist
+     case a single file name/extension is given.
+
+     all brackets "[", "]" and dots "." become "/"; dashes "-" become "..", 
+     all single colons ":" become ":/", all double colons "::" become
+     "FILE://"
+   */
+
+  int i,j,k;
+  int done;
+  int size;
+
+  if(*status > 0) return(*status);
+     
+  /* see if inpath contains a directory specification */
+
+  if(strchr(inpath,']') == NULL) 
+    done = 1;
+  else
+    done = 0;
+
+  for(i = 0, j = 0, size = strlen(inpath), buff[0] = 0; 
+                           i < size && j < FLEN_FILENAME - 8; j = strlen(buff))
+    {
+      switch(inpath[i])
+	{
+
+	case ':':
+
+	  /*
+	     must be a logical/symbol separator or (in the case of a double
+	     colon "::") machine node separator
+	   */
+
+	  if(inpath[i+1] == ':')
+	    {
+	      /* insert a "FILE://" at the start of buff ==> machine given */
+
+	      for(k = j; k >= 0; --k) buff[k+7] = buff[k];
+	      strncpy(buff,"FILE://",7);
+	      i += 2;
+	    }
+	  else if(strstr(buff,"FILE://") == NULL)
+	    {
+	      /* insert a "/" at the start of buff ==> absolute path */
+
+	      for(k = j; k >= 0; --k) buff[k+1] = buff[k];
+	      buff[0] = '/';
+	      ++i;
+	    }
+	  else
+	    ++i;
+
+	  /* a colon always ==> path separator */
+
+	  strcat(buff,"/");
+
+	  break;
+  
+	case ']':
+
+	  /* end of directory spec, file name spec begins after this */
+
+	  done = 1;
+
+	  buff[j]   = '/';
+	  buff[j+1] = 0;
+	  ++i;
+
+	  break;
+
+	case '[':
+
+	  /* 
+	     begin directory specification; add a '/' only if the last char 
+	     is not '/' 
+	  */
+
+	  if(i != 0 && buff[(j == 0 ? 0 : j-1)] != '/')
+	    {
+	      buff[j]   = '/';
+	      buff[j+1] = 0;
+	    }
+
+	  ++i;
+
+	  break;
+
+	case '.':
+
+	  /* 
+	     directory segment separator or file name/extension separator;
+	     we decide which by looking at the value of done
+	  */
+
+	  if(!done)
+	    {
+	    /* must be a directory segment separator */
+	      if(inpath[i-1] == '[')
+		{
+		  strcat(buff,"./");
+		  ++j;
+		}
+	      else
+		buff[j] = '/';
+	    }
+	  else
+	    /* must be a filename/extension separator */
+	    buff[j] = '.';
+
+	  buff[j+1] = 0;
+
+	  ++i;
+
+	  break;
+
+	case '-':
+
+	  /* 
+	     a dash is the same as ".." in Unix speak, but lets make sure
+	     that its not part of the file name first!
+	   */
+
+	  if(!done)
+	    /* must be part of the directory path specification */
+	    strcat(buff,"..");
+	  else
+	    {
+	      /* the dash is part of the filename, so just copy it as is */
+	      buff[j] = '-';
+	      buff[j+1] = 0;
+	    }
+
+	  ++i;
+
+	  break;
+
+	default:
+
+	  /* nothing special, just copy the character as is */
+
+	  buff[j]   = inpath[i];
+	  buff[j+1] = 0;
+
+	  ++i;
+
+	  break;
+
+	}
+    }
+
+  if(j > FLEN_FILENAME - 8)
+    {
+      *status = URL_PARSE_ERROR;
+      ffpmsg("resulting path to URL conversion too big (fits_path2url)");
+    }
+
+#elif defined(macintosh)
+
+  /*
+     MacOS case. The assumed form of the input path is:
+
+     disk:path:filename
+
+     It is assumed that all paths are absolute with disk and path specified,
+     unless no colons ":" are supplied with the string ==> a single file name
+     only. All colons ":" become slashes "/", and if one or more colon is 
+     encountered then the path is specified as absolute.
+  */
+
+  int i,j,k;
+  int firstColon;
+  int size;
+
+  if(*status > 0) return(*status);
+
+  for(i = 0, j = 0, firstColon = 1, size = strlen(inpath), buff[0] = 0; 
+                                                   i < size; j = strlen(buff))
+    {
+      switch(inpath[i])
+	{
+
+	case ':':
+
+	  /*
+	     colons imply path separators. If its the first colon encountered
+	     then assume that its the disk designator and add a slash to the
+	     beginning of the buff string
+	   */
+	  
+	  if(firstColon)
+	    {
+	      firstColon = 0;
+
+	      for(k = j; k >= 0; --k) buff[k+1] = buff[k];
+	      buff[0] = '/';
+	    }
+
+	  /* all colons become slashes */
+
+	  strcat(buff,"/");
+
+	  ++i;
+	  
+	  break;
+
+	default:
+
+	  /* copy the character from inpath to buff as is */
+
+	  buff[j]   = inpath[i];
+	  buff[j+1] = 0;
+
+	  ++i;
+
+	  break;
+	}
+    }
+
+#else 
+
+  /*
+     Default Unix case.
+
+     Nothing special to do here except to remove the double or more // and 
+     replace them with single /
+   */
+
+  int ii = 0;
+  int jj = 0;
+
+  if(*status > 0) return(*status);
+
+  while (inpath[ii]) {
+      if (inpath[ii] == '/' && inpath[ii+1] == '/') {
+	  /* do nothing */
+      } else {
+	  buff[jj] = inpath[ii];
+	  jj++;
+      }
+      ii++;
+  }
+  buff[jj] = '\0';
+  /* printf("buff is %s\ninpath is %s\n",buff,inpath); */
+  /* strcpy(buff,inpath); */
+
+#endif
+
+  /*
+    encode all "unsafe" and "reserved" URL characters
+  */
+
+  *status = fits_encode_url(buff,outpath,status);
+
+  return(*status);
+}
+
+/*---------------------------------------------------------------------------*/
+int fits_url2path(char *inpath,  /* input file path string  */
+		  char *outpath, /* output file path string */
+		  int  *status)
+  /*
+     convert a Unix-style URL into a platform dependent directory path. 
+     Note that this process is platform dependent. This
+     function supports Unix, MSDOS/WIN32, VMS and Macintosh platforms. Each
+     platform dependent code segment is conditionally compiled depending 
+     upon the setting of the appropriate C preprocesser macros.
+   */
+{
+  char buff[FLEN_FILENAME];
+  int absolute;
+
+#if defined(MSDOS) || defined(__WIN32__) || defined(WIN32)
+  char *tmpStr;
+#elif defined(VMS) || defined(vms) || defined(__vms)
+  int i;
+  char *tmpStr;
+#elif defined(macintosh)
+  char *tmpStr;
+#endif
+
+  if(*status != 0) return(*status);
+
+  /*
+    make a copy of the inpath so that we can manipulate it
+  */
+
+  strcpy(buff,inpath);
+
+  /*
+    convert any encoded characters to their unencoded values
+  */
+
+  *status = fits_unencode_url(inpath,buff,status);
+
+  /*
+    see if the URL is given as absolute w.r.t. the "local" file system
+  */
+
+  if(buff[0] == '/') 
+    absolute = 1;
+  else
+    absolute = 0;
+
+#if defined(WINNT) || defined(__WINNT__)
+
+  /*
+    Microsoft Windows NT case. We create output paths of the form
+
+    //disk/path/filename
+
+     All path segments but the last may be null, so that a single file name 
+     is the simplist case.     
+  */
+
+  if(absolute)
+    {
+      strcpy(outpath,"/");
+      strcat(outpath,buff);
+    }
+  else
+    {
+      strcpy(outpath,buff);
+    }
+
+#elif defined(MSDOS) || defined(__WIN32__) || defined(WIN32)
+
+  /*
+     MSDOS or Microsoft windows/NT case. The output path will be of the
+     form
+
+     disk:\path\filename
+
+     All path segments but the last may be null, so that a single file name 
+     is the simplist case.
+  */
+
+  /*
+    separate the URL into tokens at each slash '/' and process until
+    all tokens have been examined
+  */
+
+  for(tmpStr = strtok(buff,"/"), outpath[0] = 0;
+                                 tmpStr != NULL; tmpStr = strtok(NULL,"/"))
+    {
+      strcat(outpath,tmpStr);
+
+      /* 
+	 if the absolute flag is set then process the token as a disk 
+	 specification; else just process it as a directory path or filename
+      */
+
+      if(absolute)
+	{
+	  strcat(outpath,":\\");
+	  absolute = 0;
+	}
+      else
+	strcat(outpath,"\\");
+    }
+
+  /* remove the last "\" from the outpath, it does not belong there */
+
+  outpath[strlen(outpath)-1] = 0;
+
+#elif defined(VMS) || defined(vms) || defined(__vms)
+
+  /*
+     VMS case. The output path will be of the form:
+
+     node::disk:[path]filename.ext;version
+
+     Any part of the file path may be missing execpt filename.ext, so that in 
+     the simplist case a single file name/extension is given.
+
+     if the path is specified as relative starting with "./" then the first
+     part of the VMS path is "[.". If the path is relative and does not start
+     with "./" (e.g., "a/b/c") then the VMS path is constructed as
+     "[a.b.c]"
+   */
+     
+  /*
+    separate the URL into tokens at each slash '/' and process until
+    all tokens have been examined
+  */
+
+  for(tmpStr = strtok(buff,"/"), outpath[0] = 0; 
+                                 tmpStr != NULL; tmpStr = strtok(NULL,"/"))
+    {
+
+      if(strcasecmp(tmpStr,"FILE:") == 0)
+	{
+	  /* the next token should contain the DECnet machine name */
+
+	  tmpStr = strtok(NULL,"/");
+	  if(tmpStr == NULL) continue;
+
+	  strcat(outpath,tmpStr);
+	  strcat(outpath,"::");
+
+	  /* set the absolute flag to true for the next token */
+	  absolute = 1;
+	}
+
+      else if(strcmp(tmpStr,"..") == 0)
+	{
+	  /* replace all Unix-like ".." with VMS "-" */
+
+	  if(strlen(outpath) == 0) strcat(outpath,"[");
+	  strcat(outpath,"-.");
+	}
+
+      else if(strcmp(tmpStr,".") == 0 && strlen(outpath) == 0)
+	{
+	  /*
+	    must indicate a relative path specifier
+	  */
+
+	  strcat(outpath,"[.");
+	}
+  
+      else if(strchr(tmpStr,'.') != NULL)
+	{
+	  /* 
+	     must be up to the file name; turn the last "." path separator
+	     into a "]" and then add the file name to the outpath
+	  */
+	  
+	  i = strlen(outpath);
+	  if(i > 0 && outpath[i-1] == '.') outpath[i-1] = ']';
+
+	  strcat(outpath,tmpStr);
+	}
+
+      else
+	{
+	  /*
+	    process the token as a a directory path segement
+	  */
+
+	  if(absolute)
+	    {
+	      /* treat the token as a disk specifier */
+	      absolute = 0;
+	      strcat(outpath,tmpStr);
+	      strcat(outpath,":[");
+	    }
+	  else if(strlen(outpath) == 0)
+	    {
+	      /* treat the token as the first directory path specifier */
+	      strcat(outpath,"[");
+	      strcat(outpath,tmpStr);
+	      strcat(outpath,".");
+	    }
+	  else
+	    {
+	      /* treat the token as an imtermediate path specifier */
+	      strcat(outpath,tmpStr);
+	      strcat(outpath,".");
+	    }
+	}
+    }
+
+#elif defined(macintosh)
+
+  /*
+     MacOS case. The output path will be of the form
+
+     disk:path:filename
+
+     All path segments but the last may be null, so that a single file name 
+     is the simplist case.
+  */
+
+  /*
+    separate the URL into tokens at each slash '/' and process until
+    all tokens have been examined
+  */
+
+  for(tmpStr = strtok(buff,"/"), outpath[0] = 0;
+                                 tmpStr != NULL; tmpStr = strtok(NULL,"/"))
+    {
+      strcat(outpath,tmpStr);
+      strcat(outpath,":");
+    }
+
+  /* remove the last ":" from the outpath, it does not belong there */
+
+  outpath[strlen(outpath)-1] = 0;
+
+#else
+
+  /*
+     Default Unix case.
+
+     Nothing special to do here
+   */
+
+  strcpy(outpath,buff);
+
+#endif
+
+  return(*status);
+}
+
+/****************************************************************************/
+int fits_get_cwd(char *cwd,  /* IO current working directory string */
+		 int  *status)
+  /*
+     retrieve the string containing the current working directory absolute
+     path in Unix-like URL standard notation. It is assumed that the CWD
+     string has a size of at least FLEN_FILENAME.
+
+     Note that this process is platform dependent. This
+     function supports Unix, MSDOS/WIN32, VMS and Macintosh platforms. Each
+     platform dependent code segment is conditionally compiled depending 
+     upon the setting of the appropriate C preprocesser macros.
+   */
+{
+
+  char buff[FLEN_FILENAME];
+
+
+  if(*status != 0) return(*status);
+
+#if defined(macintosh)
+
+  /*
+     MacOS case. Currently unknown !!!!
+  */
+
+  *buff = 0;
+
+#else
+  /*
+    Good old getcwd() seems to work with all other platforms
+  */
+
+  getcwd(buff,FLEN_FILENAME);
+
+#endif
+
+  /*
+    convert the cwd string to a URL standard path string
+  */
+
+  fits_path2url(buff,cwd,status);
+
+  return(*status);
+}
+
+/*---------------------------------------------------------------------------*/
+int  fits_get_url(fitsfile *fptr,       /* I ptr to FITS file to evaluate    */
+		  char     *realURL,    /* O URL of real FITS file           */
+		  char     *startURL,   /* O URL of starting FITS file       */
+		  char     *realAccess, /* O true access method of FITS file */
+		  char     *startAccess,/* O "official" access of FITS file  */
+		  int      *iostate,    /* O can this file be modified?      */
+		  int      *status)
+/*
+  For grouping convention purposes, determine the URL of the FITS file
+  associated with the fitsfile pointer fptr. The true access type (file://,
+  mem://, shmem://, root://), starting "official" access type, and iostate 
+  (0 ==> readonly, 1 ==> readwrite) are also returned.
+
+  It is assumed that the url string has enough room to hold the resulting
+  URL, and the the accessType string has enough room to hold the access type.
+*/
+{
+  int i;
+  int tmpIOstate = 0;
+
+  char infile[FLEN_FILENAME];
+  char outfile[FLEN_FILENAME];
+  char tmpStr1[FLEN_FILENAME];
+  char tmpStr2[FLEN_FILENAME];
+  char tmpStr3[FLEN_FILENAME];
+  char tmpStr4[FLEN_FILENAME];
+  char *tmpPtr;
+
+
+  if(*status != 0) return(*status);
+
+  do
+    {
+      /* 
+	 retrieve the member HDU's file name as opened by ffopen() 
+	 and parse it into its constitutent pieces; get the currently
+	 active driver token too
+       */
+	  
+      *tmpStr1 = *tmpStr2 = *tmpStr3 = *tmpStr4 = 0;
+
+      *status = fits_file_name(fptr,tmpStr1,status);
+
+      *status = ffiurl(tmpStr1,NULL,infile,outfile,NULL,tmpStr2,tmpStr3,
+		       tmpStr4,status);
+
+      if((*tmpStr2) || (*tmpStr3) || (*tmpStr4)) tmpIOstate = -1;
+ 
+      *status = ffurlt(fptr,tmpStr3,status);
+
+      strcpy(tmpStr4,tmpStr3);
+
+      *status = ffrtnm(tmpStr1,tmpStr2,status);
+      strcpy(tmpStr1,tmpStr2);
+
+      /*
+	for grouping convention purposes (only) determine the URL of the
+	actual FITS file being used for the given fptr, its true access 
+	type (file://, mem://, shmem://, root://) and its iostate (0 ==>
+	read only, 1 ==> readwrite)
+      */
+
+      /*
+	The first set of access types are "simple" in that they do not
+	use any redirection to temporary memory or outfiles
+       */
+
+      /* standard disk file driver is in use */
+      
+      if(strcasecmp(tmpStr3,"file://")              == 0)         
+	{
+	  tmpIOstate = 1;
+	  
+	  if(strlen(outfile)) strcpy(tmpStr1,outfile);
+	  else *tmpStr2 = 0;
+
+	  /*
+	    make sure no FILE:// specifier is given in the tmpStr1
+	    or tmpStr2 strings; the convention calls for local files
+	    to have no access specification
+	  */
+
+	  if((tmpPtr = strstr(tmpStr1,"://")) != NULL)
+	    {
+	      strcpy(infile,tmpPtr+3);
+	      strcpy(tmpStr1,infile);
+	    }
+
+	  if((tmpPtr = strstr(tmpStr2,"://")) != NULL)
+	    {
+	      strcpy(infile,tmpPtr+3);
+	      strcpy(tmpStr2,infile);
+	    }
+	}
+
+      /* file stored in conventional memory */
+	  
+      else if(strcasecmp(tmpStr3,"mem://")          == 0)          
+	{
+	  if(tmpIOstate < 0)
+	    {
+	      /* file is a temp mem file only */
+	      ffpmsg("cannot make URL from temp MEM:// file (fits_get_url)");
+	      *status = URL_PARSE_ERROR;
+	    }
+	  else
+	    {
+	      /* file is a "perminate" mem file for this process */
+	      tmpIOstate = 1;
+	      *tmpStr2 = 0;
+	    }
+	}
+
+      /* file stored in conventional memory */
+ 
+     else if(strcasecmp(tmpStr3,"memkeep://")      == 0)      
+	{
+	  strcpy(tmpStr3,"mem://");
+	  *tmpStr4 = 0;
+	  *tmpStr2 = 0;
+	  tmpIOstate = 1;
+	}
+
+      /* file residing in shared memory */
+
+      else if(strcasecmp(tmpStr3,"shmem://")        == 0)        
+	{
+	  *tmpStr4   = 0;
+	  *tmpStr2   = 0;
+	  tmpIOstate = 1;
+	}
+      
+      /* file accessed via the ROOT network protocol */
+
+      else if(strcasecmp(tmpStr3,"root://")         == 0)         
+	{
+	  *tmpStr4   = 0;
+	  *tmpStr2   = 0;
+	  tmpIOstate = 1;
+	}
+  
+      /*
+	the next set of access types redirect the contents of the original
+	file to an special outfile because the original could not be
+	directly modified (i.e., resides on the network, was compressed).
+	In these cases the URL string takes on the value of the OUTFILE,
+	the access type becomes file://, and the iostate is set to 1 (can
+	read/write to the file).
+      */
+
+      /* compressed file uncompressed and written to disk */
+
+      else if(strcasecmp(tmpStr3,"compressfile://") == 0) 
+	{
+	  strcpy(tmpStr1,outfile);
+	  strcpy(tmpStr2,infile);
+	  strcpy(tmpStr3,"file://");
+	  strcpy(tmpStr4,"file://");
+	  tmpIOstate = 1;
+	}
+
+      /* HTTP accessed file written locally to disk */
+
+      else if(strcasecmp(tmpStr3,"httpfile://")     == 0)     
+	{
+	  strcpy(tmpStr1,outfile);
+	  strcpy(tmpStr3,"file://");
+	  strcpy(tmpStr4,"http://";);
+	  tmpIOstate = 1;
+	}
+      
+      /* FTP accessd file written locally to disk */
+
+      else if(strcasecmp(tmpStr3,"ftpfile://")      == 0)      
+	{
+	  strcpy(tmpStr1,outfile);
+	  strcpy(tmpStr3,"file://");
+	  strcpy(tmpStr4,"ftp://";);
+	  tmpIOstate = 1;
+	}
+      
+      /* file from STDIN written to disk */
+
+      else if(strcasecmp(tmpStr3,"stdinfile://")    == 0)    
+	{
+	  strcpy(tmpStr1,outfile);
+	  strcpy(tmpStr3,"file://");
+	  strcpy(tmpStr4,"stdin://");
+	  tmpIOstate = 1;
+	}
+
+      /* 
+	 the following access types use memory resident files as temporary
+	 storage; they cannot be modified or be made group members for 
+	 grouping conventions purposes, but their original files can be.
+	 Thus, their tmpStr3s are reset to mem://, their iostate
+	 values are set to 0 (for no-modification), and their URL string
+	 values remain set to their original values
+       */
+
+      /* compressed disk file uncompressed into memory */
+
+      else if(strcasecmp(tmpStr3,"compress://")     == 0)     
+	{
+	  *tmpStr1 = 0;
+	  strcpy(tmpStr2,infile);
+	  strcpy(tmpStr3,"mem://");
+	  strcpy(tmpStr4,"file://");
+	  tmpIOstate = 0;
+	}
+      
+      /* HTTP accessed file transferred into memory */
+
+      else if(strcasecmp(tmpStr3,"http://";)         == 0)         
+	{
+	  *tmpStr1 = 0;
+	  strcpy(tmpStr3,"mem://");
+	  strcpy(tmpStr4,"http://";);
+	  tmpIOstate = 0;
+	}
+      
+      /* HTTP accessed compressed file transferred into memory */
+
+      else if(strcasecmp(tmpStr3,"httpcompress://") == 0) 
+	{
+	  *tmpStr1 = 0;
+	  strcpy(tmpStr3,"mem://");
+	  strcpy(tmpStr4,"http://";);
+	  tmpIOstate = 0;
+	}
+      
+      /* FTP accessed file transferred into memory */
+      
+      else if(strcasecmp(tmpStr3,"ftp://";)          == 0)          
+	{
+	  *tmpStr1 = 0;
+	  strcpy(tmpStr3,"mem://");
+	  strcpy(tmpStr4,"ftp://";);
+	  tmpIOstate = 0;
+	}
+      
+      /* FTP accessed compressed file transferred into memory */
+
+      else if(strcasecmp(tmpStr3,"ftpcompress://")  == 0)  
+	{
+	  *tmpStr1 = 0;
+	  strcpy(tmpStr3,"mem://");
+	  strcpy(tmpStr4,"ftp://";);
+	  tmpIOstate = 0;
+	}	
+      
+      /*
+	The last set of access types cannot be used to make a meaningful URL 
+	strings from; thus an error is generated
+       */
+
+      else if(strcasecmp(tmpStr3,"stdin://")        == 0)        
+	{
+	  *status = URL_PARSE_ERROR;
+	  ffpmsg("cannot make vaild URL from stdin:// (fits_get_url)");
+	  *tmpStr1 = *tmpStr2 = 0;
+	}
+
+      else if(strcasecmp(tmpStr3,"stdout://")       == 0)       
+	{
+	  *status = URL_PARSE_ERROR;
+	  ffpmsg("cannot make vaild URL from stdout:// (fits_get_url)");
+	  *tmpStr1 = *tmpStr2 = 0;
+	}
+
+      else if(strcasecmp(tmpStr3,"irafmem://")      == 0)      
+	{
+	  *status = URL_PARSE_ERROR;
+	  ffpmsg("cannot make vaild URL from irafmem:// (fits_get_url)");
+	  *tmpStr1 = *tmpStr2 = 0;
+	}
+
+      if(*status != 0) continue;
+
+      /*
+	 assign values to the calling parameters if they are non-NULL
+      */
+
+      if(realURL != NULL)
+	{
+	  if(strlen(tmpStr1) == 0)
+	    *realURL = 0;
+	  else
+	    {
+	      if((tmpPtr = strstr(tmpStr1,"://")) != NULL)
+		{
+		  tmpPtr += 3;
+		  i = (long)tmpPtr - (long)tmpStr1;
+		  strncpy(realURL,tmpStr1,i);
+		}
+	      else
+		{
+		  tmpPtr = tmpStr1;
+		  i = 0;
+		}
+
+	      *status = fits_path2url(tmpPtr,realURL+i,status);
+	    }
+	}
+
+      if(startURL != NULL)
+	{
+	  if(strlen(tmpStr2) == 0)
+	    *startURL = 0;
+	  else
+	    {
+	      if((tmpPtr = strstr(tmpStr2,"://")) != NULL)
+		{
+		  tmpPtr += 3;
+		  i = (long)tmpPtr - (long)tmpStr2;
+		  strncpy(startURL,tmpStr2,i);
+		}
+	      else
+		{
+		  tmpPtr = tmpStr2;
+		  i = 0;
+		}
+
+	      *status = fits_path2url(tmpPtr,startURL+i,status);
+	    }
+	}
+
+      if(realAccess  != NULL)  strcpy(realAccess,tmpStr3);
+      if(startAccess != NULL)  strcpy(startAccess,tmpStr4);
+      if(iostate     != NULL) *iostate = tmpIOstate;
+
+    }while(0);
+
+  return(*status);
+}
+
+/*--------------------------------------------------------------------------
+                           URL parse support functions
+  --------------------------------------------------------------------------*/
+
+/* simple push/pop/shift/unshift string stack for use by fits_clean_url */
+typedef char* grp_stack_data; /* type of data held by grp_stack */
+
+typedef struct grp_stack_item_struct {
+  grp_stack_data data; /* value of this stack item */
+  struct grp_stack_item_struct* next; /* next stack item */
+  struct grp_stack_item_struct* prev; /* previous stack item */
+} grp_stack_item;
+
+typedef struct grp_stack_struct {
+  size_t stack_size; /* number of items on stack */
+  grp_stack_item* top; /* top item */
+} grp_stack;
+
+static char* grp_stack_default = NULL; /* initial value for new instances
+                                          of grp_stack_data */
+
+/* the following functions implement the group string stack grp_stack */
+static void delete_grp_stack(grp_stack** mystack);
+static grp_stack_item* grp_stack_append(
+  grp_stack_item* last, grp_stack_data data
+);
+static grp_stack_data grp_stack_remove(grp_stack_item* last);
+static grp_stack* new_grp_stack(void);
+static grp_stack_data pop_grp_stack(grp_stack* mystack);
+static void push_grp_stack(grp_stack* mystack, grp_stack_data data);
+static grp_stack_data shift_grp_stack(grp_stack* mystack);
+/* static void unshift_grp_stack(grp_stack* mystack, grp_stack_data data); */
+
+int fits_clean_url(char *inURL,  /* I input URL string                      */
+		   char *outURL, /* O output URL string                     */
+		   int  *status)
+/*
+  clean the URL by eliminating any ".." or "." specifiers in the inURL
+  string, and write the output to the outURL string.
+
+  Note that this function must have a valid Unix-style URL as input; platform
+  dependent path strings are not allowed.
+ */
+{
+  grp_stack* mystack; /* stack to hold pieces of URL */
+  char* tmp;
+
+  if(*status) return *status;
+
+  mystack = new_grp_stack();
+  *outURL = 0;
+
+  do {
+    /* handle URL scheme and domain if they exist */
+    tmp = strstr(inURL, "://");
+    if(tmp) {
+      /* there is a URL scheme, so look for the end of the domain too */
+      tmp = strchr(tmp + 3, '/');
+      if(tmp) {
+        /* tmp is now the end of the domain, so
+         * copy URL scheme and domain as is, and terminate by hand */
+        size_t string_size = (size_t) (tmp - inURL);
+        strncpy(outURL, inURL, string_size);
+        outURL[string_size] = 0;
+
+        /* now advance the input pointer to just after the domain and go on */
+        inURL = tmp;
+      } else {
+        /* '/' was not found, which means there are no path-like
+         * portions, so copy whole inURL to outURL and we're done */
+        strcpy(outURL, inURL);
+        continue; /* while(0) */
+      }
+    }
+
+    /* explicitly copy a leading / (absolute path) */
+    if('/' == *inURL) strcat(outURL, "/");
+
+    /* now clean the remainder of the inURL. push URL segments onto
+     * stack, dealing with .. and . as we go */
+    tmp = strtok(inURL, "/"); /* finds first / */
+    while(tmp) {
+      if(!strcmp(tmp, "..")) {
+        /* discard previous URL segment, if there was one. if not,
+         * add the .. to the stack if this is *not* an absolute path
+         * (for absolute paths, leading .. has no effect, so skip it) */
+        if(0 < mystack->stack_size) pop_grp_stack(mystack);
+        else if('/' != *inURL) push_grp_stack(mystack, tmp);
+      } else {
+        /* always just skip ., but otherwise add segment to stack */
+        if(strcmp(tmp, ".")) push_grp_stack(mystack, tmp);
+      }
+      tmp = strtok(NULL, "/"); /* get the next segment */
+    }
+
+    /* stack now has pieces of cleaned URL, so just catenate them
+     * onto output string until stack is empty */
+    while(0 < mystack->stack_size) {
+      tmp = shift_grp_stack(mystack);
+      strcat(outURL, tmp);
+      strcat(outURL, "/");
+    }
+    outURL[strlen(outURL) - 1] = 0; /* blank out trailing / */
+  } while(0);
+  delete_grp_stack(&mystack);
+  return *status;
+}
+
+/* free all stack contents using pop_grp_stack before freeing the
+ * grp_stack itself */
+static void delete_grp_stack(grp_stack** mystack) {
+  if(!mystack || !*mystack) return;
+  while((*mystack)->stack_size) pop_grp_stack(*mystack);
+  free(*mystack);
+  *mystack = NULL;
+}
+
+/* append an item to the stack, handling the special case of the first
+ * item appended */
+static grp_stack_item* grp_stack_append(
+  grp_stack_item* last, grp_stack_data data
+) {
+  /* first create a new stack item, and copy data to it */
+  grp_stack_item* new_item = (grp_stack_item*) malloc(sizeof(grp_stack_item));
+  new_item->data = data;
+  if(last) {
+    /* attach this item between the "last" item and its "next" item */
+    new_item->next = last->next;
+    new_item->prev = last;
+    last->next->prev = new_item;
+    last->next = new_item;
+  } else {
+    /* stack is empty, so "next" and "previous" both point back to it */
+    new_item->next = new_item;
+    new_item->prev = new_item;
+  }
+  return new_item;
+}
+
+/* remove an item from the stack, handling the special case of the last
+ * item removed */
+static grp_stack_data grp_stack_remove(grp_stack_item* last) {
+  grp_stack_data retval = last->data;
+  last->prev->next = last->next;
+  last->next->prev = last->prev;
+  free(last);
+  return retval;
+}
+
+/* create new stack dynamically, and give it valid initial values */
+static grp_stack* new_grp_stack(void) {
+  grp_stack* retval = (grp_stack*) malloc(sizeof(grp_stack));
+  if(retval) {
+    retval->stack_size = 0;
+    retval->top = NULL;
+  }
+  return retval;
+}
+
+/* return the value at the top of the stack and remove it, updating
+ * stack_size. top->prev becomes the new "top" */
+static grp_stack_data pop_grp_stack(grp_stack* mystack) {
+  grp_stack_data retval = grp_stack_default;
+  if(mystack && mystack->top) {
+    grp_stack_item* newtop = mystack->top->prev;
+    retval = grp_stack_remove(mystack->top);
+    mystack->top = newtop;
+    if(0 == --mystack->stack_size) mystack->top = NULL;
+  }
+  return retval;
+}
+
+/* add to the stack after the top element. the added element becomes
+ * the new "top" */
+static void push_grp_stack(grp_stack* mystack, grp_stack_data data) {
+  if(!mystack) return;
+  mystack->top = grp_stack_append(mystack->top, data);
+  ++mystack->stack_size;
+  return;
+}
+
+/* return the value at the bottom of the stack and remove it, updating
+ * stack_size. "top" pointer is unaffected */
+static grp_stack_data shift_grp_stack(grp_stack* mystack) {
+  grp_stack_data retval = grp_stack_default;
+  if(mystack && mystack->top) {
+    retval = grp_stack_remove(mystack->top->next); /* top->next == bottom */
+    if(0 == --mystack->stack_size) mystack->top = NULL;
+  }
+  return retval;
+}
+
+/* add to the stack after the top element. "top" is unaffected, except
+ * in the special case of an initially empty stack */
+/* static void unshift_grp_stack(grp_stack* mystack, grp_stack_data data) {
+   if(!mystack) return;
+   if(mystack->top) grp_stack_append(mystack->top, data);
+   else mystack->top = grp_stack_append(NULL, data);
+   ++mystack->stack_size;
+   return;
+   } */
+
+/*--------------------------------------------------------------------------*/
+int fits_url2relurl(char     *refURL, /* I reference URL string             */
+		    char     *absURL, /* I absoulute URL string to process  */
+		    char     *relURL, /* O resulting relative URL string    */
+		    int      *status)
+/*
+  create a relative URL to the file referenced by absURL with respect to the
+  reference URL refURL. The relative URL is returned in relURL.
+
+  Both refURL and absURL must be absolute URL strings; i.e. either begin
+  with an access method specification "XXX://" or with a '/' character
+  signifiying that they are absolute file paths.
+
+  Note that it is possible to make a relative URL from two input URLs
+  (absURL and refURL) that are not compatable. This function does not
+  check to see if the resulting relative URL makes any sence. For instance,
+  it is impossible to make a relative URL from the following two inputs:
+
+  absURL = ftp://a.b.c.com/x/y/z/foo.fits
+  refURL = /a/b/c/ttt.fits
+
+  The resulting relURL will be:
+
+  ../../../ftp://a.b.c.com/x/y/z/foo.fits 
+
+  Which is syntically correct but meaningless. The problem is that a file
+  with an access method of ftp:// cannot be expressed a a relative URL to
+  a local disk file.
+*/
+
+{
+  int i,j;
+  int refcount,abscount;
+  int refsize,abssize;
+  int done;
+
+
+  if(*status != 0) return(*status);
+
+  /* initialize the relative URL string */
+  relURL[0] = 0;
+
+  do
+    {
+      /*
+	refURL and absURL must be absolute to process
+      */
+
+      if(!(fits_is_url_absolute(refURL) || *refURL == '/') ||
+	 !(fits_is_url_absolute(absURL) || *absURL == '/'))
+	{
+	  *status = URL_PARSE_ERROR;
+	  ffpmsg("Cannot make rel. URL from non abs. URLs (fits_url2relurl)");
+	  continue;
+	}
+
+      /* determine the size of the refURL and absURL strings */
+
+      refsize = strlen(refURL);
+      abssize = strlen(absURL);
+
+      /* process the two URL strings and build the relative URL between them */
+		
+
+      for(done = 0, refcount = 0, abscount = 0; 
+	  !done && refcount < refsize && abscount < abssize; 
+	  ++refcount, ++abscount)
+	{
+	  for(; abscount < abssize && absURL[abscount] == '/'; ++abscount);
+	  for(; refcount < refsize && refURL[refcount] == '/'; ++refcount);
+
+	  /* find the next path segment in absURL */ 
+	  for(i = abscount; absURL[i] != '/' && i < abssize; ++i);
+	  
+	  /* find the next path segment in refURL */
+	  for(j = refcount; refURL[j] != '/' && j < refsize; ++j);
+	  
+	  /* do the two path segments match? */
+	  if(i == j && 
+	     strncmp(absURL+abscount, refURL+refcount,i-refcount) == 0)
+	    {
+	      /* they match, so ignore them and continue */
+	      abscount = i; refcount = j;
+	      continue;
+	    }
+	  
+	  /* We found a difference in the paths in refURL and absURL.
+	     For every path segment remaining in the refURL string, append
+	     a "../" path segment to the relataive URL relURL.
+	  */
+
+	  for(j = refcount; j < refsize; ++j)
+	    if(refURL[j] == '/') strcat(relURL,"../");
+	  
+	  /* copy all remaining characters of absURL to the output relURL */
+
+	  strcat(relURL,absURL+abscount);
+	  
+	  /* we are done building the relative URL */
+	  done = 1;
+	}
+
+    }while(0);
+
+  return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_relurl2url(char     *refURL, /* I reference URL string             */
+		    char     *relURL, /* I relative URL string to process   */
+		    char     *absURL, /* O absolute URL string              */
+		    int      *status)
+/*
+  create an absolute URL from a relative url and a reference URL. The 
+  reference URL is given by the FITS file pointed to by fptr.
+
+  The construction of the absolute URL from the partial and reference URl
+  is performed using the rules set forth in:
+ 
+  http://www.w3.org/Addressing/URL/URL_TOC.html
+  and
+  http://www.w3.org/Addressing/URL/4_3_Partial.html
+
+  Note that the relative URL string relURL must conform to the Unix-like
+  URL syntax; host dependent partial URL strings are not allowed.
+*/
+{
+  int i;
+
+  char tmpStr[FLEN_FILENAME];
+
+  char *tmpStr1, *tmpStr2;
+
+
+  if(*status != 0) return(*status);
+  
+  do
+    {
+
+      /*
+	make a copy of the reference URL string refURL for parsing purposes
+      */
+
+      strcpy(tmpStr,refURL);
+
+      /*
+	if the reference file has an access method of mem:// or shmem://
+	then we cannot use it as the basis of an absolute URL construction
+	for a partial URL
+      */
+	  
+      if(strncasecmp(tmpStr,"MEM:",4)   == 0 ||
+                	                strncasecmp(tmpStr,"SHMEM:",6) == 0)
+	{
+	  ffpmsg("ref URL has access mem:// or shmem:// (fits_relurl2url)");
+	  ffpmsg("   cannot construct full URL from a partial URL and ");
+	  ffpmsg("   MEM/SHMEM base URL");
+	  *status = URL_PARSE_ERROR;
+	  continue;
+	}
+
+      if(relURL[0] != '/')
+	{
+	  /*
+	    just append the relative URL string to the reference URL
+	    string (minus the reference URL file name) to form the 
+	    absolute URL string
+	  */
+	      
+	  tmpStr1 = strrchr(tmpStr,'/');
+	  
+	  if(tmpStr1 != NULL) tmpStr1[1] = 0;
+	  else                tmpStr[0]  = 0;
+	  
+	  strcat(tmpStr,relURL);
+	}
+      else
+	{
+	  /*
+	    have to parse the refURL string for the first occurnace of the 
+	    same number of '/' characters as contained in the beginning of
+	    location that is not followed by a greater number of consective 
+	    '/' charaters (yes, that is a confusing statement); this is the 
+	    location in the refURL string where the relURL string is to
+	    be appended to form the new absolute URL string
+	   */
+	  
+	  /*
+	    first, build up a slash pattern string that has one more
+	    slash in it than the starting slash pattern of the
+	    relURL string
+	  */
+	  
+	  strcpy(absURL,"/");
+	  
+	  for(i = 0; relURL[i] == '/'; ++i) strcat(absURL,"/");
+	  
+	  /*
+	    loop over the refURL string until the slash pattern stored
+	    in absURL is no longer found
+	  */
+
+	  for(tmpStr1 = tmpStr, i = strlen(absURL); 
+	      (tmpStr2 = strstr(tmpStr1,absURL)) != NULL;
+	      tmpStr1 = tmpStr2 + i);
+	  
+	  /* reduce the slash pattern string by one slash */
+	  
+	  absURL[i-1] = 0;
+	  
+	  /* 
+	     search for the slash pattern in the remaining portion
+	     of the refURL string
+	  */
+
+	  tmpStr2 = strstr(tmpStr1,absURL);
+	  
+	  /* if no slash pattern match was found */
+	  
+	  if(tmpStr2 == NULL)
+	    {
+	      /* just strip off the file name from the refURL  */
+	      
+	      tmpStr2 = strrchr(tmpStr1,'/');
+	      
+	      if(tmpStr2 != NULL) tmpStr2[0] = 0;
+	      else                tmpStr[0]  = 0;
+	    }
+	  else
+	    {
+	      /* set a string terminator at the slash pattern match */
+	      
+	      *tmpStr2 = 0;
+	    }
+	  
+	  /* 
+	    conatenate the relURL string to the refURL string to form
+	    the absURL
+	   */
+
+	  strcat(tmpStr,relURL);
+	}
+
+      /*
+	normalize the absURL by removing any ".." or "." specifiers
+	in the string
+      */
+
+      *status = fits_clean_url(tmpStr,absURL,status);
+
+    }while(0);
+
+  return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_encode_url(char *inpath,  /* I URL  to be encoded                  */ 
+		    char *outpath, /* O output encoded URL                  */
+		    int *status)
+     /*
+       encode all URL "unsafe" and "reserved" characters using the "%XX"
+       convention, where XX stand for the two hexidecimal digits of the
+       encode character's ASCII code.
+
+       Note that the output path is at least as large as, if not larger than
+       the input path, so that OUTPATH should be passed to this function
+       with room for growth. If not a runtime error could result. It is
+       assumed that OUTPATH has been allocated with enough room to hold
+       the resulting encoded URL.
+
+       This function was adopted from code in the libwww.a library available
+       via the W3 consortium <URL: http://www.w3.org>
+     */
+{
+  unsigned char a;
+  
+  char *p;
+  char *q;
+  char *hex = "0123456789ABCDEF";
+  
+unsigned const char isAcceptable[96] =
+{/* 0x0 0x1 0x2 0x3 0x4 0x5 0x6 0x7 0x8 0x9 0xA 0xB 0xC 0xD 0xE 0xF */
+  
+    0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xF,0xE,0x0,0xF,0xF,0xC, 
+                                           /* 2x  !"#$%&'()*+,-./   */
+    0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0x8,0x0,0x0,0x0,0x0,0x0,
+                                           /* 3x 0123456789:;<=>?   */
+    0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF, 
+                                           /* 4x @ABCDEFGHIJKLMNO   */
+    0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0x0,0x0,0x0,0x0,0xF,
+                                           /* 5X PQRSTUVWXYZ[\]^_   */
+    0x0,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,
+                                           /* 6x `abcdefghijklmno   */
+    0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0xF,0x0,0x0,0x0,0x0,0x0  
+                                           /* 7X pqrstuvwxyz{\}~DEL */
+};
+
+  if(*status != 0) return(*status);
+  
+  /* loop over all characters in inpath until '\0' is encountered */
+
+  for(q = outpath, p = inpath; *p; p++)
+    {
+      a = (unsigned char)*p;
+
+      /* if the charcter requires encoding then process it */
+
+      if(!( a>=32 && a<128 && (isAcceptable[a-32])))
+	{
+	  /* add a '%' character to the outpath */
+	  *q++ = HEX_ESCAPE;
+	  /* add the most significant ASCII code hex value */
+	  *q++ = hex[a >> 4];
+	  /* add the least significant ASCII code hex value */
+	  *q++ = hex[a & 15];
+	}
+      /* else just copy the character as is */
+      else *q++ = *p;
+    }
+
+  /* null terminate the outpath string */
+
+  *q++ = 0; 
+  
+  return(*status);
+}
+
+/*---------------------------------------------------------------------------*/
+int fits_unencode_url(char *inpath,  /* I input URL with encoding            */
+		      char *outpath, /* O unencoded URL                      */
+		      int  *status)
+     /*
+       unencode all URL "unsafe" and "reserved" characters to their actual
+       ASCII representation. All tokens of the form "%XX" where XX is the
+       hexidecimal code for an ASCII character, are searched for and
+       translated into the actuall ASCII character (so three chars become
+       1 char).
+
+       It is assumed that OUTPATH has enough room to hold the unencoded
+       URL.
+
+       This function was adopted from code in the libwww.a library available
+       via the W3 consortium <URL: http://www.w3.org>
+     */
+
+{
+    char *p;
+    char *q;
+    char  c;
+
+    if(*status != 0) return(*status);
+
+    p = inpath;
+    q = outpath;
+
+    /* 
+       loop over all characters in the inpath looking for the '%' escape
+       character; if found the process the escape sequence
+    */
+
+    while(*p != 0) 
+      {
+	/* 
+	   if the character is '%' then unencode the sequence, else
+	   just copy the character from inpath to outpath
+        */
+
+        if (*p == HEX_ESCAPE)
+	  {
+            if((c = *(++p)) != 0)
+	      { 
+		*q = (
+		      (c >= '0' && c <= '9') ?
+		      (c - '0') : ((c >= 'A' && c <= 'F') ?
+				   (c - 'A' + 10) : (c - 'a' + 10))
+		      )*16;
+
+		if((c = *(++p)) != 0)
+		  {
+		    *q = *q + (
+			       (c >= '0' && c <= '9') ? 
+		               (c - '0') : ((c >= 'A' && c <= 'F') ? 
+					    (c - 'A' + 10) : (c - 'a' + 10))
+			       );
+		    p++, q++;
+		  }
+	      }
+	  } 
+	else
+	  *q++ = *p++; 
+      }
+ 
+    /* terminate the outpath */
+    *q = 0;
+
+    return(*status);   
+}
+/*---------------------------------------------------------------------------*/
+
+int fits_is_url_absolute(char *url)
+/*
+  Return a True (1) or False (0) value indicating whether or not the passed
+  URL string contains an access method specifier or not. Note that this is
+  a boolean function and it neither reads nor returns the standard error
+  status parameter
+*/
+{
+  char *tmpStr1, *tmpStr2;
+
+  char reserved[] = {':',';','/','?','@','&','=','+','$',','};
+
+  /*
+    The rule for determing if an URL is relative or absolute is that it (1)
+    must have a colon ":" and (2) that the colon must appear before any other
+    reserved URL character in the URL string. We first see if a colon exists,
+    get its position in the string, and then check to see if any of the other
+    reserved characters exists and if their position in the string is greater
+    than that of the colons. 
+   */
+
+  if( (tmpStr1 = strchr(url,reserved[0])) != NULL                       &&
+     ((tmpStr2 = strchr(url,reserved[1])) == NULL || tmpStr2 > tmpStr1) &&
+     ((tmpStr2 = strchr(url,reserved[2])) == NULL || tmpStr2 > tmpStr1) &&
+     ((tmpStr2 = strchr(url,reserved[3])) == NULL || tmpStr2 > tmpStr1) &&
+     ((tmpStr2 = strchr(url,reserved[4])) == NULL || tmpStr2 > tmpStr1) &&
+     ((tmpStr2 = strchr(url,reserved[5])) == NULL || tmpStr2 > tmpStr1) &&
+     ((tmpStr2 = strchr(url,reserved[6])) == NULL || tmpStr2 > tmpStr1) &&
+     ((tmpStr2 = strchr(url,reserved[7])) == NULL || tmpStr2 > tmpStr1) &&
+     ((tmpStr2 = strchr(url,reserved[8])) == NULL || tmpStr2 > tmpStr1) &&
+     ((tmpStr2 = strchr(url,reserved[9])) == NULL || tmpStr2 > tmpStr1)   )
+    {
+      return(1);
+    }
+  else
+    {
+      return(0);
+    }
+}
diff --git a/src/plugins/cfitsio/group.h b/src/plugins/cfitsio/group.h
new file mode 100644
index 0000000..f7aae5b
--- /dev/null
+++ b/src/plugins/cfitsio/group.h
@@ -0,0 +1,65 @@
+#define MAX_HDU_TRACKER 1000
+
+typedef struct _HDUtracker HDUtracker;
+
+struct _HDUtracker
+{
+  int nHDU;
+
+  char *filename[MAX_HDU_TRACKER];
+  int  position[MAX_HDU_TRACKER];
+
+  char *newFilename[MAX_HDU_TRACKER];
+  int  newPosition[MAX_HDU_TRACKER];
+};
+
+/* functions used internally in the grouping convention module */
+
+int ffgtdc(int grouptype, int xtensioncol, int extnamecol, int extvercol,
+	   int positioncol, int locationcol, int uricol, char *ttype[],
+	   char *tform[], int *ncols, int  *status);
+
+int ffgtgc(fitsfile *gfptr, int *xtensionCol, int *extnameCol, int *extverCol,
+	   int *positionCol, int *locationCol, int *uriCol, int *grptype,
+	   int *status);
+
+int ffgmul(fitsfile *mfptr, int rmopt, int *status);
+
+int ffgmf(fitsfile *gfptr, char *xtension, char *extname, int extver,	   
+	  int position,	char *location,	long *member, int *status);
+
+int ffgtrmr(fitsfile *gfptr, HDUtracker *HDU, int *status);
+
+int ffgtcpr(fitsfile *infptr, fitsfile *outfptr, int cpopt, HDUtracker *HDU,
+	    int *status);
+
+int fftsad(fitsfile *mfptr, HDUtracker *HDU, int *newPosition, 
+	   char *newFileName);
+
+int fftsud(fitsfile *mfptr, HDUtracker *HDU, int newPosition, 
+	   char *newFileName);
+
+void prepare_keyvalue(char *keyvalue);
+
+int fits_path2url(char *inpath, char *outpath, int  *status);
+
+int fits_url2path(char *inpath, char *outpath, int  *status);
+
+int fits_get_cwd(char *cwd, int *status);
+
+int fits_get_url(fitsfile *fptr, char *realURL, char *startURL, 
+		 char *realAccess, char *startAccess, int *iostate, 
+		 int *status);
+
+int fits_clean_url(char *inURL, char *outURL, int *status);
+
+int fits_relurl2url(char *refURL, char *relURL, char *absURL, int *status);
+
+int fits_url2relurl(char *refURL, char *absURL, char *relURL, int *status);
+
+int fits_encode_url(char *inpath, char *outpath, int *status);
+
+int fits_unencode_url(char *inpath, char *outpath, int *status);
+
+int fits_is_url_absolute(char *url);
+
diff --git a/src/plugins/cfitsio/grparser.c b/src/plugins/cfitsio/grparser.c
new file mode 100644
index 0000000..a5091ea
--- /dev/null
+++ b/src/plugins/cfitsio/grparser.c
@@ -0,0 +1,1379 @@
+/*		T E M P L A T E   P A R S E R
+		=============================
+
+		by Jerzy Borkowski obs unige ch
+
+		Integral Science Data Center
+		ch. d'Ecogia 16
+		1290 Versoix
+		Switzerland
+
+14-Oct-98: initial release
+16-Oct-98: code cleanup, #include <string.h> included, now gcc -Wall prints no
+		warnings during compilation. Bugfix: now one can specify additional
+		columns in group HDU. Autoindexing also works in this situation
+		(colunms are number from 7 however).
+17-Oct-98: bugfix: complex keywords were incorrectly written (was TCOMPLEX should
+		be TDBLCOMPLEX).
+20-Oct-98: bugfix: parser was writing EXTNAME twice, when first HDU in template is
+		defined with XTENSION IMAGE then parser creates now dummy PHDU,
+		SIMPLE T is now allowed only at most once and in first HDU only.
+		WARNING: one should not define EXTNAME keyword for GROUP HDUs, as
+		they have them already defined by parser (EXTNAME = GROUPING).
+		Parser accepts EXTNAME oin GROUP HDU definition, but in this
+		case multiple EXTNAME keywords will present in HDU header.
+23-Oct-98: bugfix: unnecessary space was written to FITS file for blank
+		keywords.
+24-Oct-98: syntax change: empty lines and lines with only whitespaces are 
+		written to FITS files as blank keywords (if inside group/hdu
+		definition). Previously lines had to have at least 8 spaces.
+		Please note, that due to pecularities of CFITSIO if the
+		last keyword(s) defined for given HDU are blank keywords
+		consisting of only 80 spaces, then (some of) those keywords
+		may be silently deleted by CFITSIO.
+13-Nov-98: bugfix: parser was writing GRPNAME twice. Parser still creates
+                GRPNAME keywords for GROUP HDU's which do not specify them.
+                However, values (of form DEFAULT_GROUP_XXX) are assigned
+                not necessarily in order HDUs appear in template file, but
+                rather in order parser completes their creation in FITS
+                file. Also, when including files, if fopen fails, parser
+                tries to open file with a name = directory_of_top_level
+                file + name of file to be included, as long as name
+                of file to be included does not specify absolute pathname.
+16-Nov-98: bugfix to bugfix from 13-Nov-98
+19-Nov-98: EXTVER keyword is now automatically assigned value by parser.
+17-Dev-98: 2 new things added: 1st: CFITSIO_INCLUDE_FILES environment
+		variable can contain a colon separated list of directories
+		to look for when looking for template include files (and master
+		template also). 2nd: it is now possible to append template
+		to nonempty FITS. file. fitsfile *ff no longer needs to point
+		to an empty FITS file with 0 HDUs in it. All data written by
+		parser will simple be appended at the end of file.
+22-Jan-99: changes to parser: when in append mode parser initially scans all
+		existing HDUs to built a list of already used EXTNAME/EXTVERs
+22-Jan-99: Bruce O'Neel, bugfix : TLONG should always reference long type
+		variable on OSF/Alpha and on 64-bit archs in general
+20-Jun-2002 Wm Pence, added support for the HIERARCH keyword convention in
+                which keyword names can effectively be longer than 8 characters.
+                Example:
+                HIERARCH  LongKeywordName = 'value' / comment
+30-Jan-2003 Wm Pence, bugfix: ngp_read_xtension was testing for "ASCIITABLE" 
+                instead of "TABLE" as the XTENSION value of an ASCII table,
+                and it did not allow for optional trailing spaces in the
+                "IMAGE" or "TABLE" string. 
+16-Dec-2003 James Peachey: ngp_keyword_all_write was modified to apply
+                comments from the template file to the output file in
+                the case of reserved keywords (e.g. tform#, ttype# etcetera).
+*/
+
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifdef sparc
+#include <malloc.h>
+#include <memory.h>
+#endif
+
+#include <string.h>
+#include "fitsio2.h"
+#include "grparser.h"
+
+NGP_RAW_LINE	ngp_curline = { NULL, NULL, NULL, NGP_TTYPE_UNKNOWN, NULL, NGP_FORMAT_OK, 0 };
+NGP_RAW_LINE	ngp_prevline = { NULL, NULL, NULL, NGP_TTYPE_UNKNOWN, NULL, NGP_FORMAT_OK, 0 };
+
+int		ngp_inclevel = 0;		/* number of included files, 1 - means mean file */
+int		ngp_grplevel = 0;		/* group nesting level, 0 - means no grouping */
+
+FILE		*ngp_fp[NGP_MAX_INCLUDE];	/* stack of included file handles */
+int		ngp_keyidx = NGP_TOKEN_UNKNOWN;	/* index of token in current line */
+NGP_TOKEN	ngp_linkey;			/* keyword after line analyze */
+
+char            ngp_master_dir[NGP_MAX_FNAME];  /* directory of top level include file */
+
+NGP_TKDEF	ngp_tkdef[] = 			/* tokens recognized by parser */
+      { {	"\\INCLUDE",	NGP_TOKEN_INCLUDE },
+	{	"\\GROUP",	NGP_TOKEN_GROUP },
+	{	"\\END",	NGP_TOKEN_END },
+	{	"XTENSION",	NGP_TOKEN_XTENSION },
+	{	"SIMPLE",	NGP_TOKEN_SIMPLE },
+	{	NULL,		NGP_TOKEN_UNKNOWN }
+      };
+
+int	master_grp_idx = 1;			/* current unnamed group in object */
+
+int		ngp_extver_tab_size = 0;
+NGP_EXTVER_TAB	*ngp_extver_tab = NULL;
+
+
+int	ngp_get_extver(char *extname, int *version)
+ { NGP_EXTVER_TAB *p;
+   char 	*p2;
+   int		i;
+
+   if ((NULL == extname) || (NULL == version)) return(NGP_BAD_ARG);
+   if ((NULL == ngp_extver_tab) && (ngp_extver_tab_size > 0)) return(NGP_BAD_ARG);
+   if ((NULL != ngp_extver_tab) && (ngp_extver_tab_size <= 0)) return(NGP_BAD_ARG);
+
+   for (i=0; i<ngp_extver_tab_size; i++)
+    { if (0 == strcmp(extname, ngp_extver_tab[i].extname))
+        { *version = (++ngp_extver_tab[i].version);
+          return(NGP_OK);
+        }
+    }
+
+   if (NULL == ngp_extver_tab)
+     { p = (NGP_EXTVER_TAB *)ngp_alloc(sizeof(NGP_EXTVER_TAB)); }
+   else
+     { p = (NGP_EXTVER_TAB *)ngp_realloc(ngp_extver_tab, (ngp_extver_tab_size + 1) * sizeof(NGP_EXTVER_TAB)); }
+
+   if (NULL == p) return(NGP_NO_MEMORY);
+
+   p2 = ngp_alloc(strlen(extname) + 1);
+   if (NULL == p2)
+     { ngp_free(p);
+       return(NGP_NO_MEMORY);
+     }
+
+   strcpy(p2, extname);
+   ngp_extver_tab = p;
+   ngp_extver_tab[ngp_extver_tab_size].extname = p2;
+   *version = ngp_extver_tab[ngp_extver_tab_size].version = 1;
+
+   ngp_extver_tab_size++;
+
+   return(NGP_OK);
+ }
+
+int	ngp_set_extver(char *extname, int version)
+ { NGP_EXTVER_TAB *p;
+   char 	*p2;
+   int		i;
+
+   if (NULL == extname) return(NGP_BAD_ARG);
+   if ((NULL == ngp_extver_tab) && (ngp_extver_tab_size > 0)) return(NGP_BAD_ARG);
+   if ((NULL != ngp_extver_tab) && (ngp_extver_tab_size <= 0)) return(NGP_BAD_ARG);
+
+   for (i=0; i<ngp_extver_tab_size; i++)
+    { if (0 == strcmp(extname, ngp_extver_tab[i].extname))
+        { if (version > ngp_extver_tab[i].version)  ngp_extver_tab[i].version = version;
+          return(NGP_OK);
+        }
+    }
+
+   if (NULL == ngp_extver_tab)
+     { p = (NGP_EXTVER_TAB *)ngp_alloc(sizeof(NGP_EXTVER_TAB)); }
+   else
+     { p = (NGP_EXTVER_TAB *)ngp_realloc(ngp_extver_tab, (ngp_extver_tab_size + 1) * sizeof(NGP_EXTVER_TAB)); }
+
+   if (NULL == p) return(NGP_NO_MEMORY);
+
+   p2 = ngp_alloc(strlen(extname) + 1);
+   if (NULL == p2)
+     { ngp_free(p);
+       return(NGP_NO_MEMORY);
+     }
+
+   strcpy(p2, extname);
+   ngp_extver_tab = p;
+   ngp_extver_tab[ngp_extver_tab_size].extname = p2;
+   ngp_extver_tab[ngp_extver_tab_size].version = version;
+
+   ngp_extver_tab_size++;
+
+   return(NGP_OK);
+ }
+
+
+int	ngp_delete_extver_tab(void)
+ { int i;
+
+   if ((NULL == ngp_extver_tab) && (ngp_extver_tab_size > 0)) return(NGP_BAD_ARG);
+   if ((NULL != ngp_extver_tab) && (ngp_extver_tab_size <= 0)) return(NGP_BAD_ARG);
+   if ((NULL == ngp_extver_tab) && (0 == ngp_extver_tab_size)) return(NGP_OK);
+
+   for (i=0; i<ngp_extver_tab_size; i++)
+    { if (NULL != ngp_extver_tab[i].extname)
+        { ngp_free(ngp_extver_tab[i].extname);
+          ngp_extver_tab[i].extname = NULL;
+        }
+      ngp_extver_tab[i].version = 0;
+    }
+   ngp_free(ngp_extver_tab);
+   ngp_extver_tab = NULL;
+   ngp_extver_tab_size = 0;
+   return(NGP_OK);
+ }
+
+	/* compare strings, case does not matter */
+
+int	ngp_strcasecmp(char *p1, char *p2)
+ { char c1, c2;
+
+   for (;;)
+    {
+      c1 = *p1;
+      if ((c1 >= 'a') && (c1 <= 'z')) c1 += ('A' - 'a');
+
+      c2 = *p2;
+      if ((c2 >= 'a') && (c2 <= 'z')) c2 += ('A' - 'a');
+
+      if (c1 < c2) return(-1);
+      if (c1 > c2) return(1);
+      if (0 == c1) return(0);
+      p1++;
+      p2++;
+    }
+ }
+
+int	ngp_strcasencmp(char *p1, char *p2, int n)
+ { char c1, c2;
+   int ii;
+
+   for (ii=0;ii<n;ii++)
+    {
+      c1 = *p1;
+      if ((c1 >= 'a') && (c1 <= 'z')) c1 += ('A' - 'a');
+
+      c2 = *p2;
+      if ((c2 >= 'a') && (c2 <= 'z')) c2 += ('A' - 'a');
+
+      if (c1 < c2) return(-1);
+      if (c1 > c2) return(1);
+      if (0 == c1) return(0);
+      p1++;
+      p2++;
+    }
+    return(0);
+ }
+
+	/* read one line from file */
+
+int	ngp_line_from_file(FILE *fp, char **p)
+ { int	c, r, llen, allocsize, alen;
+   char	*p2;
+
+   if (NULL == fp) return(NGP_NUL_PTR);		/* check for stupid args */
+   if (NULL == p) return(NGP_NUL_PTR);		/* more foolproof checks */
+   
+   r = NGP_OK;					/* initialize stuff, reset err code */
+   llen = 0;					/* 0 characters read so far */
+   *p = (char *)ngp_alloc(1);			/* preallocate 1 byte */
+   allocsize = 1;				/* signal that we have allocated 1 byte */
+   if (NULL == *p) return(NGP_NO_MEMORY);	/* if this failed, system is in dire straits */
+
+   for (;;)
+    { c = getc(fp);				/* get next character */
+      if ('\r' == c) continue;			/* carriage return character ?  Just ignore it */
+      if (EOF == c)				/* EOF signalled ? */
+        { 
+          if (ferror(fp)) r = NGP_READ_ERR;	/* was it real error or simply EOF ? */
+	  if (0 == llen) return(NGP_EOF);	/* signal EOF only if 0 characters read so far */
+          break;
+        }
+      if ('\n' == c) break;			/* end of line character ? */
+      
+      llen++;					/* we have new character, make room for it */
+      alen = ((llen + NGP_ALLOCCHUNK) / NGP_ALLOCCHUNK) * NGP_ALLOCCHUNK;
+      if (alen > allocsize)
+        { p2 = (char *)ngp_realloc(*p, alen);	/* realloc buffer, if there is need */
+          if (NULL == p2)
+            { r = NGP_NO_MEMORY;
+              break;
+            }
+	  *p = p2;
+          allocsize = alen;
+        }
+      (*p)[llen - 1] = c;			/* copy character to buffer */
+    }
+
+   llen++;					/* place for terminating \0 */
+   if (llen != allocsize)
+     { p2 = (char *)ngp_realloc(*p, llen);
+       if (NULL == p2) r = NGP_NO_MEMORY;
+       else
+         { *p = p2;
+           (*p)[llen - 1] = 0;			/* copy \0 to buffer */
+         }         
+     }
+   else
+     { (*p)[llen - 1] = 0;			/* necessary when line read was empty */
+     }
+
+   if ((NGP_EOF != r) && (NGP_OK != r))		/* in case of errors free resources */
+     { ngp_free(*p);
+       *p = NULL;
+     }
+   
+   return(r);					/* return  status code */
+ }
+
+	/* free current line structure */
+
+int	ngp_free_line(void)
+ {
+   if (NULL != ngp_curline.line)
+     { ngp_free(ngp_curline.line);
+       ngp_curline.line = NULL;
+       ngp_curline.name = NULL;
+       ngp_curline.value = NULL;
+       ngp_curline.comment = NULL;
+       ngp_curline.type = NGP_TTYPE_UNKNOWN;
+       ngp_curline.format = NGP_FORMAT_OK;
+       ngp_curline.flags = 0;
+     }
+   return(NGP_OK);
+ }
+
+	/* free cached line structure */
+
+int	ngp_free_prevline(void)
+ {
+   if (NULL != ngp_prevline.line)
+     { ngp_free(ngp_prevline.line);
+       ngp_prevline.line = NULL;
+       ngp_prevline.name = NULL;
+       ngp_prevline.value = NULL;
+       ngp_prevline.comment = NULL;
+       ngp_prevline.type = NGP_TTYPE_UNKNOWN;
+       ngp_prevline.format = NGP_FORMAT_OK;
+       ngp_prevline.flags = 0;
+     }
+   return(NGP_OK);
+ }
+
+	/* read one line */
+
+int	ngp_read_line_buffered(FILE *fp)
+ {
+   ngp_free_line();				/* first free current line (if any) */
+   
+   if (NULL != ngp_prevline.line)		/* if cached, return cached line */
+     { ngp_curline = ngp_prevline;
+       ngp_prevline.line = NULL;
+       ngp_prevline.name = NULL;
+       ngp_prevline.value = NULL;
+       ngp_prevline.comment = NULL;
+       ngp_prevline.type = NGP_TTYPE_UNKNOWN;
+       ngp_prevline.format = NGP_FORMAT_OK;
+       ngp_prevline.flags = 0;
+       ngp_curline.flags = NGP_LINE_REREAD;
+       return(NGP_OK);
+     }
+
+   ngp_curline.flags = 0;   			/* if not cached really read line from file */
+   return(ngp_line_from_file(fp, &(ngp_curline.line)));
+ }
+
+	/* unread line */
+
+int	ngp_unread_line(void)
+ {
+   if (NULL == ngp_curline.line)		/* nothing to unread */
+     return(NGP_EMPTY_CURLINE);
+
+   if (NULL != ngp_prevline.line)		/* we cannot unread line twice */
+     return(NGP_UNREAD_QUEUE_FULL);
+
+   ngp_prevline = ngp_curline;
+   ngp_curline.line = NULL;
+   return(NGP_OK);
+ }
+
+	/* a first guess line decomposition */
+
+int	ngp_extract_tokens(NGP_RAW_LINE *cl)
+ { char *p, *s;
+   int	cl_flags, i;
+
+   p = cl->line;				/* start from beginning of line */
+   if (NULL == p) return(NGP_NUL_PTR);
+
+   cl->name = cl->value = cl->comment = NULL;
+   cl->type = NGP_TTYPE_UNKNOWN;
+   cl->format = NGP_FORMAT_OK;
+
+   cl_flags = 0;
+
+   for (i=0;; i++)				/* if 8 spaces at beginning then line is comment */
+    { if ((0 == *p) || ('\n' == *p))
+        {					/* if line has only blanks -> write blank keyword */
+          cl->line[0] = 0;			/* create empty name (0 length string) */
+          cl->comment = cl->name = cl->line;
+	  cl->type = NGP_TTYPE_RAW;		/* signal write unformatted to FITS file */
+          return(NGP_OK);
+        }
+      if ((' ' != *p) && ('\t' != *p)) break;
+      if (i >= 7)
+        { 
+          cl->comment = p + 1;
+          for (s = cl->comment;; s++)		/* filter out any EOS characters in comment */
+           { if ('\n' == *s) *s = 0;
+	     if (0 == *s) break;
+           }
+          cl->line[0] = 0;			/* create empty name (0 length string) */
+          cl->name = cl->line;
+	  cl->type = NGP_TTYPE_RAW;
+          return(NGP_OK);
+        }
+      p++;
+    }
+
+   cl->name = p;
+
+   for (;;)					/* we need to find 1st whitespace */
+    { if ((0 == *p) || ('\n' == *p))
+        { *p = 0;
+          break;
+        }
+
+      /*
+        from Richard Mathar, 2002-05-03, add 10 lines:
+        if upper/lowercase HIERARCH followed also by an equal sign...
+      */
+      if( strncasecmp("HIERARCH",p,strlen("HIERARCH")) == 0 )
+      {
+           char * const eqsi=strchr(p,'=') ;
+           if( eqsi )
+           {
+              cl_flags |= NGP_FOUND_EQUAL_SIGN ;
+              p=eqsi ;
+              break ;
+           }
+      }
+
+      if ((' ' == *p) || ('\t' == *p)) break;
+      if ('=' == *p)
+        { cl_flags |= NGP_FOUND_EQUAL_SIGN;
+          break;
+        }
+
+      p++;
+    }
+
+   if (*p) *(p++) = 0;				/* found end of keyname so terminate string with zero */
+
+   if ((!ngp_strcasecmp("HISTORY", cl->name))
+    || (!ngp_strcasecmp("COMMENT", cl->name))
+    || (!ngp_strcasecmp("CONTINUE", cl->name)))
+     { cl->comment = p;
+       for (s = cl->comment;; s++)		/* filter out any EOS characters in comment */
+        { if ('\n' == *s) *s = 0;
+	  if (0 == *s) break;
+        }
+       cl->type = NGP_TTYPE_RAW;
+       return(NGP_OK);
+     }
+
+   if (!ngp_strcasecmp("\\INCLUDE", cl->name))
+     {
+       for (;; p++)  if ((' ' != *p) && ('\t' != *p)) break; /* skip whitespace */
+
+       cl->value = p;
+       for (s = cl->value;; s++)		/* filter out any EOS characters */
+        { if ('\n' == *s) *s = 0;
+	  if (0 == *s) break;
+        }
+       cl->type = NGP_TTYPE_UNKNOWN;
+       return(NGP_OK);
+     }
+       
+   for (;; p++)
+    { if ((0 == *p) || ('\n' == *p))  return(NGP_OK);	/* test if at end of string */
+      if ((' ' == *p) || ('\t' == *p)) continue; /* skip whitespace */
+      if (cl_flags & NGP_FOUND_EQUAL_SIGN) break;
+      if ('=' != *p) break;			/* ignore initial equal sign */
+      cl_flags |= NGP_FOUND_EQUAL_SIGN;
+    }
+      
+   if ('/' == *p)				/* no value specified, comment only */
+     { p++;
+       if ((' ' == *p) || ('\t' == *p)) p++;
+       cl->comment = p;
+       for (s = cl->comment;; s++)		/* filter out any EOS characters in comment */
+        { if ('\n' == *s) *s = 0;
+	  if (0 == *s) break;
+        }
+       return(NGP_OK);
+     }
+
+   if ('\'' == *p)				/* we have found string within quotes */
+     { cl->value = s = ++p;			/* set pointer to beginning of that string */
+       cl->type = NGP_TTYPE_STRING;		/* signal that it is of string type */
+
+       for (;;)					/* analyze it */
+        { if ((0 == *p) || ('\n' == *p))	/* end of line -> end of string */
+            { *s = 0; return(NGP_OK); }
+
+          if ('\'' == *p)			/* we have found doublequote */
+            { if ((0 == p[1]) || ('\n' == p[1]))/* doublequote is the last character in line */
+                { *s = 0; return(NGP_OK); }
+              if (('\t' == p[1]) || (' ' == p[1])) /* duoblequote was string terminator */
+                { *s = 0; p++; break; }
+              if ('\'' == p[1]) p++;		/* doublequote is inside string, convert "" -> " */ 
+            }
+
+          *(s++) = *(p++);			/* compact string in place, necess. by "" -> " conversion */
+        }
+     }
+   else						/* regular token */
+     { 
+       cl->value = p;				/* set pointer to token */
+       cl->type = NGP_TTYPE_UNKNOWN;		/* we dont know type at the moment */
+       for (;; p++)				/* we need to find 1st whitespace */
+        { if ((0 == *p) || ('\n' == *p))
+            { *p = 0; return(NGP_OK); }
+          if ((' ' == *p) || ('\t' == *p)) break;
+        }
+       if (*p)  *(p++) = 0;			/* found so terminate string with zero */
+     }
+       
+   for (;; p++)
+    { if ((0 == *p) || ('\n' == *p))  return(NGP_OK);	/* test if at end of string */
+      if ((' ' != *p) && ('\t' != *p)) break;	/* skip whitespace */
+    }
+      
+   if ('/' == *p)				/* no value specified, comment only */
+     { p++;
+       if ((' ' == *p) || ('\t' == *p)) p++;
+       cl->comment = p;
+       for (s = cl->comment;; s++)		/* filter out any EOS characters in comment */
+        { if ('\n' == *s) *s = 0;
+	  if (0 == *s) break;
+        }
+       return(NGP_OK);
+     }
+
+   cl->format = NGP_FORMAT_ERROR;
+   return(NGP_OK);				/* too many tokens ... */
+ }
+
+/*      try to open include file. If open fails and fname
+        does not specify absolute pathname, try to open fname
+        in any directory specified in CFITSIO_INCLUDE_FILES
+        environment variable. Finally try to open fname
+        relative to ngp_master_dir, which is directory of top
+        level include file
+*/
+
+int	ngp_include_file(char *fname)		/* try to open include file */
+ { char *p, *p2, *cp, *envar, envfiles[NGP_MAX_ENVFILES];
+
+   if (NULL == fname) return(NGP_NUL_PTR);
+
+   if (ngp_inclevel >= NGP_MAX_INCLUDE)		/* too many include files */
+     return(NGP_INC_NESTING);
+
+   if (NULL == (ngp_fp[ngp_inclevel] = fopen(fname, "r")))
+     {                                          /* if simple open failed .. */
+       envar = getenv("CFITSIO_INCLUDE_FILES");	/* scan env. variable, and retry to open */
+
+       if (NULL != envar)			/* is env. variable defined ? */
+         { strncpy(envfiles, envar, NGP_MAX_ENVFILES - 1);
+           envfiles[NGP_MAX_ENVFILES - 1] = 0;	/* copy search path to local variable, env. is fragile */
+
+           for (p2 = strtok(envfiles, ":"); NULL != p2; p2 = strtok(NULL, ":"))
+            {
+	      cp = (char *)ngp_alloc(strlen(fname) + strlen(p2) + 2);
+	      if (NULL == cp) return(NGP_NO_MEMORY);
+
+	      strcpy(cp, p2);
+#ifdef  MSDOS
+              strcat(cp, "\\");			/* abs. pathname for MSDOS */
+               
+#else
+              strcat(cp, "/");			/* and for unix */
+#endif
+	      strcat(cp, fname);
+	  
+	      ngp_fp[ngp_inclevel] = fopen(cp, "r");
+	      ngp_free(cp);
+
+	      if (NULL != ngp_fp[ngp_inclevel]) break;
+	    }
+        }
+                                      
+       if (NULL == ngp_fp[ngp_inclevel])	/* finally try to open relative to top level */
+         {
+#ifdef  MSDOS
+           if ('\\' == fname[0]) return(NGP_ERR_FOPEN); /* abs. pathname for MSDOS, does not support C:\\PATH */
+#else
+           if ('/' == fname[0]) return(NGP_ERR_FOPEN); /* and for unix */
+#endif
+           if (0 == ngp_master_dir[0]) return(NGP_ERR_FOPEN);
+
+	   p = ngp_alloc(strlen(fname) + strlen(ngp_master_dir) + 1);
+           if (NULL == p) return(NGP_NO_MEMORY);
+
+           strcpy(p, ngp_master_dir);		/* construct composite pathname */
+           strcat(p, fname);			/* comp = master + fname */
+
+           ngp_fp[ngp_inclevel] = fopen(p, "r");/* try to open composite */
+           ngp_free(p);				/* we don't need buffer anymore */
+
+           if (NULL == ngp_fp[ngp_inclevel])
+             return(NGP_ERR_FOPEN);		/* fail if error */
+         }
+     }
+
+   ngp_inclevel++;
+   return(NGP_OK);
+ }
+
+
+/* read line in the intelligent way. All \INCLUDE directives are handled,
+   empty and comment line skipped. If this function returns NGP_OK, than
+   decomposed line (name, type, value in proper type and comment) are
+   stored in ngp_linkey structure. ignore_blank_lines parameter is zero
+   when parser is inside GROUP or HDU definition. Nonzero otherwise.
+*/
+
+int	ngp_read_line(int ignore_blank_lines)
+ { int r, nc, savec;
+   unsigned k;
+
+   if (ngp_inclevel <= 0)		/* do some sanity checking first */
+     { ngp_keyidx = NGP_TOKEN_EOF;	/* no parents, so report error */
+       return(NGP_OK);	
+     }
+   if (ngp_inclevel > NGP_MAX_INCLUDE)  return(NGP_INC_NESTING);
+   if (NULL == ngp_fp[ngp_inclevel - 1]) return(NGP_NUL_PTR);
+
+   for (;;)
+    { switch (r = ngp_read_line_buffered(ngp_fp[ngp_inclevel - 1]))
+       { case NGP_EOF:
+		ngp_inclevel--;			/* end of file, revert to parent */
+		if (ngp_fp[ngp_inclevel])	/* we can close old file */
+		  fclose(ngp_fp[ngp_inclevel]);
+
+		ngp_fp[ngp_inclevel] = NULL;
+		if (ngp_inclevel <= 0)
+		  { ngp_keyidx = NGP_TOKEN_EOF;	/* no parents, so report error */
+		    return(NGP_OK);	
+		  }
+		continue;
+
+	 case NGP_OK:
+		if (ngp_curline.flags & NGP_LINE_REREAD) return(r);
+		break;
+	 default:
+		return(r);
+       }
+      
+      switch (ngp_curline.line[0])
+       { case 0: if (0 == ignore_blank_lines) break; /* ignore empty lines if told so */
+         case '#': continue;			/* ignore comment lines */
+       }
+      
+      r = ngp_extract_tokens(&ngp_curline);	/* analyse line, extract tokens and comment */
+      if (NGP_OK != r) return(r);
+
+      if (NULL == ngp_curline.name)  continue;	/* skip lines consisting only of whitespaces */
+
+      for (k = 0; k < strlen(ngp_curline.name); k++)
+       { if ((ngp_curline.name[k] >= 'a') && (ngp_curline.name[k] <= 'z')) 
+           ngp_curline.name[k] += 'A' - 'a';	/* force keyword to be upper case */
+         if (k == 7) break;  /* only first 8 chars are required to be upper case */
+       }
+
+      for (k=0;; k++)				/* find index of keyword in keyword table */
+       { if (NGP_TOKEN_UNKNOWN == ngp_tkdef[k].code) break;
+         if (0 == strcmp(ngp_curline.name, ngp_tkdef[k].name)) break;
+       }
+
+      ngp_keyidx = ngp_tkdef[k].code;		/* save this index, grammar parser will need this */
+
+      if (NGP_TOKEN_INCLUDE == ngp_keyidx)	/* if this is \INCLUDE keyword, try to include file */
+        { if (NGP_OK != (r = ngp_include_file(ngp_curline.value))) return(r);
+	  continue;				/* and read next line */
+        }
+
+      ngp_linkey.type = NGP_TTYPE_UNKNOWN;	/* now, get the keyword type, it's a long story ... */
+
+      if (NULL != ngp_curline.value)		/* if no value given signal it */
+        { if (NGP_TTYPE_STRING == ngp_curline.type)  /* string type test */
+            { ngp_linkey.type = NGP_TTYPE_STRING;
+              ngp_linkey.value.s = ngp_curline.value;
+            }
+          if (NGP_TTYPE_UNKNOWN == ngp_linkey.type) /* bool type test */
+            { if ((!ngp_strcasecmp("T", ngp_curline.value)) || (!ngp_strcasecmp("F", ngp_curline.value)))
+                { ngp_linkey.type = NGP_TTYPE_BOOL;
+                  ngp_linkey.value.b = (ngp_strcasecmp("T", ngp_curline.value) ? 0 : 1);
+                }
+            }
+          if (NGP_TTYPE_UNKNOWN == ngp_linkey.type) /* complex type test */
+            { if (2 == sscanf(ngp_curline.value, "(%lg,%lg)%n", &(ngp_linkey.value.c.re), &(ngp_linkey.value.c.im), &nc))
+                { if ((' ' == ngp_curline.value[nc]) || ('\t' == ngp_curline.value[nc])
+                   || ('\n' == ngp_curline.value[nc]) || (0 == ngp_curline.value[nc]))
+                    { ngp_linkey.type = NGP_TTYPE_COMPLEX;
+                    }
+                }
+            }
+          if (NGP_TTYPE_UNKNOWN == ngp_linkey.type) /* real type test */
+            { if (strchr(ngp_curline.value, '.') && (1 == sscanf(ngp_curline.value, "%lg%n", &(ngp_linkey.value.d), &nc)))
+                {
+		 if ('D' == ngp_curline.value[nc]) {
+		   /* test if template used a 'D' rather than an 'E' as the exponent character (added by WDP in 12/2010) */
+                   savec = nc;
+		   ngp_curline.value[nc] = 'E';
+		   sscanf(ngp_curline.value, "%lg%n", &(ngp_linkey.value.d), &nc);
+		   if ((' ' == ngp_curline.value[nc]) || ('\t' == ngp_curline.value[nc])
+                    || ('\n' == ngp_curline.value[nc]) || (0 == ngp_curline.value[nc]))  {
+                       ngp_linkey.type = NGP_TTYPE_REAL;
+                     } else {  /* no, this is not a real value */
+		       ngp_curline.value[savec] = 'D';  /* restore the original D character */
+ 		     }
+		 } else {
+		  if ((' ' == ngp_curline.value[nc]) || ('\t' == ngp_curline.value[nc])
+                   || ('\n' == ngp_curline.value[nc]) || (0 == ngp_curline.value[nc]))
+                    { ngp_linkey.type = NGP_TTYPE_REAL;
+                    }
+                 } 
+                }
+            }
+          if (NGP_TTYPE_UNKNOWN == ngp_linkey.type) /* integer type test */
+            { if (1 == sscanf(ngp_curline.value, "%d%n", &(ngp_linkey.value.i), &nc))
+                { if ((' ' == ngp_curline.value[nc]) || ('\t' == ngp_curline.value[nc])
+                   || ('\n' == ngp_curline.value[nc]) || (0 == ngp_curline.value[nc]))
+                    { ngp_linkey.type = NGP_TTYPE_INT;
+                    }
+                }
+            }
+          if (NGP_TTYPE_UNKNOWN == ngp_linkey.type) /* force string type */
+            { ngp_linkey.type = NGP_TTYPE_STRING;
+              ngp_linkey.value.s = ngp_curline.value;
+            }
+        }
+      else
+        { if (NGP_TTYPE_RAW == ngp_curline.type) ngp_linkey.type = NGP_TTYPE_RAW;
+	  else ngp_linkey.type = NGP_TTYPE_NULL;
+	}
+
+      if (NULL != ngp_curline.comment)
+        { strncpy(ngp_linkey.comment, ngp_curline.comment, NGP_MAX_COMMENT); /* store comment */
+	  ngp_linkey.comment[NGP_MAX_COMMENT - 1] = 0;
+	}
+      else
+        { ngp_linkey.comment[0] = 0;
+        }
+
+      strncpy(ngp_linkey.name, ngp_curline.name, NGP_MAX_NAME); /* and keyword's name */
+      ngp_linkey.name[NGP_MAX_NAME - 1] = 0;
+
+      if (strlen(ngp_linkey.name) > FLEN_KEYWORD)  /* WDP: 20-Jun-2002:  mod to support HIERARCH */
+        { 
+           return(NGP_BAD_ARG);		/* cfitsio does not allow names > 8 chars */
+        }
+      
+      return(NGP_OK);			/* we have valid non empty line, so return success */
+    }
+ }
+
+	/* check whether keyword can be written as is */
+
+int	ngp_keyword_is_write(NGP_TOKEN *ngp_tok)
+ { int i, j, l, spc;
+                        /* indexed variables not to write */
+
+   static char *nm[] = { "NAXIS", "TFORM", "TTYPE", NULL } ;
+
+                        /* non indexed variables not allowed to write */
+  
+   static char *nmni[] = { "SIMPLE", "XTENSION", "BITPIX", "NAXIS", "PCOUNT",
+                           "GCOUNT", "TFIELDS", "THEAP", "EXTEND", "EXTVER",
+                           NULL } ;
+
+   if (NULL == ngp_tok) return(NGP_NUL_PTR);
+
+   for (j = 0; ; j++)           /* first check non indexed */
+    { if (NULL == nmni[j]) break;
+      if (0 == strcmp(nmni[j], ngp_tok->name)) return(NGP_BAD_ARG);
+    } 
+
+   for (j = 0; ; j++)           /* now check indexed */
+    { if (NULL == nm[j]) return(NGP_OK);
+      l = strlen(nm[j]);
+      if ((l < 1) || (l > 5)) continue;
+      if (0 == strncmp(nm[j], ngp_tok->name, l)) break;
+    } 
+
+   if ((ngp_tok->name[l] < '1') || (ngp_tok->name[l] > '9')) return(NGP_OK);
+   spc = 0;
+   for (i = l + 1; i < 8; i++)
+    { if (spc) { if (' ' != ngp_tok->name[i]) return(NGP_OK); }
+      else
+       { if ((ngp_tok->name[i] >= '0') || (ngp_tok->name[i] <= '9')) continue;
+         if (' ' == ngp_tok->name[i]) { spc = 1; continue; }
+         if (0 == ngp_tok->name[i]) break;
+         return(NGP_OK);
+       }
+    }
+   return(NGP_BAD_ARG);
+ }
+
+	/* write (almost) all keywords from given HDU to disk */
+
+int     ngp_keyword_all_write(NGP_HDU *ngph, fitsfile *ffp, int mode)
+ { int		i, r, ib;
+   char		buf[200];
+   long		l;
+
+
+   if (NULL == ngph) return(NGP_NUL_PTR);
+   if (NULL == ffp) return(NGP_NUL_PTR);
+   r = NGP_OK;
+   
+   for (i=0; i<ngph->tokcnt; i++)
+    { r = ngp_keyword_is_write(&(ngph->tok[i]));
+      if ((NGP_REALLY_ALL & mode) || (NGP_OK == r))
+        { switch (ngph->tok[i].type)
+           { case NGP_TTYPE_BOOL:
+			ib = ngph->tok[i].value.b;
+			fits_write_key(ffp, TLOGICAL, ngph->tok[i].name, &ib, ngph->tok[i].comment, &r);
+			break;
+             case NGP_TTYPE_STRING:
+			fits_write_key_longstr(ffp, ngph->tok[i].name, ngph->tok[i].value.s, ngph->tok[i].comment, &r);
+			break;
+             case NGP_TTYPE_INT:
+			l = ngph->tok[i].value.i;	/* bugfix - 22-Jan-99, BO - nonalignment of OSF/Alpha */
+			fits_write_key(ffp, TLONG, ngph->tok[i].name, &l, ngph->tok[i].comment, &r);
+			break;
+             case NGP_TTYPE_REAL:
+			fits_write_key(ffp, TDOUBLE, ngph->tok[i].name, &(ngph->tok[i].value.d), ngph->tok[i].comment, &r);
+			break;
+             case NGP_TTYPE_COMPLEX:
+			fits_write_key(ffp, TDBLCOMPLEX, ngph->tok[i].name, &(ngph->tok[i].value.c), ngph->tok[i].comment, &r);
+			break;
+             case NGP_TTYPE_NULL:
+			fits_write_key_null(ffp, ngph->tok[i].name, ngph->tok[i].comment, &r);
+			break;
+             case NGP_TTYPE_RAW:
+			if (0 == strcmp("HISTORY", ngph->tok[i].name))
+			  { fits_write_history(ffp, ngph->tok[i].comment, &r);
+			    break;
+			  }
+			if (0 == strcmp("COMMENT", ngph->tok[i].name))
+			  { fits_write_comment(ffp, ngph->tok[i].comment, &r);
+			    break;
+			  }
+			sprintf(buf, "%-8.8s%s", ngph->tok[i].name, ngph->tok[i].comment);
+			fits_write_record(ffp, buf, &r);
+                        break;
+           }
+        }
+      else if (NGP_BAD_ARG == r) /* enhancement 10 dec 2003, James Peachey: template comments replace defaults */
+        { r = NGP_OK;						/* update comments of special keywords like TFORM */
+          if (ngph->tok[i].comment && *ngph->tok[i].comment)	/* do not update with a blank comment */
+            { fits_modify_comment(ffp, ngph->tok[i].name, ngph->tok[i].comment, &r);
+            }
+        }
+      else /* other problem, typically a blank token */
+        { r = NGP_OK;						/* skip this token, but continue */
+        }
+      if (r) return(r);
+    }
+     
+   fits_set_hdustruc(ffp, &r);				/* resync cfitsio */
+   return(r);
+ }
+
+	/* init HDU structure */
+
+int	ngp_hdu_init(NGP_HDU *ngph)
+ { if (NULL == ngph) return(NGP_NUL_PTR);
+   ngph->tok = NULL;
+   ngph->tokcnt = 0;
+   return(NGP_OK);
+ }
+
+	/* clear HDU structure */
+
+int	ngp_hdu_clear(NGP_HDU *ngph)
+ { int i;
+
+   if (NULL == ngph) return(NGP_NUL_PTR);
+
+   for (i=0; i<ngph->tokcnt; i++)
+    { if (NGP_TTYPE_STRING == ngph->tok[i].type)
+        if (NULL != ngph->tok[i].value.s)
+          { ngp_free(ngph->tok[i].value.s);
+            ngph->tok[i].value.s = NULL;
+          }
+    }
+
+   if (NULL != ngph->tok) ngp_free(ngph->tok);
+
+   ngph->tok = NULL;
+   ngph->tokcnt = 0;
+
+   return(NGP_OK);
+ }
+
+	/* insert new token to HDU structure */
+
+int	ngp_hdu_insert_token(NGP_HDU *ngph, NGP_TOKEN *newtok)
+ { NGP_TOKEN *tkp;
+   
+   if (NULL == ngph) return(NGP_NUL_PTR);
+   if (NULL == newtok) return(NGP_NUL_PTR);
+
+   if (0 == ngph->tokcnt)
+     tkp = (NGP_TOKEN *)ngp_alloc((ngph->tokcnt + 1) * sizeof(NGP_TOKEN));
+   else
+     tkp = (NGP_TOKEN *)ngp_realloc(ngph->tok, (ngph->tokcnt + 1) * sizeof(NGP_TOKEN));
+
+   if (NULL == tkp) return(NGP_NO_MEMORY);
+       
+   ngph->tok = tkp;
+   ngph->tok[ngph->tokcnt] = *newtok;
+
+   if (NGP_TTYPE_STRING == newtok->type)
+     { if (NULL != newtok->value.s)
+         { ngph->tok[ngph->tokcnt].value.s = (char *)ngp_alloc(1 + strlen(newtok->value.s));
+           if (NULL == ngph->tok[ngph->tokcnt].value.s) return(NGP_NO_MEMORY);
+           strcpy(ngph->tok[ngph->tokcnt].value.s, newtok->value.s);
+         }
+     }
+
+   ngph->tokcnt++;
+   return(NGP_OK);
+ }
+
+
+int	ngp_append_columns(fitsfile *ff, NGP_HDU *ngph, int aftercol)
+ { int		r, i, j, exitflg, ngph_i;
+   char 	*my_tform, *my_ttype;
+   char		ngph_ctmp;
+
+
+   if (NULL == ff) return(NGP_NUL_PTR);
+   if (NULL == ngph) return(NGP_NUL_PTR);
+   if (0 == ngph->tokcnt) return(NGP_OK);	/* nothing to do ! */
+
+   r = NGP_OK;
+   exitflg = 0;
+
+   for (j=aftercol; j<NGP_MAX_ARRAY_DIM; j++)	/* 0 for table, 6 for group */
+    { 
+      my_tform = NULL;
+      my_ttype = "";
+    
+      for (i=0; ; i++)
+       { if (1 == sscanf(ngph->tok[i].name, "TFORM%d%c", &ngph_i, &ngph_ctmp))
+           { if ((NGP_TTYPE_STRING == ngph->tok[i].type) && (ngph_i == (j + 1)))
+   	    { my_tform = ngph->tok[i].value.s;
+   	    }
+                }
+         else if (1 == sscanf(ngph->tok[i].name, "TTYPE%d%c", &ngph_i, &ngph_ctmp))
+           { if ((NGP_TTYPE_STRING == ngph->tok[i].type) && (ngph_i == (j + 1)))
+               { my_ttype = ngph->tok[i].value.s;
+               }
+           }
+         
+         if ((NULL != my_tform) && (my_ttype[0])) break;
+         
+         if (i < (ngph->tokcnt - 1)) continue;
+         exitflg = 1;
+         break;
+       }
+      if ((NGP_OK == r) && (NULL != my_tform))
+        fits_insert_col(ff, j + 1, my_ttype, my_tform, &r);
+
+      if ((NGP_OK != r) || exitflg) break;
+    }
+   return(r);
+ }
+
+	/* read complete HDU */
+
+int	ngp_read_xtension(fitsfile *ff, int parent_hn, int simple_mode)
+ { int		r, exflg, l, my_hn, tmp0, incrementor_index, i, j;
+   int		ngph_dim, ngph_bitpix, ngph_node_type, my_version;
+   char		incrementor_name[NGP_MAX_STRING], ngph_ctmp;
+   char 	*ngph_extname = 0;
+   long		ngph_size[NGP_MAX_ARRAY_DIM];
+   NGP_HDU	ngph;
+   long		lv;
+
+   incrementor_name[0] = 0;			/* signal no keyword+'#' found yet */
+   incrementor_index = 0;
+
+   if (NGP_OK != (r = ngp_hdu_init(&ngph))) return(r);
+
+   if (NGP_OK != (r = ngp_read_line(0))) return(r);	/* EOF always means error here */
+   switch (NGP_XTENSION_SIMPLE & simple_mode)
+     {
+       case 0:  if (NGP_TOKEN_XTENSION != ngp_keyidx) return(NGP_TOKEN_NOT_EXPECT);
+		break;
+       default:	if (NGP_TOKEN_SIMPLE != ngp_keyidx) return(NGP_TOKEN_NOT_EXPECT);
+		break;
+     }
+       	
+   if (NGP_OK != (r = ngp_hdu_insert_token(&ngph, &ngp_linkey))) return(r);
+
+   for (;;)
+    { if (NGP_OK != (r = ngp_read_line(0))) return(r);	/* EOF always means error here */
+      exflg = 0;
+      switch (ngp_keyidx)
+       { 
+	 case NGP_TOKEN_SIMPLE:
+	 		r = NGP_TOKEN_NOT_EXPECT;
+			break;
+	 		                        
+	 case NGP_TOKEN_END:
+         case NGP_TOKEN_XTENSION:
+         case NGP_TOKEN_GROUP:
+         		r = ngp_unread_line();	/* WARNING - not break here .... */
+         case NGP_TOKEN_EOF:
+			exflg = 1;
+ 			break;
+
+         default:	l = strlen(ngp_linkey.name);
+			if ((l >= 2) && (l <= 6))
+			  { if ('#' == ngp_linkey.name[l - 1])
+			      { if (0 == incrementor_name[0])
+			          { memcpy(incrementor_name, ngp_linkey.name, l - 1);
+			            incrementor_name[l - 1] = 0;
+			          }
+			        if (((l - 1) == (int)strlen(incrementor_name)) && (0 == memcmp(incrementor_name, ngp_linkey.name, l - 1)))
+			          { incrementor_index++;
+			          }
+			        sprintf(ngp_linkey.name + l - 1, "%d", incrementor_index);
+			      }
+			  }
+			r = ngp_hdu_insert_token(&ngph, &ngp_linkey);
+ 			break;
+       }
+      if ((NGP_OK != r) || exflg) break;
+    }
+
+   if (NGP_OK == r)
+     { 				/* we should scan keywords, and calculate HDU's */
+				/* structure ourselves .... */
+
+       ngph_node_type = NGP_NODE_INVALID;	/* init variables */
+       ngph_bitpix = 0;
+       ngph_extname = NULL;
+       for (i=0; i<NGP_MAX_ARRAY_DIM; i++) ngph_size[i] = 0;
+       ngph_dim = 0;
+
+       for (i=0; i<ngph.tokcnt; i++)
+        { if (!strcmp("XTENSION", ngph.tok[i].name))
+            { if (NGP_TTYPE_STRING == ngph.tok[i].type)
+                { if (!ngp_strcasencmp("BINTABLE", ngph.tok[i].value.s,8)) ngph_node_type = NGP_NODE_BTABLE;
+                  if (!ngp_strcasencmp("TABLE", ngph.tok[i].value.s,5)) ngph_node_type = NGP_NODE_ATABLE;
+                  if (!ngp_strcasencmp("IMAGE", ngph.tok[i].value.s,5)) ngph_node_type = NGP_NODE_IMAGE;
+                }
+            }
+          else if (!strcmp("SIMPLE", ngph.tok[i].name))
+            { if (NGP_TTYPE_BOOL == ngph.tok[i].type)
+                { if (ngph.tok[i].value.b) ngph_node_type = NGP_NODE_IMAGE;
+                }
+            }
+          else if (!strcmp("BITPIX", ngph.tok[i].name))
+            { if (NGP_TTYPE_INT == ngph.tok[i].type)  ngph_bitpix = ngph.tok[i].value.i;
+            }
+          else if (!strcmp("NAXIS", ngph.tok[i].name))
+            { if (NGP_TTYPE_INT == ngph.tok[i].type)  ngph_dim = ngph.tok[i].value.i;
+            }
+          else if (!strcmp("EXTNAME", ngph.tok[i].name))	/* assign EXTNAME, I hope struct does not move */
+            { if (NGP_TTYPE_STRING == ngph.tok[i].type)  ngph_extname = ngph.tok[i].value.s;
+            }
+          else if (1 == sscanf(ngph.tok[i].name, "NAXIS%d%c", &j, &ngph_ctmp))
+            { if (NGP_TTYPE_INT == ngph.tok[i].type)
+		if ((j>=1) && (j <= NGP_MAX_ARRAY_DIM))
+		  { ngph_size[j - 1] = ngph.tok[i].value.i;
+		  }
+            }
+        }
+
+       switch (ngph_node_type)
+        { case NGP_NODE_IMAGE:
+			if (NGP_XTENSION_FIRST == ((NGP_XTENSION_FIRST | NGP_XTENSION_SIMPLE) & simple_mode))
+			  { 		/* if caller signals that this is 1st HDU in file */
+					/* and it is IMAGE defined with XTENSION, then we */
+					/* need create dummy Primary HDU */			  
+			    fits_create_img(ff, 16, 0, NULL, &r);
+			  }
+					/* create image */
+			fits_create_img(ff, ngph_bitpix, ngph_dim, ngph_size, &r);
+
+					/* update keywords */
+			if (NGP_OK == r)  r = ngp_keyword_all_write(&ngph, ff, NGP_NON_SYSTEM_ONLY);
+			break;
+
+          case NGP_NODE_ATABLE:
+          case NGP_NODE_BTABLE:
+					/* create table, 0 rows and 0 columns for the moment */
+			fits_create_tbl(ff, ((NGP_NODE_ATABLE == ngph_node_type)
+					     ? ASCII_TBL : BINARY_TBL),
+					0, 0, NULL, NULL, NULL, NULL, &r);
+			if (NGP_OK != r) break;
+
+					/* add columns ... */
+			r = ngp_append_columns(ff, &ngph, 0);
+			if (NGP_OK != r) break;
+
+					/* add remaining keywords */
+			r = ngp_keyword_all_write(&ngph, ff, NGP_NON_SYSTEM_ONLY);
+			if (NGP_OK != r) break;
+
+					/* if requested add rows */
+			if (ngph_size[1] > 0) fits_insert_rows(ff, 0, ngph_size[1], &r);
+			break;
+
+	  default:	r = NGP_BAD_ARG;
+	  		break;
+	}
+
+     }
+
+   if ((NGP_OK == r) && (NULL != ngph_extname))
+     { r = ngp_get_extver(ngph_extname, &my_version);	/* write correct ext version number */
+       lv = my_version;		/* bugfix - 22-Jan-99, BO - nonalignment of OSF/Alpha */
+       fits_write_key(ff, TLONG, "EXTVER", &lv, "auto assigned by template parser", &r); 
+     }
+
+   if (NGP_OK == r)
+     { if (parent_hn > 0)
+         { fits_get_hdu_num(ff, &my_hn);
+           fits_movabs_hdu(ff, parent_hn, &tmp0, &r);	/* link us to parent */
+           fits_add_group_member(ff, NULL, my_hn, &r);
+           fits_movabs_hdu(ff, my_hn, &tmp0, &r);
+           if (NGP_OK != r) return(r);
+         }
+     }
+
+   if (NGP_OK != r)					/* in case of error - delete hdu */
+     { tmp0 = 0;
+       fits_delete_hdu(ff, NULL, &tmp0);
+     }
+
+   ngp_hdu_clear(&ngph);
+   return(r);
+ }
+
+	/* read complete GROUP */
+
+int	ngp_read_group(fitsfile *ff, char *grpname, int parent_hn)
+ { int		r, exitflg, l, my_hn, tmp0, incrementor_index;
+   char		grnm[NGP_MAX_STRING];			/* keyword holding group name */
+   char		incrementor_name[NGP_MAX_STRING];
+   NGP_HDU	ngph;
+
+   incrementor_name[0] = 0;			/* signal no keyword+'#' found yet */
+   incrementor_index = 6;			/* first 6 cols are used by group */
+
+   ngp_grplevel++;
+   if (NGP_OK != (r = ngp_hdu_init(&ngph))) return(r);
+
+   r = NGP_OK;
+   if (NGP_OK != (r = fits_create_group(ff, grpname, GT_ID_ALL_URI, &r))) return(r);
+   fits_get_hdu_num(ff, &my_hn);
+   if (parent_hn > 0)
+     { fits_movabs_hdu(ff, parent_hn, &tmp0, &r);	/* link us to parent */
+       fits_add_group_member(ff, NULL, my_hn, &r);
+       fits_movabs_hdu(ff, my_hn, &tmp0, &r);
+       if (NGP_OK != r) return(r);
+     }
+
+   for (exitflg = 0; 0 == exitflg;)
+    { if (NGP_OK != (r = ngp_read_line(0))) break;	/* EOF always means error here */
+      switch (ngp_keyidx)
+       {
+	 case NGP_TOKEN_SIMPLE:
+	 case NGP_TOKEN_EOF:
+			r = NGP_TOKEN_NOT_EXPECT;
+			break;
+
+         case NGP_TOKEN_END:
+         		ngp_grplevel--;
+			exitflg = 1;
+			break;
+
+         case NGP_TOKEN_GROUP:
+			if (NGP_TTYPE_STRING == ngp_linkey.type)
+			  { strncpy(grnm, ngp_linkey.value.s, NGP_MAX_STRING);
+			  }
+			else
+			  { sprintf(grnm, "DEFAULT_GROUP_%d", master_grp_idx++);
+			  }
+			grnm[NGP_MAX_STRING - 1] = 0;
+			r = ngp_read_group(ff, grnm, my_hn);
+			break;			/* we can have many subsequent GROUP defs */
+
+         case NGP_TOKEN_XTENSION:
+         		r = ngp_unread_line();
+         		if (NGP_OK != r) break;
+         		r = ngp_read_xtension(ff, my_hn, 0);
+			break;			/* we can have many subsequent HDU defs */
+
+         default:	l = strlen(ngp_linkey.name);
+			if ((l >= 2) && (l <= 6))
+			  { if ('#' == ngp_linkey.name[l - 1])
+			      { if (0 == incrementor_name[0])
+			          { memcpy(incrementor_name, ngp_linkey.name, l - 1);
+			            incrementor_name[l - 1] = 0;
+			          }
+			        if (((l - 1) == (int)strlen(incrementor_name)) && (0 == memcmp(incrementor_name, ngp_linkey.name, l - 1)))
+			          { incrementor_index++;
+			          }
+			        sprintf(ngp_linkey.name + l - 1, "%d", incrementor_index);
+			      }
+			  }
+         		r = ngp_hdu_insert_token(&ngph, &ngp_linkey); 
+			break;			/* here we can add keyword */
+       }
+      if (NGP_OK != r) break;
+    }
+
+   fits_movabs_hdu(ff, my_hn, &tmp0, &r);	/* back to our HDU */
+
+   if (NGP_OK == r)				/* create additional columns, if requested */
+     r = ngp_append_columns(ff, &ngph, 6);
+
+   if (NGP_OK == r)				/* and write keywords */
+     r = ngp_keyword_all_write(&ngph, ff, NGP_NON_SYSTEM_ONLY);
+
+   if (NGP_OK != r)			/* delete group in case of error */
+     { tmp0 = 0;
+       fits_remove_group(ff, OPT_RM_GPT, &tmp0);
+     }
+
+   ngp_hdu_clear(&ngph);		/* we are done with this HDU, so delete it */
+   return(r);
+ }
+
+		/* top level API functions */
+
+/* read whole template. ff should point to the opened empty fits file. */
+
+int	fits_execute_template(fitsfile *ff, char *ngp_template, int *status)
+ { int		r, exit_flg, first_extension, i, my_hn, tmp0, keys_exist, more_keys, used_ver;
+   char		grnm[NGP_MAX_STRING], used_name[NGP_MAX_STRING];
+   long		luv;
+
+   if (NULL == status) return(NGP_NUL_PTR);
+   if (NGP_OK != *status) return(*status);
+
+   if ((NULL == ff) || (NULL == ngp_template))
+     { *status = NGP_NUL_PTR;
+       return(*status);
+     }
+
+   ngp_inclevel = 0;				/* initialize things, not all should be zero */
+   ngp_grplevel = 0;
+   master_grp_idx = 1;
+   exit_flg = 0;
+   ngp_master_dir[0] = 0;			/* this should be before 1st call to ngp_include_file */
+   first_extension = 1;				/* we need to create PHDU */
+
+   if (NGP_OK != (r = ngp_delete_extver_tab()))
+     { *status = r;
+       return(r);
+     }
+
+   fits_get_hdu_num(ff, &my_hn);		/* our HDU position */
+   if (my_hn <= 1)				/* check whether we really need to create PHDU */
+     { fits_movabs_hdu(ff, 1, &tmp0, status);
+       fits_get_hdrspace(ff, &keys_exist, &more_keys, status);
+       fits_movabs_hdu(ff, my_hn, &tmp0, status);
+       if (NGP_OK != *status) return(*status);	/* error here means file is corrupted */
+       if (keys_exist > 0) first_extension = 0;	/* if keywords exist assume PHDU already exist */
+     }
+   else
+     { first_extension = 0;			/* PHDU (followed by 1+ extensions) exist */
+
+       for (i = 2; i<= my_hn; i++)
+        { *status = NGP_OK;
+          fits_movabs_hdu(ff, 1, &tmp0, status);
+          if (NGP_OK != *status) break;
+
+          fits_read_key(ff, TSTRING, "EXTNAME", used_name, NULL, status);
+          if (NGP_OK != *status)  continue;
+
+          fits_read_key(ff, TLONG, "EXTVER", &luv, NULL, status);
+          used_ver = luv;			/* bugfix - 22-Jan-99, BO - nonalignment of OSF/Alpha */
+          if (VALUE_UNDEFINED == *status)
+            { used_ver = 1;
+              *status = NGP_OK;
+            }
+
+          if (NGP_OK == *status) *status = ngp_set_extver(used_name, used_ver);
+        }
+
+       fits_movabs_hdu(ff, my_hn, &tmp0, status);
+     }
+   if (NGP_OK != *status) return(*status);
+                                                                          
+   if (NGP_OK != (*status = ngp_include_file(ngp_template))) return(*status);
+
+   for (i = strlen(ngp_template) - 1; i >= 0; i--) /* strlen is > 0, otherwise fopen failed */
+    { 
+#ifdef MSDOS
+      if ('\\' == ngp_template[i]) break;
+#else
+      if ('/' == ngp_template[i]) break;
+#endif
+    } 
+      
+   i++;
+   if (i > (NGP_MAX_FNAME - 1)) i = NGP_MAX_FNAME - 1;
+
+   if (i > 0)
+     { memcpy(ngp_master_dir, ngp_template, i);
+       ngp_master_dir[i] = 0;
+     }
+
+
+   for (;;)
+    { if (NGP_OK != (r = ngp_read_line(1))) break;	/* EOF always means error here */
+      switch (ngp_keyidx)
+       {
+         case NGP_TOKEN_SIMPLE:
+			if (0 == first_extension)	/* simple only allowed in first HDU */
+			  { r = NGP_TOKEN_NOT_EXPECT;
+			    break;
+			  }
+			if (NGP_OK != (r = ngp_unread_line())) break;
+			r = ngp_read_xtension(ff, 0, NGP_XTENSION_SIMPLE | NGP_XTENSION_FIRST);
+			first_extension = 0;
+			break;
+
+         case NGP_TOKEN_XTENSION:
+			if (NGP_OK != (r = ngp_unread_line())) break;
+			r = ngp_read_xtension(ff, 0, (first_extension ? NGP_XTENSION_FIRST : 0));
+			first_extension = 0;
+			break;
+
+         case NGP_TOKEN_GROUP:
+			if (NGP_TTYPE_STRING == ngp_linkey.type)
+			  { strncpy(grnm, ngp_linkey.value.s, NGP_MAX_STRING); }
+			else
+			  { sprintf(grnm, "DEFAULT_GROUP_%d", master_grp_idx++); }
+			grnm[NGP_MAX_STRING - 1] = 0;
+			r = ngp_read_group(ff, grnm, 0);
+			first_extension = 0;
+			break;
+
+	 case NGP_TOKEN_EOF:
+			exit_flg = 1;
+			break;
+
+         default:	r = NGP_TOKEN_NOT_EXPECT;
+			break;
+       }
+      if (exit_flg || (NGP_OK != r)) break;
+    }
+
+/* all top level HDUs up to faulty one are left intact in case of i/o error. It is up
+   to the caller to call fits_close_file or fits_delete_file when this function returns
+   error. */
+
+   ngp_free_line();		/* deallocate last line (if any) */
+   ngp_free_prevline();		/* deallocate cached line (if any) */
+   ngp_delete_extver_tab();	/* delete extver table (if present), error ignored */
+   
+   *status = r;
+   return(r);
+ }
diff --git a/src/plugins/cfitsio/grparser.h b/src/plugins/cfitsio/grparser.h
new file mode 100644
index 0000000..56bdea0
--- /dev/null
+++ b/src/plugins/cfitsio/grparser.h
@@ -0,0 +1,185 @@
+/*		T E M P L A T E   P A R S E R   H E A D E R   F I L E
+		=====================================================
+
+		by Jerzy Borkowski obs unige ch
+
+		Integral Science Data Center
+		ch. d'Ecogia 16
+		1290 Versoix
+		Switzerland
+
+14-Oct-98: initial release
+16-Oct-98: reference to fitsio.h removed, also removed strings after #endif
+		directives to make gcc -Wall not to complain
+20-Oct-98: added declarations NGP_XTENSION_SIMPLE and NGP_XTENSION_FIRST
+24-Oct-98: prototype of ngp_read_line() function updated.
+22-Jan-99: prototype for ngp_set_extver() function added.
+20-Jun-2002 Wm Pence, added support for the HIERARCH keyword convention
+            (changed NGP_MAX_NAME from (20) to FLEN_KEYWORD)
+*/
+
+#ifndef	GRPARSER_H_INCLUDED
+#define	GRPARSER_H_INCLUDED
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+	/* error codes  - now defined in fitsio.h */
+
+	/* common constants definitions */
+
+#define	NGP_ALLOCCHUNK		(1000)
+#define	NGP_MAX_INCLUDE		(10)			/* include file nesting limit */
+#define	NGP_MAX_COMMENT		(80)			/* max size for comment */
+#define	NGP_MAX_NAME		FLEN_KEYWORD		/* max size for KEYWORD (FITS limits it to 8 chars) */
+                                                        /* except HIERARCH can have longer effective keyword names */
+#define	NGP_MAX_STRING		(80)			/* max size for various strings */
+#define	NGP_MAX_ARRAY_DIM	(999)			/* max. number of dimensions in array */
+#define NGP_MAX_FNAME           (1000)                  /* max size of combined path+fname */
+#define	NGP_MAX_ENVFILES	(10000)			/* max size of CFITSIO_INCLUDE_FILES env. variable */
+
+#define	NGP_TOKEN_UNKNOWN	(-1)			/* token type unknown */
+#define	NGP_TOKEN_INCLUDE	(0)			/* \INCLUDE token */
+#define	NGP_TOKEN_GROUP		(1)			/* \GROUP token */
+#define	NGP_TOKEN_END		(2)			/* \END token */
+#define	NGP_TOKEN_XTENSION	(3)			/* XTENSION token */
+#define	NGP_TOKEN_SIMPLE	(4)			/* SIMPLE token */
+#define	NGP_TOKEN_EOF		(5)			/* End Of File pseudo token */
+
+#define	NGP_TTYPE_UNKNOWN	(0)			/* undef (yet) token type - invalid to print/write to disk */
+#define	NGP_TTYPE_BOOL		(1)			/* boolean, it is 'T' or 'F' */
+#define	NGP_TTYPE_STRING	(2)			/* something withing "" or starting with letter */
+#define	NGP_TTYPE_INT		(3)			/* starting with digit and not with '.' */
+#define	NGP_TTYPE_REAL		(4)			/* digits + '.' */
+#define	NGP_TTYPE_COMPLEX	(5)			/* 2 reals, separated with ',' */
+#define	NGP_TTYPE_NULL		(6)			/* NULL token, format is : NAME = / comment */
+#define	NGP_TTYPE_RAW		(7)			/* HISTORY/COMMENT/8SPACES + comment string without / */
+
+#define	NGP_FOUND_EQUAL_SIGN	(1)			/* line contains '=' after keyword name */
+
+#define	NGP_FORMAT_OK		(0)			/* line format OK */
+#define	NGP_FORMAT_ERROR	(1)			/* line format error */
+
+#define	NGP_NODE_INVALID	(0)			/* default node type - invalid (to catch errors) */
+#define	NGP_NODE_IMAGE		(1)			/* IMAGE type */
+#define	NGP_NODE_ATABLE		(2)			/* ASCII table type */
+#define	NGP_NODE_BTABLE		(3)			/* BINARY table type */
+
+#define	NGP_NON_SYSTEM_ONLY	(0)			/* save all keywords except NAXIS,BITPIX,etc.. */
+#define	NGP_REALLY_ALL		(1)			/* save really all keywords */
+
+#define	NGP_XTENSION_SIMPLE	(1)			/* HDU defined with SIMPLE T */
+#define	NGP_XTENSION_FIRST	(2)			/* this is first extension in template */
+
+#define	NGP_LINE_REREAD		(1)			/* reread line */
+
+#define	NGP_BITPIX_INVALID	(-12345)		/* default BITPIX (to catch errors) */
+
+	/* common macro definitions */
+
+#ifdef	NGP_PARSER_DEBUG_MALLOC
+
+#define	ngp_alloc(x)		dal_malloc(x)
+#define	ngp_free(x)		dal_free(x)
+#define	ngp_realloc(x,y)	dal_realloc(x,y)
+
+#else
+
+#define	ngp_alloc(x)		malloc(x)
+#define	ngp_free(x)		free(x)
+#define	ngp_realloc(x,y)	realloc(x,y)
+
+#endif
+
+	/* type definitions */
+
+typedef struct NGP_RAW_LINE_STRUCT
+      {	char	*line;
+	char	*name;
+	char	*value;
+	int	type;
+	char	*comment;
+	int	format;
+	int	flags;
+      } NGP_RAW_LINE;
+
+
+typedef union NGP_TOKVAL_UNION
+      {	char	*s;		/* space allocated separately, be careful !!! */
+	char	b;
+	int	i;
+	double	d;
+	struct NGP_COMPLEX_STRUCT
+	 { double re;
+	   double im;
+	 } c;			/* complex value */
+      } NGP_TOKVAL;
+
+
+typedef struct NGP_TOKEN_STRUCT
+      { int		type;
+        char		name[NGP_MAX_NAME];
+        NGP_TOKVAL	value;
+        char		comment[NGP_MAX_COMMENT];
+      } NGP_TOKEN;
+
+
+typedef struct NGP_HDU_STRUCT
+      {	int		tokcnt;
+        NGP_TOKEN	*tok;
+      } NGP_HDU;
+
+
+typedef struct NGP_TKDEF_STRUCT
+      {	char	*name;
+	int	code;
+      } NGP_TKDEF;
+
+
+typedef struct NGP_EXTVER_TAB_STRUCT
+      {	char	*extname;
+	int	version;
+      } NGP_EXTVER_TAB;
+
+
+	/* globally visible variables declarations */
+
+extern	NGP_RAW_LINE	ngp_curline;
+extern	NGP_RAW_LINE	ngp_prevline;
+
+extern	int		ngp_extver_tab_size;
+extern	NGP_EXTVER_TAB	*ngp_extver_tab;
+
+
+	/* globally visible functions declarations */
+
+int	ngp_get_extver(char *extname, int *version);
+int	ngp_set_extver(char *extname, int version);
+int	ngp_delete_extver_tab(void);
+int	ngp_strcasecmp(char *p1, char *p2);
+int	ngp_strcasencmp(char *p1, char *p2, int n);
+int	ngp_line_from_file(FILE *fp, char **p);
+int	ngp_free_line(void);
+int	ngp_free_prevline(void);
+int	ngp_read_line_buffered(FILE *fp);
+int	ngp_unread_line(void);
+int	ngp_extract_tokens(NGP_RAW_LINE *cl);
+int	ngp_include_file(char *fname);
+int	ngp_read_line(int ignore_blank_lines);
+int	ngp_keyword_is_write(NGP_TOKEN *ngp_tok);
+int     ngp_keyword_all_write(NGP_HDU *ngph, fitsfile *ffp, int mode);
+int	ngp_hdu_init(NGP_HDU *ngph);
+int	ngp_hdu_clear(NGP_HDU *ngph);
+int	ngp_hdu_insert_token(NGP_HDU *ngph, NGP_TOKEN *newtok);
+int	ngp_append_columns(fitsfile *ff, NGP_HDU *ngph, int aftercol);
+int	ngp_read_xtension(fitsfile *ff, int parent_hn, int simple_mode);
+int	ngp_read_group(fitsfile *ff, char *grpname, int parent_hn);
+
+		/* top level API function - now defined in fitsio.h */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/plugins/cfitsio/histo.c b/src/plugins/cfitsio/histo.c
new file mode 100644
index 0000000..6af856f
--- /dev/null
+++ b/src/plugins/cfitsio/histo.c
@@ -0,0 +1,2221 @@
+/*   Globally defined histogram parameters */
+#include <string.h>
+#include <ctype.h>
+#include <math.h>
+#include <stdlib.h>
+#include "fitsio2.h"
+
+typedef struct {  /*  Structure holding all the histogramming information   */
+   union {        /*  the iterator work functions (ffwritehist, ffcalchist) */
+      char   *b;  /*  need to do their job... passed via *userPointer.      */
+      short  *i;
+      int    *j;
+      float  *r;
+      double *d;
+   } hist;
+
+   fitsfile *tblptr;
+
+   int   haxis, hcolnum[4], himagetype;
+   long  haxis1, haxis2, haxis3, haxis4;
+   float amin1, amin2, amin3, amin4;
+   float maxbin1, maxbin2, maxbin3, maxbin4;
+   float binsize1, binsize2, binsize3, binsize4;
+   int   wtrecip, wtcolnum;
+   float weight;
+   char  *rowselector;
+
+} histType;
+
+/*--------------------------------------------------------------------------*/
+int ffbins(char *binspec,   /* I - binning specification */
+                   int *imagetype,      /* O - image type, TINT or TSHORT */
+                   int *histaxis,       /* O - no. of axes in the histogram */
+                   char colname[4][FLEN_VALUE],  /* column name for axis */
+                   double *minin,        /* minimum value for each axis */
+                   double *maxin,        /* maximum value for each axis */
+                   double *binsizein,    /* size of bins on each axis */
+                   char minname[4][FLEN_VALUE],  /* keyword name for min */
+                   char maxname[4][FLEN_VALUE],  /* keyword name for max */
+                   char binname[4][FLEN_VALUE],  /* keyword name for binsize */
+                   double *wt,          /* weighting factor          */
+                   char *wtname,        /* keyword or column name for weight */
+                   int *recip,          /* the reciprocal of the weight? */
+                   int *status)
+{
+/*
+   Parse the input binning specification string, returning the binning
+   parameters.  Supports up to 4 dimensions.  The binspec string has
+   one of these forms:
+
+   bin binsize                  - 2D histogram with binsize on each axis
+   bin xcol                     - 1D histogram on column xcol
+   bin (xcol, ycol) = binsize   - 2D histogram with binsize on each axis
+   bin x=min:max:size, y=min:max:size, z..., t... 
+   bin x=:max, y=::size
+   bin x=size, y=min::size
+
+   most other reasonable combinations are supported.        
+*/
+    int ii, slen, defaulttype;
+    char *ptr, tmpname[30], *file_expr = NULL;
+    double  dummy;
+
+    if (*status > 0)
+         return(*status);
+
+    /* set the default values */
+    *histaxis = 2;
+    *imagetype = TINT;
+    defaulttype = 1;
+    *wt = 1.;
+    *recip = 0;
+    *wtname = '\0';
+
+    /* set default values */
+    for (ii = 0; ii < 4; ii++)
+    {
+        *colname[ii] = '\0';
+        *minname[ii] = '\0';
+        *maxname[ii] = '\0';
+        *binname[ii] = '\0';
+        minin[ii] = DOUBLENULLVALUE;  /* undefined values */
+        maxin[ii] = DOUBLENULLVALUE;
+        binsizein[ii] = DOUBLENULLVALUE;
+    }
+
+    ptr = binspec + 3;  /* skip over 'bin' */
+
+    if (*ptr == 'i' )  /* bini */
+    {
+        *imagetype = TSHORT;
+        defaulttype = 0;
+        ptr++;
+    }
+    else if (*ptr == 'j' )  /* binj; same as default */
+    {
+        defaulttype = 0;
+        ptr ++;
+    }
+    else if (*ptr == 'r' )  /* binr */
+    {
+        *imagetype = TFLOAT;
+        defaulttype = 0;
+        ptr ++;
+    }
+    else if (*ptr == 'd' )  /* bind */
+    {
+        *imagetype = TDOUBLE;
+        defaulttype = 0;
+        ptr ++;
+    }
+    else if (*ptr == 'b' )  /* binb */
+    {
+        *imagetype = TBYTE;
+        defaulttype = 0;
+        ptr ++;
+    }
+
+    if (*ptr == '\0')  /* use all defaults for other parameters */
+        return(*status);
+    else if (*ptr != ' ')  /* must be at least one blank */
+    {
+        ffpmsg("binning specification syntax error:");
+        ffpmsg(binspec);
+        return(*status = URL_PARSE_ERROR);
+    }
+
+    while (*ptr == ' ')  /* skip over blanks */
+           ptr++;
+
+    if (*ptr == '\0')   /* no other parameters; use defaults */
+        return(*status);
+
+    /* Check if need to import expression from a file */
+
+    if( *ptr=='@' ) {
+       if( ffimport_file( ptr+1, &file_expr, status ) ) return(*status);
+       ptr = file_expr;
+       while (*ptr == ' ')
+               ptr++;       /* skip leading white space... again */
+    }
+
+    if (*ptr == '(' )
+    {
+        /* this must be the opening parenthesis around a list of column */
+        /* names, optionally followed by a '=' and the binning spec. */
+
+        for (ii = 0; ii < 4; ii++)
+        {
+            ptr++;               /* skip over the '(', ',', or ' ') */
+            while (*ptr == ' ')  /* skip over blanks */
+                ptr++;
+
+            slen = strcspn(ptr, " ,)");
+            strncat(colname[ii], ptr, slen); /* copy 1st column name */
+
+            ptr += slen;
+            while (*ptr == ' ')  /* skip over blanks */
+                ptr++;
+
+            if (*ptr == ')' )   /* end of the list of names */
+            {
+                *histaxis = ii + 1;
+                break;
+            }
+        }
+
+        if (ii == 4)   /* too many names in the list , or missing ')'  */
+        {
+            ffpmsg(
+ "binning specification has too many column names or is missing closing ')':");
+            ffpmsg(binspec);
+	    if( file_expr ) free( file_expr );
+            return(*status = URL_PARSE_ERROR);
+        }
+
+        ptr++;  /* skip over the closing parenthesis */
+        while (*ptr == ' ')  /* skip over blanks */
+            ptr++;
+
+        if (*ptr == '\0') {
+	    if( file_expr ) free( file_expr );
+            return(*status);  /* parsed the entire string */
+	}
+
+        else if (*ptr != '=')  /* must be an equals sign now*/
+        {
+            ffpmsg("illegal binning specification in URL:");
+            ffpmsg(" an equals sign '=' must follow the column names");
+            ffpmsg(binspec);
+	    if( file_expr ) free( file_expr );
+            return(*status = URL_PARSE_ERROR);
+        }
+
+        ptr++;  /* skip over the equals sign */
+        while (*ptr == ' ')  /* skip over blanks */
+            ptr++;
+
+        /* get the single range specification for all the columns */
+        ffbinr(&ptr, tmpname, minin,
+                                     maxin, binsizein, minname[0],
+                                     maxname[0], binname[0], status);
+        if (*status > 0)
+        {
+            ffpmsg("illegal binning specification in URL:");
+            ffpmsg(binspec);
+	    if( file_expr ) free( file_expr );
+            return(*status);
+        }
+
+        for (ii = 1; ii < *histaxis; ii++)
+        {
+            minin[ii] = minin[0];
+            maxin[ii] = maxin[0];
+            binsizein[ii] = binsizein[0];
+            strcpy(minname[ii], minname[0]);
+            strcpy(maxname[ii], maxname[0]);
+            strcpy(binname[ii], binname[0]);
+        }
+
+        while (*ptr == ' ')  /* skip over blanks */
+            ptr++;
+
+        if (*ptr == ';')
+            goto getweight;   /* a weighting factor is specified */
+
+        if (*ptr != '\0')  /* must have reached end of string */
+        {
+            ffpmsg("illegal syntax after binning range specification in URL:");
+            ffpmsg(binspec);
+	    if( file_expr ) free( file_expr );
+            return(*status = URL_PARSE_ERROR);
+        }
+
+        return(*status);
+    }             /* end of case with list of column names in ( )  */
+
+    /* if we've reached this point, then the binning specification */
+    /* must be of the form: XCOL = min:max:binsize, YCOL = ...     */
+    /* where the column name followed by '=' are optional.         */
+    /* If the column name is not specified, then use the default name */
+
+    for (ii = 0; ii < 4; ii++) /* allow up to 4 histogram dimensions */
+    {
+        ffbinr(&ptr, colname[ii], &minin[ii],
+                                     &maxin[ii], &binsizein[ii], minname[ii],
+                                     maxname[ii], binname[ii], status);
+
+        if (*status > 0)
+        {
+            ffpmsg("illegal syntax in binning range specification in URL:");
+            ffpmsg(binspec);
+	    if( file_expr ) free( file_expr );
+            return(*status);
+        }
+
+        if (*ptr == '\0' || *ptr == ';')
+            break;        /* reached the end of the string */
+
+        if (*ptr == ' ')
+        {
+            while (*ptr == ' ')  /* skip over blanks */
+                ptr++;
+
+            if (*ptr == '\0' || *ptr == ';')
+                break;        /* reached the end of the string */
+
+            if (*ptr == ',')
+                ptr++;  /* comma separates the next column specification */
+        }
+        else if (*ptr == ',')
+        {          
+            ptr++;  /* comma separates the next column specification */
+        }
+        else
+        {
+            ffpmsg("illegal characters following binning specification in URL:");
+            ffpmsg(binspec);
+	    if( file_expr ) free( file_expr );
+            return(*status = URL_PARSE_ERROR);
+        }
+    }
+
+    if (ii == 4)
+    {
+        /* there are yet more characters in the string */
+        ffpmsg("illegal binning specification in URL:");
+        ffpmsg("apparently greater than 4 histogram dimensions");
+        ffpmsg(binspec);
+        return(*status = URL_PARSE_ERROR);
+    }
+    else
+        *histaxis = ii + 1;
+
+    /* special case: if a single number was entered it should be      */
+    /* interpreted as the binning factor for the default X and Y axes */
+
+    if (*histaxis == 1 && *colname[0] == '\0' && 
+         minin[0] == DOUBLENULLVALUE && maxin[0] == DOUBLENULLVALUE)
+    {
+        *histaxis = 2;
+        binsizein[1] = binsizein[0];
+    }
+
+getweight:
+    if (*ptr == ';')  /* looks like a weighting factor is given */
+    {
+        ptr++;
+       
+        while (*ptr == ' ')  /* skip over blanks */
+            ptr++;
+
+        recip = 0;
+        if (*ptr == '/')
+        {
+            *recip = 1;  /* the reciprocal of the weight is entered */
+            ptr++;
+
+            while (*ptr == ' ')  /* skip over blanks */
+                ptr++;
+        }
+
+        /* parse the weight as though it were a binrange. */
+        /* either a column name or a numerical value will be returned */
+
+        ffbinr(&ptr, wtname, &dummy, &dummy, wt, tmpname,
+                                     tmpname, tmpname, status);
+
+        if (*status > 0)
+        {
+            ffpmsg("illegal binning weight specification in URL:");
+            ffpmsg(binspec);
+	    if( file_expr ) free( file_expr );
+            return(*status);
+        }
+
+        /* creat a float datatype histogram by default, if weight */
+        /* factor is not = 1.0  */
+
+        if ( (defaulttype && *wt != 1.0) || (defaulttype && *wtname) )
+            *imagetype = TFLOAT;
+    }
+
+    while (*ptr == ' ')  /* skip over blanks */
+         ptr++;
+
+    if (*ptr != '\0')  /* should have reached the end of string */
+    {
+        ffpmsg("illegal syntax after binning weight specification in URL:");
+        ffpmsg(binspec);
+        *status = URL_PARSE_ERROR;
+    }
+
+    if( file_expr ) free( file_expr );
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffbinr(char **ptr, 
+                   char *colname, 
+                   double *minin,
+                   double *maxin, 
+                   double *binsizein,
+                   char *minname,
+                   char *maxname,
+                   char *binname,
+                   int *status)
+/*
+   Parse the input binning range specification string, returning 
+   the column name, histogram min and max values, and bin size.
+*/
+{
+    int slen, isanumber;
+    char token[FLEN_VALUE];
+
+    if (*status > 0)
+        return(*status);
+
+    slen = fits_get_token(ptr, " ,=:;", token, &isanumber); /* get 1st token */
+
+    if (slen == 0 && (**ptr == '\0' || **ptr == ',' || **ptr == ';') )
+        return(*status);   /* a null range string */
+
+    if (!isanumber && **ptr != ':')
+    {
+        /* this looks like the column name */
+
+        if (token[0] == '#' && isdigit((int) token[1]) )
+        {
+            /* omit the leading '#' in the column number */
+            strcpy(colname, token+1);
+        }
+        else
+            strcpy(colname, token);
+
+        while (**ptr == ' ')  /* skip over blanks */
+             (*ptr)++;
+
+        if (**ptr != '=')
+            return(*status);  /* reached the end */
+
+        (*ptr)++;   /* skip over the = sign */
+
+        while (**ptr == ' ')  /* skip over blanks */
+             (*ptr)++;
+
+        slen = fits_get_token(ptr, " ,:;", token, &isanumber); /* get token */
+    }
+
+    if (**ptr != ':')
+    {
+        /* this is the first token, and since it is not followed by */
+        /* a ':' this must be the binsize token */
+        if (!isanumber)
+            strcpy(binname, token);
+        else
+            *binsizein =  strtod(token, NULL);
+
+        return(*status);  /* reached the end */
+    }
+    else
+    {
+        /* the token contains the min value */
+        if (slen)
+        {
+            if (!isanumber)
+                strcpy(minname, token);
+            else
+                *minin = strtod(token, NULL);
+        }
+    }
+
+    (*ptr)++;  /* skip the colon between the min and max values */
+    slen = fits_get_token(ptr, " ,:;", token, &isanumber); /* get token */
+
+    /* the token contains the max value */
+    if (slen)
+    {
+        if (!isanumber)
+            strcpy(maxname, token);
+        else
+            *maxin = strtod(token, NULL);
+    }
+
+    if (**ptr != ':')
+        return(*status);  /* reached the end; no binsize token */
+
+    (*ptr)++;  /* skip the colon between the max and binsize values */
+    slen = fits_get_token(ptr, " ,:;", token, &isanumber); /* get token */
+
+    /* the token contains the binsize value */
+    if (slen)
+    {
+        if (!isanumber)
+            strcpy(binname, token);
+        else
+            *binsizein = strtod(token, NULL);
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffhist2(fitsfile **fptr,  /* IO - pointer to table with X and Y cols;    */
+                             /*     on output, points to histogram image    */
+           char *outfile,    /* I - name for the output histogram file      */
+           int imagetype,    /* I - datatype for image: TINT, TSHORT, etc   */
+           int naxis,        /* I - number of axes in the histogram image   */
+           char colname[4][FLEN_VALUE],   /* I - column names               */
+           double *minin,     /* I - minimum histogram value, for each axis */
+           double *maxin,     /* I - maximum histogram value, for each axis */
+           double *binsizein, /* I - bin size along each axis               */
+           char minname[4][FLEN_VALUE], /* I - optional keywords for min    */
+           char maxname[4][FLEN_VALUE], /* I - optional keywords for max    */
+           char binname[4][FLEN_VALUE], /* I - optional keywords for binsize */
+           double weightin,        /* I - binning weighting factor          */
+           char wtcol[FLEN_VALUE], /* I - optional keyword or col for weight*/
+           int recip,              /* I - use reciprocal of the weight?     */
+           char *selectrow,        /* I - optional array (length = no. of   */
+                             /* rows in the table).  If the element is true */
+                             /* then the corresponding row of the table will*/
+                             /* be included in the histogram, otherwise the */
+                             /* row will be skipped.  Ingnored if *selectrow*/
+                             /* is equal to NULL.                           */
+           int *status)
+{
+    fitsfile *histptr;
+    int   bitpix, colnum[4], wtcolnum;
+    long haxes[4];
+    float amin[4], amax[4], binsize[4],  weight;
+
+    if (*status > 0)
+        return(*status);
+
+    if (naxis > 4)
+    {
+        ffpmsg("histogram has more than 4 dimensions");
+        return(*status = BAD_DIMEN);
+    }
+
+    /* reset position to the correct HDU if necessary */
+    if ((*fptr)->HDUposition != ((*fptr)->Fptr)->curhdu)
+        ffmahd(*fptr, ((*fptr)->HDUposition) + 1, NULL, status);
+
+    if (imagetype == TBYTE)
+        bitpix = BYTE_IMG;
+    else if (imagetype == TSHORT)
+        bitpix = SHORT_IMG;
+    else if (imagetype == TINT)
+        bitpix = LONG_IMG;
+    else if (imagetype == TFLOAT)
+        bitpix = FLOAT_IMG;
+    else if (imagetype == TDOUBLE)
+        bitpix = DOUBLE_IMG;
+    else
+        return(*status = BAD_DATATYPE);
+
+    
+    /*    Calculate the binning parameters:    */
+    /*   columm numbers, axes length, min values,  max values, and binsizes.  */
+
+    if (fits_calc_binning(
+      *fptr, naxis, colname, minin, maxin, binsizein, minname, maxname, binname,
+      colnum,  haxes, amin, amax, binsize, status) > 0)
+    {
+        ffpmsg("failed to determine binning parameters");
+        return(*status);
+    }
+ 
+    /* get the histogramming weighting factor, if any */
+    if (*wtcol)
+    {
+        /* first, look for a keyword with the weight value */
+        if (ffgky(*fptr, TFLOAT, wtcol, &weight, NULL, status) )
+        {
+            /* not a keyword, so look for column with this name */
+            *status = 0;
+
+            /* get the column number in the table */
+            if (ffgcno(*fptr, CASEINSEN, wtcol, &wtcolnum, status) > 0)
+            {
+               ffpmsg(
+               "keyword or column for histogram weights doesn't exist: ");
+               ffpmsg(wtcol);
+               return(*status);
+            }
+
+            weight = FLOATNULLVALUE;
+        }
+    }
+    else
+        weight = (float) weightin;
+
+    if (weight <= 0. && weight != FLOATNULLVALUE)
+    {
+        ffpmsg("Illegal histogramming weighting factor <= 0.");
+        return(*status = URL_PARSE_ERROR);
+    }
+
+    if (recip && weight != FLOATNULLVALUE)
+       /* take reciprocal of weight */
+       weight = (float) (1.0 / weight);
+
+    /* size of histogram is now known, so create temp output file */
+    if (fits_create_file(&histptr, outfile, status) > 0)
+    {
+        ffpmsg("failed to create temp output file for histogram");
+        return(*status);
+    }
+
+    /* create output FITS image HDU */
+    if (ffcrim(histptr, bitpix, naxis, haxes, status) > 0)
+    {
+        ffpmsg("failed to create output histogram FITS image");
+        return(*status);
+    }
+
+    /* copy header keywords, converting pixel list WCS keywords to image WCS form */
+    if (fits_copy_pixlist2image(*fptr, histptr, 9, naxis, colnum, status) > 0)
+    {
+        ffpmsg("failed to copy pixel list keywords to new histogram header");
+        return(*status);
+    }
+
+    /* if the table columns have no WCS keywords, then write default keywords */
+    fits_write_keys_histo(*fptr, histptr, naxis, colnum, status);
+    
+    /* update the WCS keywords for the ref. pixel location, and pixel size */
+    fits_rebin_wcs(histptr, naxis, amin, binsize,  status);      
+    
+    /* now compute the output image by binning the column values */
+    if (fits_make_hist(*fptr, histptr, bitpix, naxis, haxes, colnum, amin, amax,
+        binsize, weight, wtcolnum, recip, selectrow, status) > 0)
+    {
+        ffpmsg("failed to calculate new histogram values");
+        return(*status);
+    }
+              
+    /* finally, close the original file and return ptr to the new image */
+    ffclos(*fptr, status);
+    *fptr = histptr;
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffhist(fitsfile **fptr,  /* IO - pointer to table with X and Y cols;    */
+                             /*     on output, points to histogram image    */
+           char *outfile,    /* I - name for the output histogram file      */
+           int imagetype,    /* I - datatype for image: TINT, TSHORT, etc   */
+           int naxis,        /* I - number of axes in the histogram image   */
+           char colname[4][FLEN_VALUE],   /* I - column names               */
+           double *minin,     /* I - minimum histogram value, for each axis */
+           double *maxin,     /* I - maximum histogram value, for each axis */
+           double *binsizein, /* I - bin size along each axis               */
+           char minname[4][FLEN_VALUE], /* I - optional keywords for min    */
+           char maxname[4][FLEN_VALUE], /* I - optional keywords for max    */
+           char binname[4][FLEN_VALUE], /* I - optional keywords for binsize */
+           double weightin,        /* I - binning weighting factor          */
+           char wtcol[FLEN_VALUE], /* I - optional keyword or col for weight*/
+           int recip,              /* I - use reciprocal of the weight?     */
+           char *selectrow,        /* I - optional array (length = no. of   */
+                             /* rows in the table).  If the element is true */
+                             /* then the corresponding row of the table will*/
+                             /* be included in the histogram, otherwise the */
+                             /* row will be skipped.  Ingnored if *selectrow*/
+                             /* is equal to NULL.                           */
+           int *status)
+{
+    int ii, datatype, repeat, imin, imax, ibin, bitpix, tstatus, use_datamax = 0;
+    long haxes[4];
+    fitsfile *histptr;
+    char errmsg[FLEN_ERRMSG], keyname[FLEN_KEYWORD], card[FLEN_CARD];
+    tcolumn *colptr;
+    iteratorCol imagepars[1];
+    int n_cols = 1, nkeys;
+    long  offset = 0;
+    long n_per_loop = -1;  /* force whole array to be passed at one time */
+    histType histData;    /* Structure holding histogram info for iterator */
+    
+    float amin[4], amax[4], binsize[4], maxbin[4];
+    float datamin = FLOATNULLVALUE, datamax = FLOATNULLVALUE;
+    char svalue[FLEN_VALUE];
+    double dvalue;
+    char cpref[4][FLEN_VALUE];
+    char *cptr;
+
+    if (*status > 0)
+        return(*status);
+
+    if (naxis > 4)
+    {
+        ffpmsg("histogram has more than 4 dimensions");
+        return(*status = BAD_DIMEN);
+    }
+
+    /* reset position to the correct HDU if necessary */
+    if ((*fptr)->HDUposition != ((*fptr)->Fptr)->curhdu)
+        ffmahd(*fptr, ((*fptr)->HDUposition) + 1, NULL, status);
+
+    histData.tblptr     = *fptr;
+    histData.himagetype = imagetype;
+    histData.haxis      = naxis;
+    histData.rowselector = selectrow;
+
+    if (imagetype == TBYTE)
+        bitpix = BYTE_IMG;
+    else if (imagetype == TSHORT)
+        bitpix = SHORT_IMG;
+    else if (imagetype == TINT)
+        bitpix = LONG_IMG;
+    else if (imagetype == TFLOAT)
+        bitpix = FLOAT_IMG;
+    else if (imagetype == TDOUBLE)
+        bitpix = DOUBLE_IMG;
+    else
+        return(*status = BAD_DATATYPE);
+
+    /* The CPREF keyword, if it exists, gives the preferred columns. */
+    /* Otherwise, assume "X", "Y", "Z", and "T"  */
+
+    tstatus = 0;
+    ffgky(*fptr, TSTRING, "CPREF", cpref[0], NULL, &tstatus);
+
+    if (!tstatus)
+    {
+        /* Preferred column names are given;  separate them */
+        cptr = cpref[0];
+
+        /* the first preferred axis... */
+        while (*cptr != ',' && *cptr != '\0')
+           cptr++;
+
+        if (*cptr != '\0')
+        {
+           *cptr = '\0';
+           cptr++;
+           while (*cptr == ' ')
+               cptr++;
+
+           strcpy(cpref[1], cptr);
+           cptr = cpref[1];
+
+          /* the second preferred axis... */
+          while (*cptr != ',' && *cptr != '\0')
+             cptr++;
+
+          if (*cptr != '\0')
+          {
+             *cptr = '\0';
+             cptr++;
+             while (*cptr == ' ')
+                 cptr++;
+
+             strcpy(cpref[2], cptr);
+             cptr = cpref[2];
+
+            /* the third preferred axis... */
+            while (*cptr != ',' && *cptr != '\0')
+               cptr++;
+
+            if (*cptr != '\0')
+            {
+               *cptr = '\0';
+               cptr++;
+               while (*cptr == ' ')
+                   cptr++;
+
+               strcpy(cpref[3], cptr);
+
+            }
+          }
+        }
+    }
+
+    for (ii = 0; ii < naxis; ii++)
+    {
+
+      /* get the min, max, and binsize values from keywords, if specified */
+
+      if (*minname[ii])
+      {
+         if (ffgky(*fptr, TDOUBLE, minname[ii], &minin[ii], NULL, status) )
+         {
+             ffpmsg("error reading histogramming minimum keyword");
+             ffpmsg(minname[ii]);
+             return(*status);
+         }
+      }
+
+      if (*maxname[ii])
+      {
+         if (ffgky(*fptr, TDOUBLE, maxname[ii], &maxin[ii], NULL, status) )
+         {
+             ffpmsg("error reading histogramming maximum keyword");
+             ffpmsg(maxname[ii]);
+             return(*status);
+         }
+      }
+
+      if (*binname[ii])
+      {
+         if (ffgky(*fptr, TDOUBLE, binname[ii], &binsizein[ii], NULL, status) )
+         {
+             ffpmsg("error reading histogramming binsize keyword");
+             ffpmsg(binname[ii]);
+             return(*status);
+         }
+      }
+
+      if (binsizein[ii] == 0.)
+      {
+        ffpmsg("error: histogram binsize = 0");
+        return(*status = ZERO_SCALE);
+      }
+
+      if (*colname[ii] == '\0')
+      {
+         strcpy(colname[ii], cpref[ii]); /* try using the preferred column */
+         if (*colname[ii] == '\0')
+         {
+           if (ii == 0)
+              strcpy(colname[ii], "X");
+           else if (ii == 1)
+              strcpy(colname[ii], "Y");
+           else if (ii == 2)
+              strcpy(colname[ii], "Z");
+           else if (ii == 3)
+              strcpy(colname[ii], "T");
+         }
+      }
+
+      /* get the column number in the table */
+      if (ffgcno(*fptr, CASEINSEN, colname[ii], histData.hcolnum+ii, status)
+              > 0)
+      {
+        strcpy(errmsg, "column for histogram axis doesn't exist: ");
+        strcat(errmsg, colname[ii]);
+        ffpmsg(errmsg);
+        return(*status);
+      }
+
+      colptr = ((*fptr)->Fptr)->tableptr;
+      colptr += (histData.hcolnum[ii] - 1);
+
+      repeat = (int) colptr->trepeat;  /* vector repeat factor of the column */
+      if (repeat > 1)
+      {
+        strcpy(errmsg, "Can't bin a vector column: ");
+        strcat(errmsg, colname[ii]);
+        ffpmsg(errmsg);
+        return(*status = BAD_DATATYPE);
+      }
+
+      /* get the datatype of the column */
+      fits_get_coltype(*fptr, histData.hcolnum[ii], &datatype,
+         NULL, NULL, status);
+
+      if (datatype < 0 || datatype == TSTRING)
+      {
+        strcpy(errmsg, "Inappropriate datatype; can't bin this column: ");
+        strcat(errmsg, colname[ii]);
+        ffpmsg(errmsg);
+        return(*status = BAD_DATATYPE);
+      }
+
+      /* use TLMINn and TLMAXn keyword values if min and max were not given */
+      /* else use actual data min and max if TLMINn and TLMAXn don't exist */
+ 
+      if (minin[ii] == DOUBLENULLVALUE)
+      {
+        ffkeyn("TLMIN", histData.hcolnum[ii], keyname, status);
+        if (ffgky(*fptr, TFLOAT, keyname, amin+ii, NULL, status) > 0)
+        {
+            /* use actual data minimum value for the histogram minimum */
+            *status = 0;
+            if (fits_get_col_minmax(*fptr, histData.hcolnum[ii], amin+ii, &datamax, status) > 0)
+            {
+                strcpy(errmsg, "Error calculating datamin and datamax for column: ");
+                strcat(errmsg, colname[ii]);
+                ffpmsg(errmsg);
+                return(*status);
+            }
+         }
+      }
+      else
+      {
+        amin[ii] = (float) minin[ii];
+      }
+
+      if (maxin[ii] == DOUBLENULLVALUE)
+      {
+        ffkeyn("TLMAX", histData.hcolnum[ii], keyname, status);
+        if (ffgky(*fptr, TFLOAT, keyname, &amax[ii], NULL, status) > 0)
+        {
+          *status = 0;
+          if(datamax != FLOATNULLVALUE)  /* already computed max value */
+          {
+             amax[ii] = datamax;
+          }
+          else
+          {
+             /* use actual data maximum value for the histogram maximum */
+             if (fits_get_col_minmax(*fptr, histData.hcolnum[ii], &datamin, &amax[ii], status) > 0)
+             {
+                 strcpy(errmsg, "Error calculating datamin and datamax for column: ");
+                 strcat(errmsg, colname[ii]);
+                 ffpmsg(errmsg);
+                 return(*status);
+             }
+          }
+        }
+        use_datamax = 1;  /* flag that the max was determined by the data values */
+                          /* and not specifically set by the calling program */
+      }
+      else
+      {
+        amax[ii] = (float) maxin[ii];
+      }
+
+      /* use TDBINn keyword or else 1 if bin size is not given */
+      if (binsizein[ii] == DOUBLENULLVALUE)
+      {
+         tstatus = 0;
+         ffkeyn("TDBIN", histData.hcolnum[ii], keyname, &tstatus);
+
+         if (ffgky(*fptr, TDOUBLE, keyname, binsizein + ii, NULL, &tstatus) > 0)
+         {
+	    /* make at least 10 bins */
+            binsizein[ii] = (amax[ii] - amin[ii]) / 10. ;
+            if (binsizein[ii] > 1.)
+                binsizein[ii] = 1.;  /* use default bin size */
+         }
+      }
+
+      if ( (amin[ii] > amax[ii] && binsizein[ii] > 0. ) ||
+           (amin[ii] < amax[ii] && binsizein[ii] < 0. ) )
+          binsize[ii] = (float) -binsizein[ii];  /* reverse the sign of binsize */
+      else
+          binsize[ii] =  (float) binsizein[ii];  /* binsize has the correct sign */
+
+      ibin = (int) binsize[ii];
+      imin = (int) amin[ii];
+      imax = (int) amax[ii];
+
+      /* Determine the range and number of bins in the histogram. This  */
+      /* depends on whether the input columns are integer or floats, so */
+      /* treat each case separately.                                    */
+
+      if (datatype <= TLONG && (float) imin == amin[ii] &&
+                               (float) imax == amax[ii] &&
+                               (float) ibin == binsize[ii] )
+      {
+        /* This is an integer column and integer limits were entered. */
+        /* Shift the lower and upper histogramming limits by 0.5, so that */
+        /* the values fall in the center of the bin, not on the edge. */
+
+        haxes[ii] = (imax - imin) / ibin + 1;  /* last bin may only */
+                                               /* be partially full */
+        maxbin[ii] = (float) (haxes[ii] + 1.);  /* add 1. instead of .5 to avoid roundoff */
+
+        if (amin[ii] < amax[ii])
+        {
+          amin[ii] = (float) (amin[ii] - 0.5);
+          amax[ii] = (float) (amax[ii] + 0.5);
+        }
+        else
+        {
+          amin[ii] = (float) (amin[ii] + 0.5);
+          amax[ii] = (float) (amax[ii] - 0.5);
+        }
+      }
+      else if (use_datamax)  
+      {
+        /* Either the column datatype and/or the limits are floating point, */
+        /* and the histogram limits are being defined by the min and max */
+        /* values of the array.  Add 1 to the number of histogram bins to */
+        /* make sure that pixels that are equal to the maximum or are */
+        /* in the last partial bin are included.  */
+
+        maxbin[ii] = (amax[ii] - amin[ii]) / binsize[ii]; 
+        haxes[ii] = (long) (maxbin[ii] + 1);
+      }
+      else  
+      {
+        /*  float datatype column and/or limits, and the maximum value to */
+        /*  include in the histogram is specified by the calling program. */
+        /*  The lower limit is inclusive, but upper limit is exclusive    */
+        maxbin[ii] = (amax[ii] - amin[ii]) / binsize[ii];
+        haxes[ii] = (long) maxbin[ii];
+
+        if (amin[ii] < amax[ii])
+        {
+          if (amin[ii] + (haxes[ii] * binsize[ii]) < amax[ii])
+            haxes[ii]++;   /* need to include another partial bin */
+        }
+        else
+        {
+          if (amin[ii] + (haxes[ii] * binsize[ii]) > amax[ii])
+            haxes[ii]++;   /* need to include another partial bin */
+        }
+      }
+    }
+
+       /* get the histogramming weighting factor */
+    if (*wtcol)
+    {
+        /* first, look for a keyword with the weight value */
+        if (ffgky(*fptr, TFLOAT, wtcol, &histData.weight, NULL, status) )
+        {
+            /* not a keyword, so look for column with this name */
+            *status = 0;
+
+            /* get the column number in the table */
+            if (ffgcno(*fptr, CASEINSEN, wtcol, &histData.wtcolnum, status) > 0)
+            {
+               ffpmsg(
+               "keyword or column for histogram weights doesn't exist: ");
+               ffpmsg(wtcol);
+               return(*status);
+            }
+
+            histData.weight = FLOATNULLVALUE;
+        }
+    }
+    else
+        histData.weight = (float) weightin;
+
+    if (histData.weight <= 0. && histData.weight != FLOATNULLVALUE)
+    {
+        ffpmsg("Illegal histogramming weighting factor <= 0.");
+        return(*status = URL_PARSE_ERROR);
+    }
+
+    if (recip && histData.weight != FLOATNULLVALUE)
+       /* take reciprocal of weight */
+       histData.weight = (float) (1.0 / histData.weight);
+
+    histData.wtrecip = recip;
+        
+    /* size of histogram is now known, so create temp output file */
+    if (ffinit(&histptr, outfile, status) > 0)
+    {
+        ffpmsg("failed to create temp output file for histogram");
+        return(*status);
+    }
+
+    if (ffcrim(histptr, bitpix, histData.haxis, haxes, status) > 0)
+    {
+        ffpmsg("failed to create primary array histogram in temp file");
+        ffclos(histptr, status);
+        return(*status);
+    }
+
+    /* copy all non-structural keywords from the table to the image */
+    fits_get_hdrspace(*fptr, &nkeys, NULL, status);
+    for (ii = 1; ii <= nkeys; ii++)
+    {
+       fits_read_record(*fptr, ii, card, status);
+       if (fits_get_keyclass(card) >= 120)
+           fits_write_record(histptr, card, status);
+    }           
+
+    /* Set global variables with histogram parameter values.    */
+    /* Use separate scalar variables rather than arrays because */
+    /* it is more efficient when computing the histogram.       */
+
+    histData.amin1 = amin[0];
+    histData.maxbin1 = maxbin[0];
+    histData.binsize1 = binsize[0];
+    histData.haxis1 = haxes[0];
+
+    if (histData.haxis > 1)
+    {
+      histData.amin2 = amin[1];
+      histData.maxbin2 = maxbin[1];
+      histData.binsize2 = binsize[1];
+      histData.haxis2 = haxes[1];
+
+      if (histData.haxis > 2)
+      {
+        histData.amin3 = amin[2];
+        histData.maxbin3 = maxbin[2];
+        histData.binsize3 = binsize[2];
+        histData.haxis3 = haxes[2];
+
+        if (histData.haxis > 3)
+        {
+          histData.amin4 = amin[3];
+          histData.maxbin4 = maxbin[3];
+          histData.binsize4 = binsize[3];
+          histData.haxis4 = haxes[3];
+        }
+      }
+    }
+
+    /* define parameters of image for the iterator function */
+    fits_iter_set_file(imagepars, histptr);        /* pointer to image */
+    fits_iter_set_datatype(imagepars, imagetype);  /* image datatype   */
+    fits_iter_set_iotype(imagepars, OutputCol);    /* image is output  */
+
+    /* call the iterator function to write out the histogram image */
+    if (fits_iterate_data(n_cols, imagepars, offset, n_per_loop,
+                          ffwritehisto, (void*)&histData, status) )
+         return(*status);
+
+    /* write the World Coordinate System (WCS) keywords */
+    /* create default values if WCS keywords are not present in the table */
+    for (ii = 0; ii < histData.haxis; ii++)
+    {
+     /*  CTYPEn  */
+       tstatus = 0;
+       ffkeyn("TCTYP", histData.hcolnum[ii], keyname, &tstatus);
+       ffgky(*fptr, TSTRING, keyname, svalue, NULL, &tstatus);
+       if (tstatus)
+       {               /* just use column name as the type */
+          tstatus = 0;
+          ffkeyn("TTYPE", histData.hcolnum[ii], keyname, &tstatus);
+          ffgky(*fptr, TSTRING, keyname, svalue, NULL, &tstatus);
+       }
+
+       if (!tstatus)
+       {
+        ffkeyn("CTYPE", ii + 1, keyname, &tstatus);
+        ffpky(histptr, TSTRING, keyname, svalue, "Coordinate Type", &tstatus);
+       }
+       else
+          tstatus = 0;
+
+     /*  CUNITn  */
+       ffkeyn("TCUNI", histData.hcolnum[ii], keyname, &tstatus);
+       ffgky(*fptr, TSTRING, keyname, svalue, NULL, &tstatus);
+       if (tstatus)
+       {         /* use the column units */
+          tstatus = 0;
+          ffkeyn("TUNIT", histData.hcolnum[ii], keyname, &tstatus);
+          ffgky(*fptr, TSTRING, keyname, svalue, NULL, &tstatus);
+       }
+
+       if (!tstatus)
+       {
+        ffkeyn("CUNIT", ii + 1, keyname, &tstatus);
+        ffpky(histptr, TSTRING, keyname, svalue, "Coordinate Units", &tstatus);
+       }
+       else
+         tstatus = 0;
+
+     /*  CRPIXn  - Reference Pixel  */
+       ffkeyn("TCRPX", histData.hcolnum[ii], keyname, &tstatus);
+       ffgky(*fptr, TDOUBLE, keyname, &dvalue, NULL, &tstatus);
+       if (tstatus)
+       {
+         dvalue = 1.0; /* choose first pixel in new image as ref. pix. */
+         tstatus = 0;
+       }
+       else
+       {
+           /* calculate locate of the ref. pix. in the new image */
+           dvalue = (dvalue - amin[ii]) / binsize[ii] + .5;
+       }
+
+       ffkeyn("CRPIX", ii + 1, keyname, &tstatus);
+       ffpky(histptr, TDOUBLE, keyname, &dvalue, "Reference Pixel", &tstatus);
+
+     /*  CRVALn - Value at the location of the reference pixel */
+       ffkeyn("TCRVL", histData.hcolnum[ii], keyname, &tstatus);
+       ffgky(*fptr, TDOUBLE, keyname, &dvalue, NULL, &tstatus);
+       if (tstatus)
+       {
+         /* calculate value at ref. pix. location (at center of 1st pixel) */
+         dvalue = amin[ii] + binsize[ii]/2.;
+         tstatus = 0;
+       }
+
+       ffkeyn("CRVAL", ii + 1, keyname, &tstatus);
+       ffpky(histptr, TDOUBLE, keyname, &dvalue, "Reference Value", &tstatus);
+
+     /*  CDELTn - unit size of pixels  */
+       ffkeyn("TCDLT", histData.hcolnum[ii], keyname, &tstatus);
+       ffgky(*fptr, TDOUBLE, keyname, &dvalue, NULL, &tstatus);
+       if (tstatus)
+       {
+         dvalue = 1.0;  /* use default pixel size */
+         tstatus = 0;
+       }
+
+       dvalue = dvalue * binsize[ii];
+       ffkeyn("CDELT", ii + 1, keyname, &tstatus);
+       ffpky(histptr, TDOUBLE, keyname, &dvalue, "Pixel size", &tstatus);
+
+     /*  CROTAn - Rotation angle (degrees CCW)  */
+     /*  There should only be a CROTA2 keyword, and only for 2+ D images */
+       if (ii == 1)
+       {
+         ffkeyn("TCROT", histData.hcolnum[ii], keyname, &tstatus);
+         ffgky(*fptr, TDOUBLE, keyname, &dvalue, NULL, &tstatus);
+         if (!tstatus && dvalue != 0.)  /* only write keyword if angle != 0 */
+         {
+           ffkeyn("CROTA", ii + 1, keyname, &tstatus);
+           ffpky(histptr, TDOUBLE, keyname, &dvalue,
+                 "Rotation angle", &tstatus);
+         }
+         else
+         {
+            /* didn't find CROTA for the 2nd axis, so look for one */
+            /* on the first axis */
+           tstatus = 0;
+           ffkeyn("TCROT", histData.hcolnum[0], keyname, &tstatus);
+           ffgky(*fptr, TDOUBLE, keyname, &dvalue, NULL, &tstatus);
+           if (!tstatus && dvalue != 0.)  /* only write keyword if angle != 0 */
+           {
+             dvalue *= -1.;   /* negate the value, because mirror image */
+             ffkeyn("CROTA", ii + 1, keyname, &tstatus);
+             ffpky(histptr, TDOUBLE, keyname, &dvalue,
+                   "Rotation angle", &tstatus);
+           }
+         }
+       }
+    }
+
+    /* convert any TPn_k keywords to PCi_j; the value remains unchanged */
+    /* also convert any TCn_k to CDi_j; the value is modified by n binning size */
+    /* This is a bit of a kludge, and only works for 2D WCS */
+
+    if (histData.haxis == 2) {
+
+      /* PC1_1 */
+      tstatus = 0;
+      ffkeyn("TP", histData.hcolnum[0], card, &tstatus);
+      strcat(card,"_");
+      ffkeyn(card, histData.hcolnum[0], keyname, &tstatus);
+      ffgky(*fptr, TDOUBLE, keyname, &dvalue, card, &tstatus);
+      if (!tstatus) 
+         ffpky(histptr, TDOUBLE, "PC1_1", &dvalue, card, &tstatus);
+
+      tstatus = 0;
+      keyname[1] = 'C';
+      ffgky(*fptr, TDOUBLE, keyname, &dvalue, card, &tstatus);
+      if (!tstatus) {
+         dvalue *=  binsize[0];
+         ffpky(histptr, TDOUBLE, "CD1_1", &dvalue, card, &tstatus);
+      }
+
+      /* PC1_2 */
+      tstatus = 0;
+      ffkeyn("TP", histData.hcolnum[0], card, &tstatus);
+      strcat(card,"_");
+      ffkeyn(card, histData.hcolnum[1], keyname, &tstatus);
+      ffgky(*fptr, TDOUBLE, keyname, &dvalue, card, &tstatus);
+      if (!tstatus) 
+         ffpky(histptr, TDOUBLE, "PC1_2", &dvalue, card, &tstatus);
+ 
+      tstatus = 0;
+      keyname[1] = 'C';
+      ffgky(*fptr, TDOUBLE, keyname, &dvalue, card, &tstatus);
+      if (!tstatus) {
+        dvalue *=  binsize[0];
+        ffpky(histptr, TDOUBLE, "CD1_2", &dvalue, card, &tstatus);
+      }
+       
+      /* PC2_1 */
+      tstatus = 0;
+      ffkeyn("TP", histData.hcolnum[1], card, &tstatus);
+      strcat(card,"_");
+      ffkeyn(card, histData.hcolnum[0], keyname, &tstatus);
+      ffgky(*fptr, TDOUBLE, keyname, &dvalue, card, &tstatus);
+      if (!tstatus) 
+         ffpky(histptr, TDOUBLE, "PC2_1", &dvalue, card, &tstatus);
+ 
+      tstatus = 0;
+      keyname[1] = 'C';
+      ffgky(*fptr, TDOUBLE, keyname, &dvalue, card, &tstatus);
+      if (!tstatus) {
+         dvalue *=  binsize[1];
+         ffpky(histptr, TDOUBLE, "CD2_1", &dvalue, card, &tstatus);
+      }
+       
+       /* PC2_2 */
+      tstatus = 0;
+      ffkeyn("TP", histData.hcolnum[1], card, &tstatus);
+      strcat(card,"_");
+      ffkeyn(card, histData.hcolnum[1], keyname, &tstatus);
+      ffgky(*fptr, TDOUBLE, keyname, &dvalue, card, &tstatus);
+      if (!tstatus) 
+         ffpky(histptr, TDOUBLE, "PC2_2", &dvalue, card, &tstatus);
+        
+      tstatus = 0;
+      keyname[1] = 'C';
+      ffgky(*fptr, TDOUBLE, keyname, &dvalue, card, &tstatus);
+      if (!tstatus) {
+         dvalue *=  binsize[1];
+         ffpky(histptr, TDOUBLE, "CD2_2", &dvalue, card, &tstatus);
+      }
+    }   
+       
+    /* finally, close the original file and return ptr to the new image */
+    ffclos(*fptr, status);
+    *fptr = histptr;
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_calc_binning(
+      fitsfile *fptr,  /* IO - pointer to table to be binned      ;       */
+      int naxis,       /* I - number of axes/columns in the binned image  */
+      char colname[4][FLEN_VALUE],   /* I - optional column names         */
+      double *minin,     /* I - optional lower bound value for each axis  */
+      double *maxin,     /* I - optional upper bound value, for each axis */
+      double *binsizein, /* I - optional bin size along each axis         */
+      char minname[4][FLEN_VALUE], /* I - optional keywords for min       */
+      char maxname[4][FLEN_VALUE], /* I - optional keywords for max       */
+      char binname[4][FLEN_VALUE], /* I - optional keywords for binsize   */
+
+    /* The returned parameters for each axis of the n-dimensional histogram are */
+
+      int *colnum,     /* O - column numbers, to be binned */
+      long *haxes,     /* O - number of bins in each histogram axis */
+      float *amin,     /* O - lower bound of the histogram axes */
+      float *amax,     /* O - upper bound of the histogram axes */
+      float *binsize,  /* O - width of histogram bins/pixels on each axis */
+      int *status)
+/*_
+    Calculate the actual binning parameters, based on various user input
+    options.
+*/
+{
+    tcolumn *colptr;
+    char *cptr, cpref[4][FLEN_VALUE];
+    char errmsg[FLEN_ERRMSG], keyname[FLEN_KEYWORD];
+    int tstatus, ii;
+    int datatype, repeat, imin, imax, ibin,  use_datamax = 0;
+    float datamin, datamax;
+
+    /* check inputs */
+    
+    if (*status > 0)
+        return(*status);
+
+    if (naxis > 4)
+    {
+        ffpmsg("histograms with more than 4 dimensions are not supported");
+        return(*status = BAD_DIMEN);
+    }
+
+    /* reset position to the correct HDU if necessary */
+    if ((fptr)->HDUposition != ((fptr)->Fptr)->curhdu)
+        ffmahd(fptr, ((fptr)->HDUposition) + 1, NULL, status);
+    
+    /* ============================================================= */
+    /* The CPREF keyword, if it exists, gives the preferred columns. */
+    /* Otherwise, assume "X", "Y", "Z", and "T"  */
+
+    *cpref[0] = '\0';
+    *cpref[1] = '\0';
+    *cpref[2] = '\0';
+    *cpref[3] = '\0';
+
+    tstatus = 0;
+    ffgky(fptr, TSTRING, "CPREF", cpref[0], NULL, &tstatus);
+
+    if (!tstatus)
+    {
+        /* Preferred column names are given;  separate them */
+        cptr = cpref[0];
+
+        /* the first preferred axis... */
+        while (*cptr != ',' && *cptr != '\0')
+           cptr++;
+
+        if (*cptr != '\0')
+        {
+           *cptr = '\0';
+           cptr++;
+           while (*cptr == ' ')
+               cptr++;
+
+           strcpy(cpref[1], cptr);
+           cptr = cpref[1];
+
+          /* the second preferred axis... */
+          while (*cptr != ',' && *cptr != '\0')
+             cptr++;
+
+          if (*cptr != '\0')
+          {
+             *cptr = '\0';
+             cptr++;
+             while (*cptr == ' ')
+                 cptr++;
+
+             strcpy(cpref[2], cptr);
+             cptr = cpref[2];
+
+            /* the third preferred axis... */
+            while (*cptr != ',' && *cptr != '\0')
+               cptr++;
+
+            if (*cptr != '\0')
+            {
+               *cptr = '\0';
+               cptr++;
+               while (*cptr == ' ')
+                   cptr++;
+
+               strcpy(cpref[3], cptr);
+
+            }
+          }
+        }
+    }
+
+    /* ============================================================= */
+    /* Main Loop for calculating parameters for each column          */
+
+    for (ii = 0; ii < naxis; ii++)
+    {
+
+      /* =========================================================== */
+      /* Determine column Number, based on, in order of priority,
+         1  input column name, or
+	 2  name given by CPREF keyword, or
+	 3  assume X, Y, Z and T for the name
+      */
+	  
+      if (*colname[ii] == '\0')
+      {
+         strcpy(colname[ii], cpref[ii]); /* try using the preferred column */
+         if (*colname[ii] == '\0')
+         {
+           if (ii == 0)
+              strcpy(colname[ii], "X");
+           else if (ii == 1)
+              strcpy(colname[ii], "Y");
+           else if (ii == 2)
+              strcpy(colname[ii], "Z");
+           else if (ii == 3)
+              strcpy(colname[ii], "T");
+         }
+      }
+
+      /* get the column number in the table */
+      if (ffgcno(fptr, CASEINSEN, colname[ii], colnum+ii, status)
+              > 0)
+      {
+          strcpy(errmsg, "column for histogram axis doesn't exist: ");
+          strcat(errmsg, colname[ii]);
+          ffpmsg(errmsg);
+          return(*status);
+      }
+
+      /* ================================================================ */
+      /* check tha column is not a vector or a string                     */
+
+      colptr = ((fptr)->Fptr)->tableptr;
+      colptr += (colnum[ii] - 1);
+
+      repeat = (int) colptr->trepeat;  /* vector repeat factor of the column */
+      if (repeat > 1)
+      {
+        strcpy(errmsg, "Can't bin a vector column: ");
+        strcat(errmsg, colname[ii]);
+        ffpmsg(errmsg);
+        return(*status = BAD_DATATYPE);
+      }
+
+      /* get the datatype of the column */
+      fits_get_coltype(fptr, colnum[ii], &datatype,
+         NULL, NULL, status);
+
+      if (datatype < 0 || datatype == TSTRING)
+      {
+        strcpy(errmsg, "Inappropriate datatype; can't bin this column: ");
+        strcat(errmsg, colname[ii]);
+        ffpmsg(errmsg);
+        return(*status = BAD_DATATYPE);
+      }
+
+      /* ================================================================ */
+      /* get the minimum value */
+
+      datamin = FLOATNULLVALUE;
+      datamax = FLOATNULLVALUE;
+      
+      if (*minname[ii])
+      {
+         if (ffgky(fptr, TDOUBLE, minname[ii], &minin[ii], NULL, status) )
+         {
+             ffpmsg("error reading histogramming minimum keyword");
+             ffpmsg(minname[ii]);
+             return(*status);
+         }
+      }
+
+      if (minin[ii] != DOUBLENULLVALUE)
+      {
+        amin[ii] = (float) minin[ii];
+      }
+      else
+      {
+        ffkeyn("TLMIN", colnum[ii], keyname, status);
+        if (ffgky(fptr, TFLOAT, keyname, amin+ii, NULL, status) > 0)
+        {
+            /* use actual data minimum value for the histogram minimum */
+            *status = 0;
+            if (fits_get_col_minmax(fptr, colnum[ii], amin+ii, &datamax, status) > 0)
+            {
+                strcpy(errmsg, "Error calculating datamin and datamax for column: ");
+                strcat(errmsg, colname[ii]);
+                ffpmsg(errmsg);
+                return(*status);
+            }
+         }
+      }
+
+      /* ================================================================ */
+      /* get the maximum value */
+
+      if (*maxname[ii])
+      {
+         if (ffgky(fptr, TDOUBLE, maxname[ii], &maxin[ii], NULL, status) )
+         {
+             ffpmsg("error reading histogramming maximum keyword");
+             ffpmsg(maxname[ii]);
+             return(*status);
+         }
+      }
+
+      if (maxin[ii] != DOUBLENULLVALUE)
+      {
+        amax[ii] = (float) maxin[ii];
+      }
+      else
+      {
+        ffkeyn("TLMAX", colnum[ii], keyname, status);
+        if (ffgky(fptr, TFLOAT, keyname, &amax[ii], NULL, status) > 0)
+        {
+          *status = 0;
+          if(datamax != FLOATNULLVALUE)  /* already computed max value */
+          {
+             amax[ii] = datamax;
+          }
+          else
+          {
+             /* use actual data maximum value for the histogram maximum */
+             if (fits_get_col_minmax(fptr, colnum[ii], &datamin, &amax[ii], status) > 0)
+             {
+                 strcpy(errmsg, "Error calculating datamin and datamax for column: ");
+                 strcat(errmsg, colname[ii]);
+                 ffpmsg(errmsg);
+                 return(*status);
+             }
+          }
+        }
+        use_datamax = 1;  /* flag that the max was determined by the data values */
+                          /* and not specifically set by the calling program */
+      }
+
+
+      /* ================================================================ */
+      /* determine binning size and range                                 */
+
+      if (*binname[ii])
+      {
+         if (ffgky(fptr, TDOUBLE, binname[ii], &binsizein[ii], NULL, status) )
+         {
+             ffpmsg("error reading histogramming binsize keyword");
+             ffpmsg(binname[ii]);
+             return(*status);
+         }
+      }
+
+      if (binsizein[ii] == 0.)
+      {
+        ffpmsg("error: histogram binsize = 0");
+        return(*status = ZERO_SCALE);
+      }
+
+      /* use TDBINn keyword or else 1 if bin size is not given */
+      if (binsizein[ii] != DOUBLENULLVALUE)
+      { 
+         binsize[ii] = (float) binsizein[ii];
+      }
+      else
+      {
+         tstatus = 0;
+         ffkeyn("TDBIN", colnum[ii], keyname, &tstatus);
+
+         if (ffgky(fptr, TDOUBLE, keyname, binsizein + ii, NULL, &tstatus) > 0)
+         {
+	    /* make at least 10 bins */
+            binsize[ii] = (amax[ii] - amin[ii]) / 10.F ;
+            if (binsize[ii] > 1.)
+                binsize[ii] = 1.;  /* use default bin size */
+         }
+      }
+
+      /* ================================================================ */
+      /* if the min is greater than the max, make the binsize negative */
+      if ( (amin[ii] > amax[ii] && binsize[ii] > 0. ) ||
+           (amin[ii] < amax[ii] && binsize[ii] < 0. ) )
+          binsize[ii] =  -binsize[ii];  /* reverse the sign of binsize */
+
+
+      ibin = (int) binsize[ii];
+      imin = (int) amin[ii];
+      imax = (int) amax[ii];
+
+      /* Determine the range and number of bins in the histogram. This  */
+      /* depends on whether the input columns are integer or floats, so */
+      /* treat each case separately.                                    */
+
+      if (datatype <= TLONG && (float) imin == amin[ii] &&
+                               (float) imax == amax[ii] &&
+                               (float) ibin == binsize[ii] )
+      {
+        /* This is an integer column and integer limits were entered. */
+        /* Shift the lower and upper histogramming limits by 0.5, so that */
+        /* the values fall in the center of the bin, not on the edge. */
+
+        haxes[ii] = (imax - imin) / ibin + 1;  /* last bin may only */
+                                               /* be partially full */
+        if (amin[ii] < amax[ii])
+        {
+          amin[ii] = (float) (amin[ii] - 0.5);
+          amax[ii] = (float) (amax[ii] + 0.5);
+        }
+        else
+        {
+          amin[ii] = (float) (amin[ii] + 0.5);
+          amax[ii] = (float) (amax[ii] - 0.5);
+        }
+      }
+      else if (use_datamax)  
+      {
+        /* Either the column datatype and/or the limits are floating point, */
+        /* and the histogram limits are being defined by the min and max */
+        /* values of the array.  Add 1 to the number of histogram bins to */
+        /* make sure that pixels that are equal to the maximum or are */
+        /* in the last partial bin are included.  */
+
+        haxes[ii] = (long) (((amax[ii] - amin[ii]) / binsize[ii]) + 1.); 
+      }
+      else  
+      {
+        /*  float datatype column and/or limits, and the maximum value to */
+        /*  include in the histogram is specified by the calling program. */
+        /*  The lower limit is inclusive, but upper limit is exclusive    */
+        haxes[ii] = (long) ((amax[ii] - amin[ii]) / binsize[ii]);
+
+        if (amin[ii] < amax[ii])
+        {
+          if (amin[ii] + (haxes[ii] * binsize[ii]) < amax[ii])
+            haxes[ii]++;   /* need to include another partial bin */
+        }
+        else
+        {
+          if (amin[ii] + (haxes[ii] * binsize[ii]) > amax[ii])
+            haxes[ii]++;   /* need to include another partial bin */
+        }
+      }
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_write_keys_histo(
+      fitsfile *fptr,   /* I - pointer to table to be binned              */
+      fitsfile *histptr,  /* I - pointer to output histogram image HDU      */
+      int naxis,        /* I - number of axes in the histogram image      */
+      int *colnum,      /* I - column numbers (array length = naxis)      */
+      int *status)     
+{      
+   /*  Write default WCS keywords in the output histogram image header */
+   /*  if the keywords do not already exist.   */
+
+    int ii, tstatus;
+    char keyname[FLEN_KEYWORD], svalue[FLEN_VALUE];
+    double dvalue;
+    
+    if (*status > 0)
+        return(*status);
+
+    for (ii = 0; ii < naxis; ii++)
+    {
+     /*  CTYPEn  */
+       tstatus = 0;
+       ffkeyn("CTYPE", ii+1, keyname, &tstatus);
+       ffgky(histptr, TSTRING, keyname, svalue, NULL, &tstatus);
+       
+       if (!tstatus) continue;  /* keyword already exists, so skip to next axis */
+       
+       /* use column name as the axis name */
+       tstatus = 0;
+       ffkeyn("TTYPE", colnum[ii], keyname, &tstatus);
+       ffgky(fptr, TSTRING, keyname, svalue, NULL, &tstatus);
+
+       if (!tstatus)
+       {
+         ffkeyn("CTYPE", ii + 1, keyname, &tstatus);
+         ffpky(histptr, TSTRING, keyname, svalue, "Coordinate Type", &tstatus);
+       }
+
+       /*  CUNITn,  use the column units */
+       tstatus = 0;
+       ffkeyn("TUNIT", colnum[ii], keyname, &tstatus);
+       ffgky(fptr, TSTRING, keyname, svalue, NULL, &tstatus);
+
+       if (!tstatus)
+       {
+         ffkeyn("CUNIT", ii + 1, keyname, &tstatus);
+         ffpky(histptr, TSTRING, keyname, svalue, "Coordinate Units", &tstatus);
+       }
+
+       /*  CRPIXn  - Reference Pixel choose first pixel in new image as ref. pix. */
+       dvalue = 1.0;
+       tstatus = 0;
+       ffkeyn("CRPIX", ii + 1, keyname, &tstatus);
+       ffpky(histptr, TDOUBLE, keyname, &dvalue, "Reference Pixel", &tstatus);
+
+       /*  CRVALn - Value at the location of the reference pixel */
+       dvalue = 1.0;
+       tstatus = 0;
+       ffkeyn("CRVAL", ii + 1, keyname, &tstatus);
+       ffpky(histptr, TDOUBLE, keyname, &dvalue, "Reference Value", &tstatus);
+
+       /*  CDELTn - unit size of pixels  */
+       dvalue = 1.0;  
+       tstatus = 0;
+       dvalue = 1.;
+       ffkeyn("CDELT", ii + 1, keyname, &tstatus);
+       ffpky(histptr, TDOUBLE, keyname, &dvalue, "Pixel size", &tstatus);
+
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_rebin_wcs(
+      fitsfile *fptr,   /* I - pointer to table to be binned           */
+      int naxis,        /* I - number of axes in the histogram image   */
+      float *amin,        /* I - first pixel include in each axis        */
+      float *binsize,     /* I - binning factor for each axis            */
+      int *status)      
+{      
+   /*  Update the  WCS keywords that define the location of the reference */
+   /*  pixel, and the pixel size, along each axis.   */
+
+    int ii, jj, tstatus, reset ;
+    char keyname[FLEN_KEYWORD], svalue[FLEN_VALUE];
+    double dvalue;
+    
+    if (*status > 0)
+        return(*status);
+  
+    for (ii = 0; ii < naxis; ii++)
+    {
+       reset = 0;  /* flag to reset the reference pixel */
+       tstatus = 0;
+       ffkeyn("CRVAL", ii + 1, keyname, &tstatus);
+       /* get previous (pre-binning) value */
+       ffgky(fptr, TDOUBLE, keyname, &dvalue, NULL, &tstatus); 
+       if (!tstatus && dvalue == 1.0)
+           reset = 1;
+
+       tstatus = 0;
+       /*  CRPIXn - update location of the ref. pix. in the binned image */
+       ffkeyn("CRPIX", ii + 1, keyname, &tstatus);
+
+       /* get previous (pre-binning) value */
+       ffgky(fptr, TDOUBLE, keyname, &dvalue, NULL, &tstatus); 
+
+       if (!tstatus)
+       {
+           if (dvalue != 1.0)
+	      reset = 0;
+
+           /* updated value to give pixel location after binning */
+           dvalue = (dvalue - amin[ii]) / ((double) binsize[ii]) + .5;  
+
+           fits_modify_key_dbl(fptr, keyname, dvalue, -14, NULL, &tstatus);
+       } else {
+          reset = 0;
+       }
+
+       /*  CDELTn - update unit size of pixels  */
+       tstatus = 0;
+       ffkeyn("CDELT", ii + 1, keyname, &tstatus);
+
+       /* get previous (pre-binning) value */
+       ffgky(fptr, TDOUBLE, keyname, &dvalue, NULL, &tstatus); 
+
+       if (!tstatus)
+       {
+           if (dvalue != 1.0)
+	      reset = 0;
+
+           /* updated to give post-binning value */
+           dvalue = dvalue * binsize[ii];  
+
+           fits_modify_key_dbl(fptr, keyname, dvalue, -14, NULL, &tstatus);
+       }
+       else
+       {   /* no CDELTn keyword, so look for a CDij keywords */
+          reset = 0;
+
+          for (jj = 0; jj < naxis; jj++)
+	  {
+             tstatus = 0;
+             ffkeyn("CD", jj + 1, svalue, &tstatus);
+	     strcat(svalue,"_");
+	     ffkeyn(svalue, ii + 1, keyname, &tstatus);
+
+             /* get previous (pre-binning) value */
+             ffgky(fptr, TDOUBLE, keyname, &dvalue, NULL, &tstatus); 
+
+             if (!tstatus)
+             {
+                /* updated to give post-binning value */
+               dvalue = dvalue * binsize[ii];  
+
+               fits_modify_key_dbl(fptr, keyname, dvalue, -14, NULL, &tstatus);
+             }
+	  }
+       }
+
+       if (reset) {
+          /* the original CRPIX, CRVAL, and CDELT keywords were all = 1.0 */
+	  /* In this special case, reset the reference pixel to be the */
+	  /* first pixel in the array (instead of possibly far off the array) */
+ 
+           dvalue = 1.0;
+           ffkeyn("CRPIX", ii + 1, keyname, &tstatus);
+           fits_modify_key_dbl(fptr, keyname, dvalue, -14, NULL, &tstatus);
+
+           ffkeyn("CRVAL", ii + 1, keyname, &tstatus);
+	   dvalue = amin[ii] + (binsize[ii] / 2.0);	  
+           fits_modify_key_dbl(fptr, keyname, dvalue, -14, NULL, &tstatus);
+	}
+
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+
+int fits_make_hist(fitsfile *fptr, /* IO - pointer to table with X and Y cols; */
+    fitsfile *histptr, /* I - pointer to output FITS image      */
+    int bitpix,       /* I - datatype for image: 16, 32, -32, etc    */
+    int naxis,        /* I - number of axes in the histogram image   */
+    long *naxes,      /* I - size of axes in the histogram image   */
+    int *colnum,    /* I - column numbers (array length = naxis)   */
+    float *amin,     /* I - minimum histogram value, for each axis */
+    float *amax,     /* I - maximum histogram value, for each axis */
+    float *binsize, /* I - bin size along each axis               */
+    float weight,        /* I - binning weighting factor          */
+    int wtcolnum, /* I - optional keyword or col for weight*/
+    int recip,              /* I - use reciprocal of the weight?     */
+    char *selectrow,        /* I - optional array (length = no. of   */
+                             /* rows in the table).  If the element is true */
+                             /* then the corresponding row of the table will*/
+                             /* be included in the histogram, otherwise the */
+                             /* row will be skipped.  Ingnored if *selectrow*/
+                             /* is equal to NULL.                           */
+    int *status)
+{		  
+    int ii, imagetype, datatype;
+    int n_cols = 1;
+    long imin, imax, ibin;
+    long  offset = 0;
+    long n_per_loop = -1;  /* force whole array to be passed at one time */
+    float taxes[4], tmin[4], tmax[4], tbin[4], maxbin[4];
+    histType histData;    /* Structure holding histogram info for iterator */
+    iteratorCol imagepars[1];
+
+    /* check inputs */
+    
+    if (*status > 0)
+        return(*status);
+
+    if (naxis > 4)
+    {
+        ffpmsg("histogram has more than 4 dimensions");
+        return(*status = BAD_DIMEN);
+    }
+
+    if   (bitpix == BYTE_IMG)
+         imagetype = TBYTE;
+    else if (bitpix == SHORT_IMG)
+         imagetype = TSHORT;
+    else if (bitpix == LONG_IMG)
+         imagetype = TINT;    
+    else if (bitpix == FLOAT_IMG)
+         imagetype = TFLOAT;    
+    else if (bitpix == DOUBLE_IMG)
+         imagetype = TDOUBLE;    
+    else
+        return(*status = BAD_DATATYPE);
+
+    /* reset position to the correct HDU if necessary */
+    if ((fptr)->HDUposition != ((fptr)->Fptr)->curhdu)
+        ffmahd(fptr, ((fptr)->HDUposition) + 1, NULL, status);
+
+    histData.weight     = weight;
+    histData.wtcolnum   = wtcolnum;
+    histData.wtrecip    = recip;
+    histData.tblptr     = fptr;
+    histData.himagetype = imagetype;
+    histData.haxis      = naxis;
+    histData.rowselector = selectrow;
+
+    for (ii = 0; ii < naxis; ii++)
+    {
+      taxes[ii] = (float) naxes[ii];
+      tmin[ii] = amin[ii];
+      tmax[ii] = amax[ii];
+      if ( (amin[ii] > amax[ii] && binsize[ii] > 0. ) ||
+           (amin[ii] < amax[ii] && binsize[ii] < 0. ) )
+          tbin[ii] =  -binsize[ii];  /* reverse the sign of binsize */
+      else
+          tbin[ii] =   binsize[ii];  /* binsize has the correct sign */
+          
+      imin = (long) tmin[ii];
+      imax = (long) tmax[ii];
+      ibin = (long) tbin[ii];
+    
+      /* get the datatype of the column */
+      fits_get_coltype(fptr, colnum[ii], &datatype, NULL, NULL, status);
+
+      if (datatype <= TLONG && (float) imin == tmin[ii] &&
+                               (float) imax == tmax[ii] &&
+                               (float) ibin == tbin[ii] )
+      {
+        /* This is an integer column and integer limits were entered. */
+        /* Shift the lower and upper histogramming limits by 0.5, so that */
+        /* the values fall in the center of the bin, not on the edge. */
+
+        maxbin[ii] = (taxes[ii] + 1.F);  /* add 1. instead of .5 to avoid roundoff */
+
+        if (tmin[ii] < tmax[ii])
+        {
+          tmin[ii] = tmin[ii] - 0.5F;
+          tmax[ii] = tmax[ii] + 0.5F;
+        }
+        else
+        {
+          tmin[ii] = tmin[ii] + 0.5F;
+          tmax[ii] = tmax[ii] - 0.5F;
+        }
+      } else {  /* not an integer column with integer limits */
+          maxbin[ii] = (tmax[ii] - tmin[ii]) / tbin[ii]; 
+      }
+    }
+
+    /* Set global variables with histogram parameter values.    */
+    /* Use separate scalar variables rather than arrays because */
+    /* it is more efficient when computing the histogram.       */
+
+    histData.hcolnum[0]  = colnum[0];
+    histData.amin1 = tmin[0];
+    histData.maxbin1 = maxbin[0];
+    histData.binsize1 = tbin[0];
+    histData.haxis1 = (long) taxes[0];
+
+    if (histData.haxis > 1)
+    {
+      histData.hcolnum[1]  = colnum[1];
+      histData.amin2 = tmin[1];
+      histData.maxbin2 = maxbin[1];
+      histData.binsize2 = tbin[1];
+      histData.haxis2 = (long) taxes[1];
+
+      if (histData.haxis > 2)
+      {
+        histData.hcolnum[2]  = colnum[2];
+        histData.amin3 = tmin[2];
+        histData.maxbin3 = maxbin[2];
+        histData.binsize3 = tbin[2];
+        histData.haxis3 = (long) taxes[2];
+
+        if (histData.haxis > 3)
+        {
+          histData.hcolnum[3]  = colnum[3];
+          histData.amin4 = tmin[3];
+          histData.maxbin4 = maxbin[3];
+          histData.binsize4 = tbin[3];
+          histData.haxis4 = (long) taxes[3];
+        }
+      }
+    }
+
+    /* define parameters of image for the iterator function */
+    fits_iter_set_file(imagepars, histptr);        /* pointer to image */
+    fits_iter_set_datatype(imagepars, imagetype);  /* image datatype   */
+    fits_iter_set_iotype(imagepars, OutputCol);    /* image is output  */
+
+    /* call the iterator function to write out the histogram image */
+    fits_iterate_data(n_cols, imagepars, offset, n_per_loop,
+                          ffwritehisto, (void*)&histData, status);
+       
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_get_col_minmax(fitsfile *fptr, int colnum, float *datamin, 
+                     float *datamax, int *status)
+/* 
+   Simple utility routine to compute the min and max value in a column
+*/
+{
+    int anynul;
+    long nrows, ntodo, firstrow, ii;
+    float array[1000], nulval;
+
+    ffgky(fptr, TLONG, "NAXIS2", &nrows, NULL, status); /* no. of rows */
+
+    firstrow = 1;
+    nulval = FLOATNULLVALUE;
+    *datamin =  9.0E36F;
+    *datamax = -9.0E36F;
+
+    while(nrows)
+    {
+        ntodo = minvalue(nrows, 100);
+        ffgcv(fptr, TFLOAT, colnum, firstrow, 1, ntodo, &nulval, array,
+              &anynul, status);
+
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            if (array[ii] != nulval)
+            {
+                *datamin = minvalue(*datamin, array[ii]);
+                *datamax = maxvalue(*datamax, array[ii]);
+            }
+        }
+
+        nrows -= ntodo;
+        firstrow += ntodo;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffwritehisto(long totaln, long pixoffset, long firstn, long nvalues,
+             int narrays, iteratorCol *imagepars, void *userPointer)
+/*
+   Interator work function that writes out the histogram.
+   The histogram values are calculated by another work function, ffcalchisto.
+   This work function only gets called once, and totaln = nvalues.
+*/
+{
+    iteratorCol colpars[5];
+    int ii, status = 0, ncols;
+    long rows_per_loop = 0, offset = 0;
+    histType *histData;
+
+    histData = (histType *)userPointer;
+
+    /* store pointer to the histogram array, and initialize to zero */
+
+    switch( histData->himagetype ) {
+    case TBYTE:
+       histData->hist.b = (char *  ) fits_iter_get_array(imagepars);
+       break;
+    case TSHORT:
+       histData->hist.i = (short * ) fits_iter_get_array(imagepars);
+       break;
+    case TINT:
+       histData->hist.j = (int *   ) fits_iter_get_array(imagepars);
+       break;
+    case TFLOAT:
+       histData->hist.r = (float * ) fits_iter_get_array(imagepars);
+       break;
+    case TDOUBLE:
+       histData->hist.d = (double *) fits_iter_get_array(imagepars);
+       break;
+    }
+
+    /* set the column parameters for the iterator function */
+    for (ii = 0; ii < histData->haxis; ii++)
+    {
+      fits_iter_set_by_num(&colpars[ii], histData->tblptr,
+			   histData->hcolnum[ii], TFLOAT, InputCol);
+    }
+    ncols = histData->haxis;
+
+    if (histData->weight == FLOATNULLVALUE)
+    {
+      fits_iter_set_by_num(&colpars[histData->haxis], histData->tblptr,
+			   histData->wtcolnum, TFLOAT, InputCol);
+      ncols = histData->haxis + 1;
+    }
+
+    /* call iterator function to calc the histogram pixel values */
+
+    /* must lock this call in multithreaded environoments because */
+    /* the ffcalchist work routine uses static vaiables that would */
+    /* get clobbered if multiple threads were running at the same time */
+    FFLOCK;
+    fits_iterate_data(ncols, colpars, offset, rows_per_loop,
+                          ffcalchist, (void*)histData, &status);
+    FFUNLOCK;
+
+    return(status);
+}
+/*--------------------------------------------------------------------------*/
+int ffcalchist(long totalrows, long offset, long firstrow, long nrows,
+             int ncols, iteratorCol *colpars, void *userPointer)
+/*
+   Interator work function that calculates values for the 2D histogram.
+*/
+{
+    long ii, ipix, iaxisbin;
+    float pix, axisbin;
+    static float *col1, *col2, *col3, *col4; /* static to preserve values */
+    static float *wtcol;
+    static long incr2, incr3, incr4;
+    static histType histData;
+    static char *rowselect;
+
+    /*  Initialization procedures: execute on the first call  */
+    if (firstrow == 1)
+    {
+
+      /*  Copy input histogram data to static local variable so we */
+      /*  don't have to constantly dereference it.                 */
+
+      histData = *(histType*)userPointer;
+      rowselect = histData.rowselector;
+
+      /* assign the input array pointers to local pointers */
+      col1 = (float *) fits_iter_get_array(&colpars[0]);
+      if (histData.haxis > 1)
+      {
+        col2 = (float *) fits_iter_get_array(&colpars[1]);
+        incr2 = histData.haxis1;
+
+        if (histData.haxis > 2)
+        {
+          col3 = (float *) fits_iter_get_array(&colpars[2]);
+          incr3 = incr2 * histData.haxis2;
+
+          if (histData.haxis > 3)
+          {
+            col4 = (float *) fits_iter_get_array(&colpars[3]);
+            incr4 = incr3 * histData.haxis3;
+          }
+        }
+      }
+
+      if (ncols > histData.haxis)  /* then weights are give in a column */
+      {
+        wtcol = (float *) fits_iter_get_array(&colpars[histData.haxis]);
+      }
+    }   /* end of Initialization procedures */
+
+    /*  Main loop: increment the histogram at position of each event */
+    for (ii = 1; ii <= nrows; ii++) 
+    {
+        if (rowselect)     /* if a row selector array is supplied... */
+        {
+           if (*rowselect)
+           {
+               rowselect++;   /* this row is included in the histogram */
+           }
+           else
+           {
+               rowselect++;   /* this row is excluded from the histogram */
+               continue;
+           }
+        }
+
+        if (col1[ii] == FLOATNULLVALUE)  /* test for null value */
+            continue;
+
+        pix = (col1[ii] - histData.amin1) / histData.binsize1;
+        ipix = (long) (pix + 1.); /* add 1 because the 1st pixel is the null value */
+
+	/* test if bin is within range */
+        if (ipix < 1 || ipix > histData.haxis1 || pix > histData.maxbin1)
+            continue;
+
+        if (histData.haxis > 1)
+        {
+          if (col2[ii] == FLOATNULLVALUE)
+              continue;
+
+          axisbin = (col2[ii] - histData.amin2) / histData.binsize2;
+          iaxisbin = (long) axisbin;
+
+          if (axisbin < 0. || iaxisbin >= histData.haxis2 || axisbin > histData.maxbin2)
+              continue;
+
+          ipix += (iaxisbin * incr2);
+
+          if (histData.haxis > 2)
+          {
+            if (col3[ii] == FLOATNULLVALUE)
+                continue;
+
+            axisbin = (col3[ii] - histData.amin3) / histData.binsize3;
+            iaxisbin = (long) axisbin;
+            if (axisbin < 0. || iaxisbin >= histData.haxis3 || axisbin > histData.maxbin3)
+                continue;
+
+            ipix += (iaxisbin * incr3);
+ 
+            if (histData.haxis > 3)
+            {
+              if (col4[ii] == FLOATNULLVALUE)
+                  continue;
+
+              axisbin = (col4[ii] - histData.amin4) / histData.binsize4;
+              iaxisbin = (long) axisbin;
+              if (axisbin < 0. || iaxisbin >= histData.haxis4 || axisbin > histData.maxbin4)
+                  continue;
+
+              ipix += (iaxisbin * incr4);
+
+            }  /* end of haxis > 3 case */
+          }    /* end of haxis > 2 case */
+        }      /* end of haxis > 1 case */
+
+        /* increment the histogram pixel */
+        if (histData.weight != FLOATNULLVALUE) /* constant weight factor */
+        {
+            if (histData.himagetype == TINT)
+              histData.hist.j[ipix] += (int) histData.weight;
+            else if (histData.himagetype == TSHORT)
+              histData.hist.i[ipix] += (short) histData.weight;
+            else if (histData.himagetype == TFLOAT)
+              histData.hist.r[ipix] += histData.weight;
+            else if (histData.himagetype == TDOUBLE)
+              histData.hist.d[ipix] += histData.weight;
+            else if (histData.himagetype == TBYTE)
+              histData.hist.b[ipix] += (char) histData.weight;
+        }
+        else if (histData.wtrecip) /* use reciprocal of the weight */
+        {
+            if (histData.himagetype == TINT)
+              histData.hist.j[ipix] += (int) (1./wtcol[ii]);
+            else if (histData.himagetype == TSHORT)
+              histData.hist.i[ipix] += (short) (1./wtcol[ii]);
+            else if (histData.himagetype == TFLOAT)
+              histData.hist.r[ipix] += (float) (1./wtcol[ii]);
+            else if (histData.himagetype == TDOUBLE)
+              histData.hist.d[ipix] += 1./wtcol[ii];
+            else if (histData.himagetype == TBYTE)
+              histData.hist.b[ipix] += (char) (1./wtcol[ii]);
+        }
+        else   /* no weights */
+        {
+            if (histData.himagetype == TINT)
+              histData.hist.j[ipix] += (int) wtcol[ii];
+            else if (histData.himagetype == TSHORT)
+              histData.hist.i[ipix] += (short) wtcol[ii];
+            else if (histData.himagetype == TFLOAT)
+              histData.hist.r[ipix] += wtcol[ii];
+            else if (histData.himagetype == TDOUBLE)
+              histData.hist.d[ipix] += wtcol[ii];
+            else if (histData.himagetype == TBYTE)
+              histData.hist.b[ipix] += (char) wtcol[ii];
+        }
+
+    }  /* end of main loop over all rows */
+
+    return(0);
+}
+
diff --git a/src/plugins/cfitsio/imcompress.c b/src/plugins/cfitsio/imcompress.c
new file mode 100644
index 0000000..6330bf8
--- /dev/null
+++ b/src/plugins/cfitsio/imcompress.c
@@ -0,0 +1,9247 @@
+# include <stdio.h>
+# include <stdlib.h>
+# include <string.h>
+# include <math.h>
+# include <ctype.h>
+# include <time.h>
+# include "fitsio2.h"
+
+#define NULL_VALUE -2147483647 /* value used to represent undefined pixels */
+
+/* nearest integer function */
+# define NINT(x)  ((x >= 0.) ? (int) (x + 0.5) : (int) (x - 0.5))
+
+/* special quantize level value indicates that floating point image pixels */
+/* should not be quantized and instead losslessly compressed (with GZIP) */
+#define NO_QUANTIZE 9999
+
+
+/* string array for storing the individual column compression stats */
+char results[999][60];
+float trans_ratio[999];
+
+float *fits_rand_value = 0;
+
+int imcomp_write_nocompress_tile(fitsfile *outfptr, long row, int datatype, 
+    void *tiledata, long tilelen, int nullcheck, void *nullflagval, int *status);
+int imcomp_convert_tile_tshort(fitsfile *outfptr, void *tiledata, long tilelen,
+    int nullcheck, void *nullflagval, int nullval, int zbitpix, double scale,
+    double zero, double actual_bzero, int *intlength, int *status);
+int imcomp_convert_tile_tushort(fitsfile *outfptr, void *tiledata, long tilelen,
+    int nullcheck, void *nullflagval, int nullval, int zbitpix, double scale,
+    double zero, int *intlength, int *status);
+int imcomp_convert_tile_tint(fitsfile *outfptr, void *tiledata, long tilelen,
+    int nullcheck, void *nullflagval, int nullval, int zbitpix, double scale,
+    double zero, int *intlength, int *status);
+int imcomp_convert_tile_tuint(fitsfile *outfptr, void *tiledata, long tilelen,
+    int nullcheck, void *nullflagval, int nullval, int zbitpix, double scale,
+    double zero, int *intlength, int *status);
+int imcomp_convert_tile_tbyte(fitsfile *outfptr, void *tiledata, long tilelen,
+    int nullcheck, void *nullflagval, int nullval, int zbitpix, double scale,
+    double zero, int *intlength, int *status);
+int imcomp_convert_tile_tsbyte(fitsfile *outfptr, void *tiledata, long tilelen,
+    int nullcheck, void *nullflagval, int nullval, int zbitpix, double scale,
+    double zero, int *intlength, int *status);
+int imcomp_convert_tile_tfloat(fitsfile *outfptr, long row, void *tiledata, long tilelen,
+    long tilenx, long tileny, int nullcheck, void *nullflagval, int nullval, int zbitpix,
+    double scale, double zero, int *intlength, int *flag, double *bscale, double *bzero,int *status);
+int imcomp_convert_tile_tdouble(fitsfile *outfptr, long row, void *tiledata, long tilelen,
+    long tilenx, long tileny, int nullcheck, void *nullflagval, int nullval, int zbitpix, 
+    double scale, double zero, int *intlength, int *flag, double *bscale, double *bzero, int *status);
+
+static int unquantize_i1r4(long row,
+            unsigned char *input,         /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            unsigned char tnull,          /* I - value of FITS TNULLn keyword if any */
+            float nullval,        /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            float *output,        /* O - array of converted pixels           */
+            int *status);          /* IO - error status                       */
+static int unquantize_i2r4(long row,
+            short *input,         /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            short tnull,          /* I - value of FITS TNULLn keyword if any */
+            float nullval,        /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            float *output,        /* O - array of converted pixels           */
+            int *status);          /* IO - error status                       */
+static int unquantize_i4r4(long row,
+            INT32BIT *input,      /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            INT32BIT tnull,       /* I - value of FITS TNULLn keyword if any */
+            float nullval,        /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            float *output,        /* O - array of converted pixels           */
+            int *status);          /* IO - error status                       */
+static int unquantize_i1r8(long row,
+            unsigned char *input,         /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            unsigned char tnull,          /* I - value of FITS TNULLn keyword if any */
+            double nullval,        /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            double *output,        /* O - array of converted pixels           */
+            int *status);          /* IO - error status                       */
+static int unquantize_i2r8(long row,
+            short *input,         /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            short tnull,          /* I - value of FITS TNULLn keyword if any */
+            double nullval,        /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            double *output,        /* O - array of converted pixels           */
+            int *status);          /* IO - error status                       */
+static int unquantize_i4r8(long row,
+            INT32BIT *input,      /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            INT32BIT tnull,       /* I - value of FITS TNULLn keyword if any */
+            double nullval,        /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            double *output,        /* O - array of converted pixels           */
+            int *status);          /* IO - error status                       */
+static int imcomp_float2nan(float *indata, long tilelen, int *outdata,
+    float nullflagval,  int *status);
+static int imcomp_double2nan(double *indata, long tilelen, LONGLONG *outdata,
+    double nullflagval,  int *status);    
+static int fits_read_write_compressed_img(fitsfile *fptr,   /* I - FITS file pointer */
+            int  datatype,  /* I - datatype of the array to be returned      */
+            LONGLONG  *infpixel, /* I - 'bottom left corner' of the subsection    */
+            LONGLONG  *inlpixel, /* I - 'top right corner' of the subsection      */
+            long  *ininc,    /* I - increment to be applied in each dimension */
+            int  nullcheck,  /* I - 0 for no null checking                   */
+                              /*     1: set undefined pixels = nullval       */
+            void *nullval,    /* I - value for undefined pixels              */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            fitsfile *outfptr,   /* I - FITS file pointer                    */
+            int  *status);
+
+static int fits_shuffle_8bytes(char *heap, LONGLONG length, int *status);
+static int fits_shuffle_4bytes(char *heap, LONGLONG length, int *status);
+static int fits_shuffle_2bytes(char *heap, LONGLONG length, int *status);
+static int fits_gzip_heap(fitsfile *infptr, fitsfile *outfptr, int *status);
+static int fits_unshuffle_8bytes(char *heap, LONGLONG length, int *status);
+static int fits_unshuffle_4bytes(char *heap, LONGLONG length, int *status);
+static int fits_unshuffle_2bytes(char *heap, LONGLONG length, int *status);
+static int fits_gunzip_heap(fitsfile *infptr, fitsfile *outfptr, int *status);
+
+/*---------------------------------------------------------------------------*/
+int fits_init_randoms(void) {
+
+/* initialize an array of random numbers */
+
+    int ii;
+    double a = 16807.0;
+    double m = 2147483647.0;
+    double temp, seed;
+
+    FFLOCK;
+ 
+    if (fits_rand_value) {
+       FFUNLOCK;
+       return(0);  /* array is already initialized */
+    }
+
+    /* allocate array for the random number sequence */
+    /* THIS MEMORY IS NEVER FREED */
+    fits_rand_value = calloc(N_RANDOM, sizeof(float));
+
+    if (!fits_rand_value) {
+        FFUNLOCK;
+	return(MEMORY_ALLOCATION);
+    }
+		       
+    /*  We need a portable algorithm that anyone can use to generate this
+        exact same sequence of random number.  The C 'rand' function is not
+	suitable because it is not available to Fortran or Java programmers.
+	Instead, use a well known simple algorithm published here: 
+	"Random number generators: good ones are hard to find", Communications of the ACM,
+        Volume 31 ,  Issue 10  (October 1988) Pages: 1192 - 1201 
+    */  
+
+    /* initialize the random numbers */
+    seed = 1;
+    for (ii = 0; ii < N_RANDOM; ii++) {
+        temp = a * seed;
+	seed = temp -m * ((int) (temp / m) );
+	fits_rand_value[ii] = (float) (seed / m);
+    }
+
+    FFUNLOCK;
+
+    /* 
+    IMPORTANT NOTE: the 10000th seed value must have the value 1043618065 if the 
+       algorithm has been implemented correctly */
+    
+    if ( (int) seed != 1043618065) {
+        ffpmsg("fits_init_randoms generated incorrect random number sequence");
+	return(1);
+    } else {
+        return(0);
+    }
+}
+/*--------------------------------------------------------------------------*/
+void bz_internal_error(int errcode)
+{
+    /* external function declared by the bzip2 code in bzlib_private.h */
+    ffpmsg("bzip2 returned an internal error");
+    ffpmsg("This should never happen");
+    return;
+}
+/*--------------------------------------------------------------------------*/
+int fits_set_compression_type(fitsfile *fptr,  /* I - FITS file pointer     */
+       int ctype,    /* image compression type code;                        */
+                     /* allowed values: RICE_1, GZIP_1, GZIP_2, PLIO_1,     */
+                     /*  HCOMPRESS_1, BZIP2_1, and NOCOMPRESS               */
+       int *status)  /* IO - error status                                   */
+{
+/*
+   This routine specifies the image compression algorithm that should be
+   used when writing a FITS image.  The image is divided into tiles, and
+   each tile is compressed and stored in a row of at variable length binary
+   table column.
+*/
+    (fptr->Fptr)->request_compress_type = ctype;
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_set_tile_dim(fitsfile *fptr,  /* I - FITS file pointer             */
+           int ndim,   /* number of dimensions in the compressed image      */
+           long *dims, /* size of image compression tile in each dimension  */
+                      /* default tile size = (NAXIS1, 1, 1, ...)            */
+           int *status)         /* IO - error status                        */
+{
+/*
+   This routine specifies the size (dimension) of the image
+   compression  tiles that should be used when writing a FITS
+   image.  The image is divided into tiles, and each tile is compressed
+   and stored in a row of at variable length binary table column.
+*/
+    int ii;
+
+    if (ndim < 0 || ndim > MAX_COMPRESS_DIM)
+    {
+        *status = BAD_DIMEN;
+        return(*status);
+    }
+
+    for (ii = 0; ii < ndim; ii++)
+    {
+        (fptr->Fptr)->request_tilesize[ii] = dims[ii];
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_set_quantize_level(fitsfile *fptr,  /* I - FITS file pointer   */
+           float qlevel,        /* floating point quantization level      */
+           int *status)         /* IO - error status                */
+{
+/*
+   This routine specifies the value of the quantization level, q,  that
+   should be used when compressing floating point images.  The image is
+   divided into tiles, and each tile is compressed and stored in a row
+   of at variable length binary table column.
+*/
+    if (qlevel == 0.)
+    {
+        /* this means don't quantize the floating point values. Instead, */
+	/* the floating point values will be losslessly compressed */
+       (fptr->Fptr)->quantize_level = NO_QUANTIZE;
+    } else {
+
+        (fptr->Fptr)->quantize_level = qlevel;
+
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_set_quantize_dither(fitsfile *fptr,  /* I - FITS file pointer   */
+           int dither,        /* dither type      */
+           int *status)         /* IO - error status                */
+{
+/*
+   This routine specifies what type of dithering (randomization) should
+   be performed when quantizing floating point images to integer prior to
+   compression.   A value of -1 means do no dithering.  A value of 0 means
+   used the default  SUBTRACTIVE_DITHER_1 (which is equivalent to dither = 1).
+   A value of -1 means do not apply any dither.
+*/
+
+    if (dither == 0) dither = 1;
+    (fptr->Fptr)->request_quantize_dither = dither;
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_set_dither_offset(fitsfile *fptr,  /* I - FITS file pointer   */
+           int offset,        /* random dithering offset value (1 to 10000) */
+           int *status)         /* IO - error status                */
+{
+/*
+   This routine specifies the value of the offset that should be applied when
+   calculating the random dithering when quantizing floating point iamges.
+   A random offset should be applied to each image to avoid quantization 
+   effects when taking the difference of 2 images, or co-adding a set of
+   images.  Without this random offset, the corresponding pixel in every image
+   will have exactly the same dithering.
+   
+   offset = 0 means use the default random dithering based on system time
+   offset = negative means randomly chose dithering based on 1st tile checksum
+   offset = [1 - 10000] means use that particular dithering pattern
+
+*/
+    /* if positive, ensure that the value is in the range 1 to 10000 */
+    if (offset > 0)
+       (fptr->Fptr)->request_dither_offset = ((offset - 1) % 10000 ) + 1; 
+    else
+       (fptr->Fptr)->request_dither_offset = offset; 
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_set_noise_bits(fitsfile *fptr,  /* I - FITS file pointer   */
+           int noisebits,       /* noise_bits parameter value       */
+                                /* (default = 4)                    */
+           int *status)         /* IO - error status                */
+{
+/*
+   ********************************************************************
+   ********************************************************************
+   THIS ROUTINE IS PROVIDED ONLY FOR BACKWARDS COMPATIBILITY;
+   ALL NEW SOFTWARE SHOULD CALL fits_set_quantize_level INSTEAD
+   ********************************************************************
+   ********************************************************************
+
+   This routine specifies the value of the noice_bits parameter that
+   should be used when compressing floating point images.  The image is
+   divided into tiles, and each tile is compressed and stored in a row
+   of at variable length binary table column.
+
+   Feb 2008:  the "noisebits" parameter has been replaced with the more
+   general "quantize level" parameter.
+*/
+    float qlevel;
+
+    if (noisebits < 1 || noisebits > 16)
+    {
+        *status = DATA_COMPRESSION_ERR;
+        return(*status);
+    }
+
+    qlevel = (float) pow (2., (double)noisebits);
+    fits_set_quantize_level(fptr, qlevel, status);
+    
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_set_hcomp_scale(fitsfile *fptr,  /* I - FITS file pointer   */
+           float scale,       /* hcompress scale parameter value       */
+                                /* (default = 0.)                    */
+           int *status)         /* IO - error status                */
+{
+/*
+   This routine specifies the value of the hcompress scale parameter that
+  The image is
+   divided into tiles, and each tile is compressed and stored in a row
+   of at variable length binary table column.
+*/
+    (fptr->Fptr)->request_hcomp_scale = scale;
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_set_hcomp_smooth(fitsfile *fptr,  /* I - FITS file pointer   */
+           int smooth,       /* hcompress smooth parameter value       */
+                                /* if scale > 1 and smooth != 0, then */
+				/*  the image will be smoothed when it is */
+				/* decompressed to remove some of the */
+				/* 'blockiness' in the image produced */
+				/* by the lossy compression    */
+           int *status)         /* IO - error status                */
+{
+/*
+   This routine specifies the value of the hcompress scale parameter that
+  The image is
+   divided into tiles, and each tile is compressed and stored in a row
+   of at variable length binary table column.
+*/
+
+    (fptr->Fptr)->request_hcomp_smooth = smooth;
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_set_lossy_int(fitsfile *fptr,  /* I - FITS file pointer   */
+           int lossy_int,       /* I - True (!= 0) or False (0) */
+           int *status)         /* IO - error status                */
+{
+/*
+   This routine specifies whether images with integer pixel values should
+   quantized and compressed the same way float images are compressed.
+   The default is to not do this, and instead apply a lossless compression
+   algorithm to integer images.
+*/
+
+    (fptr->Fptr)->request_lossy_int_compress = lossy_int;
+    return(*status);
+}/*--------------------------------------------------------------------------*/
+int fits_get_compression_type(fitsfile *fptr,  /* I - FITS file pointer     */
+       int *ctype,   /* image compression type code;                        */
+                     /* allowed values:                                     */
+		     /* RICE_1, GZIP_1, GZIP_2, PLIO_1, HCOMPRESS_1, BZIP2_1 */
+       int *status)  /* IO - error status                                   */
+{
+/*
+   This routine returns the image compression algorithm that should be
+   used when writing a FITS image.  The image is divided into tiles, and
+   each tile is compressed and stored in a row of at variable length binary
+   table column.
+*/
+    *ctype = (fptr->Fptr)->request_compress_type;
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_get_tile_dim(fitsfile *fptr,  /* I - FITS file pointer             */
+           int ndim,   /* number of dimensions in the compressed image      */
+           long *dims, /* size of image compression tile in each dimension  */
+                       /* default tile size = (NAXIS1, 1, 1, ...)           */
+           int *status)         /* IO - error status                        */
+{
+/*
+   This routine returns the size (dimension) of the image
+   compression  tiles that should be used when writing a FITS
+   image.  The image is divided into tiles, and each tile is compressed
+   and stored in a row of at variable length binary table column.
+*/
+    int ii;
+
+    if (ndim < 0 || ndim > MAX_COMPRESS_DIM)
+    {
+        *status = BAD_DIMEN;
+        return(*status);
+    }
+
+    for (ii = 0; ii < ndim; ii++)
+    {
+        dims[ii] = (fptr->Fptr)->request_tilesize[ii];
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_get_noise_bits(fitsfile *fptr,  /* I - FITS file pointer   */
+           int *noisebits,       /* noise_bits parameter value       */
+                                /* (default = 4)                    */
+           int *status)         /* IO - error status                */
+{
+/*
+   ********************************************************************
+   ********************************************************************
+   THIS ROUTINE IS PROVIDED ONLY FOR BACKWARDS COMPATIBILITY;
+   ALL NEW SOFTWARE SHOULD CALL fits_set_quantize_level INSTEAD
+   ********************************************************************
+   ********************************************************************
+
+
+   This routine returns the value of the noice_bits parameter that
+   should be used when compressing floating point images.  The image is
+   divided into tiles, and each tile is compressed and stored in a row
+   of at variable length binary table column.
+
+   Feb 2008: code changed to use the more general "quantize level" parameter
+   rather than the "noise bits" parameter.  If quantize level is greater than
+   zero, then the previous noisebits parameter is approximately given by
+   
+   noise bits = natural logarithm (quantize level) / natural log (2)
+   
+   This result is rounded to the nearest integer.
+*/
+    double qlevel;
+
+    qlevel = (fptr->Fptr)->quantize_level;
+
+    if (qlevel > 0. && qlevel < 65537. )
+         *noisebits =  (int) ((log(qlevel) / log(2.0)) + 0.5);
+    else 
+        *noisebits = 0;
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_get_quantize_level(fitsfile *fptr,  /* I - FITS file pointer   */
+           float *qlevel,       /* quantize level parameter value       */
+           int *status)         /* IO - error status                */
+{
+/*
+   This routine returns the value of the noice_bits parameter that
+   should be used when compressing floating point images.  The image is
+   divided into tiles, and each tile is compressed and stored in a row
+   of at variable length binary table column.
+*/
+
+    if ((fptr->Fptr)->quantize_level == NO_QUANTIZE) {
+      *qlevel = 0;
+    } else {
+      *qlevel = (fptr->Fptr)->quantize_level;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_get_dither_offset(fitsfile *fptr,  /* I - FITS file pointer   */
+           int *offset,       /* dithering offset parameter value       */
+           int *status)         /* IO - error status                */
+{
+/*
+   This routine returns the value of the dithering offset parameter that
+   is used when compressing floating point images.  The image is
+   divided into tiles, and each tile is compressed and stored in a row
+   of at variable length binary table column.
+*/
+
+    *offset = (fptr->Fptr)->request_dither_offset;
+    return(*status);
+}/*--------------------------------------------------------------------------*/
+int fits_get_hcomp_scale(fitsfile *fptr,  /* I - FITS file pointer   */
+           float *scale,          /* Hcompress scale parameter value       */
+           int *status)         /* IO - error status                */
+
+{
+/*
+   This routine returns the value of the noice_bits parameter that
+   should be used when compressing floating point images.  The image is
+   divided into tiles, and each tile is compressed and stored in a row
+   of at variable length binary table column.
+*/
+
+    *scale = (fptr->Fptr)->request_hcomp_scale;
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_get_hcomp_smooth(fitsfile *fptr,  /* I - FITS file pointer   */
+           int *smooth,          /* Hcompress smooth parameter value       */
+           int *status)         /* IO - error status                */
+
+{
+    *smooth = (fptr->Fptr)->request_hcomp_smooth;
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_img_compress(fitsfile *infptr, /* pointer to image to be compressed */
+                 fitsfile *outfptr, /* empty HDU for output compressed image */
+                 int *status)       /* IO - error status               */
+
+/*
+   This routine initializes the output table, copies all the keywords,
+   and  loops through the input image, compressing the data and
+   writing the compressed tiles to the output table.
+   
+   This is a high level routine that is called by the fpack and funpack
+   FITS compression utilities.
+*/
+{
+    int bitpix, naxis;
+    long naxes[MAX_COMPRESS_DIM];
+
+    if (*status > 0)
+        return(*status);
+
+    /* get datatype and size of input image */
+    if (fits_get_img_param(infptr, MAX_COMPRESS_DIM, &bitpix, 
+                       &naxis, naxes, status) > 0)
+        return(*status);
+
+    if (naxis < 1 || naxis > MAX_COMPRESS_DIM)
+    {
+        ffpmsg("Image cannot be compressed: NAXIS out of range");
+        return(*status = BAD_NAXIS);
+    }
+
+    /* if requested, treat integer images same as a float image. */
+    /* Then the pixels will be quantized (lossy algorithm) to achieve */
+    /* higher amounts of compression than with lossless algorithms */
+
+    if ( (outfptr->Fptr)->request_lossy_int_compress != 0  && bitpix > 0) 
+	bitpix = FLOAT_IMG;  /* compress integer images as if float */
+
+    /* initialize output table */
+    if (imcomp_init_table(outfptr, bitpix, naxis, naxes, 0, status) > 0)
+        return (*status);    
+    
+    /* Copy the image header keywords to the table header. */
+    if (imcomp_copy_img2comp(infptr, outfptr, status) > 0)
+	    return (*status);
+
+    /* turn off any intensity scaling (defined by BSCALE and BZERO */
+    /* keywords) so that unscaled values will be read by CFITSIO */
+    /* (except if quantizing an int image, same as a float image) */
+    if ( (outfptr->Fptr)->request_lossy_int_compress == 0 && bitpix > 0) 
+        ffpscl(infptr, 1.0, 0.0, status);
+
+    /* force a rescan of the output file keywords, so that */
+    /* the compression parameters will be copied to the internal */
+    /* fitsfile structure used by CFITSIO */
+    ffrdef(outfptr, status);
+
+    /* turn off any intensity scaling (defined by BSCALE and BZERO */
+    /* keywords) so that unscaled values will be written by CFITSIO */
+    /* (except if quantizing an int image, same as a float image) */
+    if ( (outfptr->Fptr)->request_lossy_int_compress == 0 && bitpix > 0) 
+        ffpscl(outfptr, 1.0, 0.0, status);
+
+    /* Read each image tile, compress, and write to a table row. */
+    imcomp_compress_image (infptr, outfptr, status);
+
+    /* force another rescan of the output file keywords, to */
+    /* update PCOUNT and TFORMn = '1PB(iii)' keyword values. */
+    ffrdef(outfptr, status);
+
+    return (*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_compress_img_OBSOLETE(fitsfile *infptr, /* pointer to image to be compressed */
+                 fitsfile *outfptr, /* empty HDU for output compressed image */
+                 int compress_type, /* compression type code               */
+                                    /*  RICE_1, HCOMPRESS_1, etc.          */
+                 long *intilesize,    /* size in each dimension of the tiles */
+                                    /* NULL pointer means tile by rows */
+		 int blocksize,     /* compression parameter: blocksize  */
+                 int nbits,         /* compression parameter: nbits  */
+                 int *status)       /* IO - error status               */
+
+/*
+  !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!!
+   This routine is obsolete and should not be used.  The 
+   ftools 'fimgzip' task used to call this routine (but that task has been deleted); 
+
+  The name of the routine was changed 4/27/2011, to see if anyone complains.
+  If not, then this routine should be deleted from the source code.
+  !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!!
+  
+   This routine initializes the output table, copies all the keywords,
+   and  loops through the input image, compressing the data and
+   writing the compressed tiles to the output table.
+*/
+{
+    int bitpix, naxis;
+    long naxes[MAX_COMPRESS_DIM];
+
+    if (*status > 0)
+        return(*status);
+
+    /* get datatype and size of input image */
+    if (fits_get_img_param(infptr, MAX_COMPRESS_DIM, &bitpix, 
+                       &naxis, naxes, status) > 0)
+        return(*status);
+
+    if (naxis < 1 || naxis > MAX_COMPRESS_DIM)
+    {
+        ffpmsg("Image cannot be compressed: NAXIS out of range");
+        return(*status = BAD_NAXIS);
+    }
+
+    /* initialize output table */
+    if (imcomp_init_table(outfptr, bitpix, naxis, naxes, 0, status) > 0)
+        return (*status);
+
+    /* Copy the image header keywords to the table header. */
+    if (imcomp_copy_imheader(infptr, outfptr, status) > 0)
+	    return (*status);
+
+    /* turn off any intensity scaling (defined by BSCALE and BZERO */
+    /* keywords) so that unscaled values will be read by CFITSIO */
+    ffpscl(infptr, 1.0, 0.0, status);
+
+    /* force a rescan of the output file keywords, so that */
+    /* the compression parameters will be copied to the internal */
+    /* fitsfile structure used by CFITSIO */
+    ffrdef(outfptr, status);
+
+    /* Read each image tile, compress, and write to a table row. */
+    imcomp_compress_image (infptr, outfptr, status);
+
+    /* force another rescan of the output file keywords, to */
+    /* update PCOUNT and TFORMn = '1PB(iii)' keyword values. */
+    ffrdef(outfptr, status);
+
+    return (*status);
+}
+/*--------------------------------------------------------------------------*/
+int imcomp_init_table(fitsfile *outfptr,
+        int inbitpix,
+        int naxis,
+        long *naxes,
+	int writebitpix,    /* write the ZBITPIX, ZNAXIS, and ZNAXES keyword? */
+        int *status)
+/* 
+  create a BINTABLE extension for the output compressed image.
+*/
+{
+    char keyname[FLEN_KEYWORD], zcmptype[12];
+    int ii,  remain,  ncols, bitpix;
+    long nrows;
+    char *ttype[] = {"COMPRESSED_DATA", "ZSCALE", "ZZERO"};
+    char *tform[3];
+    char tf0[4], tf1[4], tf2[4];
+    char *tunit[] = {"\0",            "\0",            "\0"  };
+    char comm[FLEN_COMMENT];
+    long actual_tilesize[MAX_COMPRESS_DIM]; /* Actual size to use for tiles */
+    
+    if (*status > 0)
+        return(*status);
+
+    /* check for special case of losslessly compressing floating point */
+    /* images.  Only compression algorithm that supports this is GZIP */
+    if ( (outfptr->Fptr)->quantize_level == NO_QUANTIZE) {
+       if (((outfptr->Fptr)->request_compress_type != GZIP_1) &&
+           ((outfptr->Fptr)->request_compress_type != GZIP_2)) {
+         ffpmsg("Lossless compression of floating point images must use GZIP (imcomp_init_table)");
+         return(*status = DATA_COMPRESSION_ERR);
+       }
+    }
+    
+    /* test for the 2 special cases that represent unsigned integers */
+    if (inbitpix == USHORT_IMG)
+        bitpix = SHORT_IMG;
+    else if (inbitpix == ULONG_IMG)
+        bitpix = LONG_IMG;
+    else if (inbitpix == SBYTE_IMG)
+        bitpix = BYTE_IMG;
+    else 
+        bitpix = inbitpix;
+
+    /* reset default tile dimensions too if required */
+    memcpy(actual_tilesize, outfptr->Fptr->request_tilesize, MAX_COMPRESS_DIM * sizeof(long));
+
+    if ((outfptr->Fptr)->request_compress_type == HCOMPRESS_1) {
+
+         if (naxis < 2 ) {
+            ffpmsg("Hcompress cannot be used with 1-dimensional images (imcomp_init_table)");
+            return(*status = DATA_COMPRESSION_ERR);
+
+	 } else if  (naxes[0] < 4 || naxes[1] < 4) {
+            ffpmsg("Hcompress minimum image dimension is 4 pixels (imcomp_init_table)");
+            return(*status = DATA_COMPRESSION_ERR);
+         }
+
+         if ((actual_tilesize[0] == 0) &&
+             (actual_tilesize[1] == 0) ){
+	     
+	    /* compress the whole image as a single tile */
+             actual_tilesize[0] = naxes[0];
+             actual_tilesize[1] = naxes[1];
+
+              for (ii = 2; ii < naxis; ii++) {
+	             /* set all higher tile dimensions = 1 */
+                     actual_tilesize[ii] = 1;
+	      }
+
+         } else if ((actual_tilesize[0] == 0) &&
+             (actual_tilesize[1] == 1) ){
+	     
+             /*
+              The Hcompress algorithm is inherently 2D in nature, so the row by row
+	      tiling that is used for other compression algorithms is not appropriate.
+	      If the image has less than 30 rows, then the entire image will be compressed
+	      as a single tile.  Otherwise the tiles will consist of 16 rows of the image. 
+	      This keeps the tiles to a reasonable size, and it also includes enough rows
+	      to allow good compression efficiency.  If the last tile of the image 
+	      happens to contain less than 4 rows, then find another tile size with
+	      between 14 and 30 rows (preferably even), so that the last tile has 
+	      at least 4 rows
+	     */ 
+	      
+             /* 1st tile dimension is the row length of the image */
+             actual_tilesize[0] = naxes[0];
+
+              if (naxes[1] <= 30) {  /* use whole image if it is small */
+                   actual_tilesize[1] = naxes[1];
+	      } else {
+                /* look for another good tile dimension */
+	          if        (naxes[1] % 16 == 0 || naxes[1] % 16 > 3) {
+                      actual_tilesize[1] = 16;
+		  } else if (naxes[1] % 24 == 0 || naxes[1] % 24 > 3) {
+                      actual_tilesize[1] = 24;
+		  } else if (naxes[1] % 20 == 0 || naxes[1] % 20 > 3) {
+                      actual_tilesize[1] = 20;
+		  } else if (naxes[1] % 30 == 0 || naxes[1] % 30 > 3) {
+                      actual_tilesize[1] = 30;
+		  } else if (naxes[1] % 28 == 0 || naxes[1] % 28 > 3) {
+                      actual_tilesize[1] = 28;
+		  } else if (naxes[1] % 26 == 0 || naxes[1] % 26 > 3) {
+                      actual_tilesize[1] = 26;
+		  } else if (naxes[1] % 22 == 0 || naxes[1] % 22 > 3) {
+                      actual_tilesize[1] = 22;
+		  } else if (naxes[1] % 18 == 0 || naxes[1] % 18 > 3) {
+                      actual_tilesize[1] = 18;
+		  } else if (naxes[1] % 14 == 0 || naxes[1] % 14 > 3) {
+                      actual_tilesize[1] = 14;
+		  } else  {
+                      actual_tilesize[1] = 17;
+
+		  }
+	      }
+
+        } else if (actual_tilesize[0] < 4 ||
+                   actual_tilesize[1] < 4) {
+
+            /* user-specified tile size is too small */
+            ffpmsg("Hcompress minimum tile dimension is 4 pixels (imcomp_init_table)");
+            return(*status = DATA_COMPRESSION_ERR);
+	}
+	
+        /* check if requested tile size causes the last tile to to have less than 4 pixels */
+        remain = naxes[0] % (actual_tilesize[0]);  /* 1st dimension */
+        if (remain > 0 && remain < 4) {
+            (actual_tilesize[0])++; /* try increasing tile size by 1 */
+	   
+            remain = naxes[0] % (actual_tilesize[0]);
+            if (remain > 0 && remain < 4) {
+                ffpmsg("Last tile along 1st dimension has less than 4 pixels (imcomp_init_table)");
+                return(*status = DATA_COMPRESSION_ERR);	
+            }        
+        }
+
+        remain = naxes[1] % (actual_tilesize[1]);  /* 2nd dimension */
+        if (remain > 0 && remain < 4) {
+            (actual_tilesize[1])++; /* try increasing tile size by 1 */
+	   
+            remain = naxes[1] % (actual_tilesize[1]);
+            if (remain > 0 && remain < 4) {
+                ffpmsg("Last tile along 2nd dimension has less than 4 pixels (imcomp_init_table)");
+                return(*status = DATA_COMPRESSION_ERR);	
+            }        
+        }
+
+    } /* end, if HCOMPRESS_1 */
+    
+    for (ii = 0; ii < naxis; ii++) {
+        if (actual_tilesize[ii] <= 0) {
+	    /* tile size of 0 means use the image size of that dimension */
+            actual_tilesize[ii] = naxes[ii];
+	}
+    }
+
+    /* ---- set up array of TFORM strings -------------------------------*/
+    strcpy(tf0, "1PB");
+    strcpy(tf1, "1D");
+    strcpy(tf2, "1D");
+
+    tform[0] = tf0;
+    tform[1] = tf1;
+    tform[2] = tf2;
+
+    /* calculate number of rows in output table */
+    nrows = 1;
+    for (ii = 0; ii < naxis; ii++)
+    {
+        nrows = nrows * ((naxes[ii] - 1)/ (actual_tilesize[ii]) + 1);
+    }
+
+    /* determine the default  number of columns in the output table */
+    if (bitpix < 0 && (outfptr->Fptr)->quantize_level != NO_QUANTIZE)  
+        ncols = 3;  /* quantized and scaled floating point image */
+    else
+        ncols = 1; /* default table has just one 'COMPRESSED_DATA' column */
+
+    if ((outfptr->Fptr)->request_compress_type == RICE_1)
+    {
+        strcpy(zcmptype, "RICE_1");
+    }
+    else if ((outfptr->Fptr)->request_compress_type == GZIP_1)
+    {
+        strcpy(zcmptype, "GZIP_1");
+    }
+    else if ((outfptr->Fptr)->request_compress_type == GZIP_2)
+    {
+        strcpy(zcmptype, "GZIP_2");
+    }
+    else if ((outfptr->Fptr)->request_compress_type == BZIP2_1)
+    {
+        strcpy(zcmptype, "BZIP2_1");
+    }
+    else if ((outfptr->Fptr)->request_compress_type == PLIO_1)
+    {
+        strcpy(zcmptype, "PLIO_1");
+       /* the PLIO compression algorithm outputs short integers, not bytes */
+       strcpy(tform[0], "1PI");
+    }
+    else if ((outfptr->Fptr)->request_compress_type == HCOMPRESS_1)
+    {
+        strcpy(zcmptype, "HCOMPRESS_1");
+    }
+    else if ((outfptr->Fptr)->request_compress_type == NOCOMPRESS)
+    {
+        strcpy(zcmptype, "NOCOMPRESS");
+    }    
+    else
+    {
+        ffpmsg("unknown compression type (imcomp_init_table)");
+        return(*status = DATA_COMPRESSION_ERR);
+    }
+
+    /* create the bintable extension to contain the compressed image */
+    ffcrtb(outfptr, BINARY_TBL, nrows, ncols, ttype, 
+                tform, tunit, 0, status);
+
+    /* Add standard header keywords. */
+    ffpkyl (outfptr, "ZIMAGE", 1, 
+           "extension contains compressed image", status);  
+
+    if (writebitpix) {
+        /*  write the keywords defining the datatype and dimensions of */
+	/*  the uncompressed image.  If not, these keywords will be */
+        /*  copied later from the input uncompressed image  */
+	   
+        ffpkyj (outfptr, "ZBITPIX", bitpix,
+			"data type of original image", status);
+        ffpkyj (outfptr, "ZNAXIS", naxis,
+			"dimension of original image", status);
+
+        for (ii = 0;  ii < naxis;  ii++)
+        {
+            sprintf (keyname, "ZNAXIS%d", ii+1);
+            ffpkyj (outfptr, keyname, naxes[ii],
+			"length of original image axis", status);
+        }
+    }
+                      
+    for (ii = 0;  ii < naxis;  ii++)
+    {
+        sprintf (keyname, "ZTILE%d", ii+1);
+        ffpkyj (outfptr, keyname, actual_tilesize[ii],
+			"size of tiles to be compressed", status);
+    }
+
+    if (bitpix < 0) {
+       
+	if ((outfptr->Fptr)->quantize_level == NO_QUANTIZE) {
+	    ffpkys(outfptr, "ZQUANTIZ", "NONE", 
+	      "Lossless compression without quantization", status);
+	} else {
+	    
+	    /* Unless dithering has been specifically turned off by setting */
+	    /* request_quantize_dither = -1, use dithering by default */
+	    /* when quantizing floating point images. */
+	
+	    if ( (outfptr->Fptr)->request_quantize_dither == 0) 
+              (outfptr->Fptr)->request_quantize_dither = SUBTRACTIVE_DITHER_1;
+       
+	    if ((outfptr->Fptr)->request_quantize_dither == SUBTRACTIVE_DITHER_1) {
+	      ffpkys(outfptr, "ZQUANTIZ", "SUBTRACTIVE_DITHER_1", 
+	        "Pixel Quantization Algorithm", status);
+
+	      /* also write the associated ZDITHER0 keyword with a default value */
+	      /* which may get updated later. */
+              ffpky(outfptr, TINT, "ZDITHER0", &((outfptr->Fptr)->request_dither_offset), 
+	       "dithering offset when quantizing floats", status);
+            }
+	}
+    }
+
+    ffpkys (outfptr, "ZCMPTYPE", zcmptype,
+	          "compression algorithm", status);
+
+    /* write any algorithm-specific keywords */
+    if ((outfptr->Fptr)->request_compress_type == RICE_1)
+    {
+        ffpkys (outfptr, "ZNAME1", "BLOCKSIZE",
+            "compression block size", status);
+
+        /* for now at least, the block size is always 32 */
+        ffpkyj (outfptr, "ZVAL1", 32,
+			"pixels per block", status);
+
+        ffpkys (outfptr, "ZNAME2", "BYTEPIX",
+            "bytes per pixel (1, 2, 4, or 8)", status);
+
+        if (bitpix == BYTE_IMG)
+            ffpkyj (outfptr, "ZVAL2", 1,
+			"bytes per pixel (1, 2, 4, or 8)", status);
+        else if (bitpix == SHORT_IMG)
+            ffpkyj (outfptr, "ZVAL2", 2,
+			"bytes per pixel (1, 2, 4, or 8)", status);
+        else 
+            ffpkyj (outfptr, "ZVAL2", 4,
+			"bytes per pixel (1, 2, 4, or 8)", status);
+
+    }
+    else if ((outfptr->Fptr)->request_compress_type == HCOMPRESS_1)
+    {
+        ffpkys (outfptr, "ZNAME1", "SCALE",
+            "HCOMPRESS scale factor", status);
+        ffpkye (outfptr, "ZVAL1", (outfptr->Fptr)->request_hcomp_scale,
+		7, "HCOMPRESS scale factor", status);
+
+        ffpkys (outfptr, "ZNAME2", "SMOOTH",
+            "HCOMPRESS smooth option", status);
+        ffpkyj (outfptr, "ZVAL2", (long) (outfptr->Fptr)->request_hcomp_smooth,
+			"HCOMPRESS smooth option", status);
+    }
+
+    /* Write the BSCALE and BZERO keywords, if an unsigned integer image */
+    if (inbitpix == USHORT_IMG)
+    {
+        strcpy(comm, "offset data range to that of unsigned short");
+        ffpkyg(outfptr, "BZERO", 32768., 0, comm, status);
+        strcpy(comm, "default scaling factor");
+        ffpkyg(outfptr, "BSCALE", 1.0, 0, comm, status);
+    }
+    else if (inbitpix == SBYTE_IMG)
+    {
+        strcpy(comm, "offset data range to that of signed byte");
+        ffpkyg(outfptr, "BZERO", -128., 0, comm, status);
+        strcpy(comm, "default scaling factor");
+        ffpkyg(outfptr, "BSCALE", 1.0, 0, comm, status);
+    }
+    else if (inbitpix == ULONG_IMG)
+    {
+        strcpy(comm, "offset data range to that of unsigned long");
+        ffpkyg(outfptr, "BZERO", 2147483648., 0, comm, status);
+        strcpy(comm, "default scaling factor");
+        ffpkyg(outfptr, "BSCALE", 1.0, 0, comm, status);
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int imcomp_calc_max_elem (int comptype, int nx, int zbitpix, int blocksize)
+
+/* This function returns the maximum number of bytes in a compressed
+   image line.
+
+    nx = maximum number of pixels in a tile
+    blocksize is only relevant for RICE compression
+*/
+{    
+    if (comptype == RICE_1)
+    {
+        if (zbitpix == 16)
+            return (sizeof(short) * nx + nx / blocksize + 2 + 4);
+	else
+            return (sizeof(float) * nx + nx / blocksize + 2 + 4);
+    }
+    else if ((comptype == GZIP_1) || (comptype == GZIP_2))
+    {
+        /* gzip usually compressed by at least a factor of 2 for I*4 images */
+        /* and somewhat less for I*2 images */
+        /* If this size turns out to be too small, then the gzip */
+        /* compression routine will allocate more space as required */
+        /* to be on the safe size, allocate buffer same size as input */
+	
+        if (zbitpix == 16)
+            return(nx * 2);
+	else if (zbitpix == 8)
+            return(nx);
+	else
+            return(nx * 4);
+    }
+    else if (comptype == BZIP2_1)
+    {
+        /* To guarantee that the compressed data will fit, allocate an output
+	   buffer of size 1% larger than the uncompressed data, plus 600 bytes */
+
+            return((int) (nx * 1.01 * zbitpix / 8. + 601.));
+    }
+     else if (comptype == HCOMPRESS_1)
+    {
+        /* Imperical evidence suggests in the worst case, 
+	   the compressed stream could be up to 10% larger than the original
+	   image.  Add 26 byte overhead, only significant for very small tiles
+	   
+         Possible improvement: may need to allow a larger size for 32-bit images */
+
+        if (zbitpix == 16 || zbitpix == 8)
+	
+            return( (int) (nx * 2.2 + 26));   /* will be compressing 16-bit int array */
+        else
+            return( (int) (nx * 4.4 + 26));   /* will be compressing 32-bit int array */
+    }
+    else
+        return(nx * sizeof(int));
+}
+/*--------------------------------------------------------------------------*/
+int imcomp_compress_image (fitsfile *infptr, fitsfile *outfptr, int *status)
+
+/* This routine does the following:
+        - reads an image one tile at a time
+        - if it is a float or double image, then it tries to quantize the pixels
+          into scaled integers.
+        - it then compressess the integer pixels, or if the it was not
+	  possible to quantize the floating point pixels, then it losslessly
+	  compresses them with gzip
+	- writes the compressed byte stream to the output FITS file
+*/
+{
+    double *tiledata;
+    int anynul, gotnulls = 0, datatype;
+    long ii, row;
+    int naxis;
+    double dummy = 0., dblnull = DOUBLENULLVALUE;
+    float fltnull = FLOATNULLVALUE;
+    long maxtilelen, tilelen, incre[] = {1, 1, 1, 1, 1, 1};
+    long naxes[MAX_COMPRESS_DIM], fpixel[MAX_COMPRESS_DIM];
+    long lpixel[MAX_COMPRESS_DIM], tile[MAX_COMPRESS_DIM];
+    long tilesize[MAX_COMPRESS_DIM];
+    long i0, i1, i2, i3, i4, i5;
+    char card[FLEN_CARD];
+
+    if (*status > 0)
+        return(*status);
+
+    maxtilelen = (outfptr->Fptr)->maxtilelen;
+
+    /* 
+     Allocate buffer to hold 1 tile of data; size depends on which compression 
+     algorithm is used:
+
+      Rice and GZIP will compress byte, short, or int arrays without conversion.
+      PLIO requires 4-byte int values, so byte and short arrays must be converted to int.
+      HCompress internally converts byte or short values to ints, and
+         converts int values to 8-byte longlong integers.
+    */
+    
+    if ((outfptr->Fptr)->zbitpix == FLOAT_IMG)
+    {
+        datatype = TFLOAT;
+
+        if ( (outfptr->Fptr)->compress_type == HCOMPRESS_1) {
+	    /* need twice as much scratch space (8 bytes per pixel) */
+            tiledata = (double*) malloc (maxtilelen * 2 *sizeof (float));	
+	} else {
+            tiledata = (double*) malloc (maxtilelen * sizeof (float));
+	}
+    }
+    else if ((outfptr->Fptr)->zbitpix == DOUBLE_IMG)
+    {
+        datatype = TDOUBLE;
+        tiledata = (double*) malloc (maxtilelen * sizeof (double));
+    }
+    else if ((outfptr->Fptr)->zbitpix == SHORT_IMG)
+    {
+        datatype = TSHORT;
+        if ( (outfptr->Fptr)->compress_type == RICE_1  ||
+	     (outfptr->Fptr)->compress_type == GZIP_1  ||
+	     (outfptr->Fptr)->compress_type == GZIP_2  ||
+	     (outfptr->Fptr)->compress_type == BZIP2_1 ||
+             (outfptr->Fptr)->compress_type == NOCOMPRESS) {
+	    /* only need  buffer of I*2 pixels for gzip, bzip2, and Rice */
+
+            tiledata = (double*) malloc (maxtilelen * sizeof (short));	
+	} else {
+ 	    /*  need  buffer of I*4 pixels for Hcompress and PLIO */
+            tiledata = (double*) malloc (maxtilelen * sizeof (int));
+        }
+    }
+    else if ((outfptr->Fptr)->zbitpix == BYTE_IMG)
+    {
+
+        datatype = TBYTE;
+        if ( (outfptr->Fptr)->compress_type == RICE_1  ||
+	     (outfptr->Fptr)->compress_type == BZIP2_1 ||
+	     (outfptr->Fptr)->compress_type == GZIP_1  ||
+	     (outfptr->Fptr)->compress_type == GZIP_2) {
+	    /* only need  buffer of I*1 pixels for gzip, bzip2, and Rice */
+
+            tiledata = (double*) malloc (maxtilelen);	
+	} else {
+ 	    /*  need  buffer of I*4 pixels for Hcompress and PLIO */
+            tiledata = (double*) malloc (maxtilelen * sizeof (int));
+        }
+    }
+    else if ((outfptr->Fptr)->zbitpix == LONG_IMG)
+    {
+        datatype = TINT;
+        if ( (outfptr->Fptr)->compress_type == HCOMPRESS_1) {
+	    /* need twice as much scratch space (8 bytes per pixel) */
+
+            tiledata = (double*) malloc (maxtilelen * 2 * sizeof (int));	
+	} else {
+ 	    /* only need  buffer of I*4 pixels for gzip, bzip2,  Rice, and PLIO */
+
+            tiledata = (double*) malloc (maxtilelen * sizeof (int));
+        }
+    }
+    else
+    {
+	ffpmsg("Bad image datatype. (imcomp_compress_image)");
+	return (*status = MEMORY_ALLOCATION);
+    }
+    
+    if (tiledata == NULL)
+    {
+	ffpmsg("Out of memory. (imcomp_compress_image)");
+	return (*status = MEMORY_ALLOCATION);
+    }
+
+    /*  calculate size of tile in each dimension */
+    naxis = (outfptr->Fptr)->zndim;
+    for (ii = 0; ii < MAX_COMPRESS_DIM; ii++)
+    {
+        if (ii < naxis)
+        {
+             naxes[ii] = (outfptr->Fptr)->znaxis[ii];
+             tilesize[ii] = (outfptr->Fptr)->tilesize[ii];
+        }
+        else
+        {
+            naxes[ii] = 1;
+            tilesize[ii] = 1;
+        }
+    }
+    row = 1;
+
+    /* set up big loop over up to 6 dimensions */
+    for (i5 = 1; i5 <= naxes[5]; i5 += tilesize[5])
+    {
+     fpixel[5] = i5;
+     lpixel[5] = minvalue(i5 + tilesize[5] - 1, naxes[5]);
+     tile[5] = lpixel[5] - fpixel[5] + 1;
+     for (i4 = 1; i4 <= naxes[4]; i4 += tilesize[4])
+     {
+      fpixel[4] = i4;
+      lpixel[4] = minvalue(i4 + tilesize[4] - 1, naxes[4]);
+      tile[4] = lpixel[4] - fpixel[4] + 1;
+      for (i3 = 1; i3 <= naxes[3]; i3 += tilesize[3])
+      {
+       fpixel[3] = i3;
+       lpixel[3] = minvalue(i3 + tilesize[3] - 1, naxes[3]);
+       tile[3] = lpixel[3] - fpixel[3] + 1;
+       for (i2 = 1; i2 <= naxes[2]; i2 += tilesize[2])
+       {
+        fpixel[2] = i2;
+        lpixel[2] = minvalue(i2 + tilesize[2] - 1, naxes[2]);
+        tile[2] = lpixel[2] - fpixel[2] + 1;
+        for (i1 = 1; i1 <= naxes[1]; i1 += tilesize[1])
+        {
+         fpixel[1] = i1;
+         lpixel[1] = minvalue(i1 + tilesize[1] - 1, naxes[1]);
+         tile[1] = lpixel[1] - fpixel[1] + 1;
+         for (i0 = 1; i0 <= naxes[0]; i0 += tilesize[0])
+         {
+          fpixel[0] = i0;
+          lpixel[0] = minvalue(i0 + tilesize[0] - 1, naxes[0]);
+          tile[0] = lpixel[0] - fpixel[0] + 1;
+
+          /* number of pixels in this tile */
+          tilelen = tile[0];
+          for (ii = 1; ii < naxis; ii++)
+          {
+             tilelen *= tile[ii];
+          }
+
+          /* read next tile of data from image */
+	  anynul = 0;
+          if (datatype == TFLOAT)
+          {
+              ffgsve(infptr, 1, naxis, naxes, fpixel, lpixel, incre, 
+                  FLOATNULLVALUE, (float *) tiledata,  &anynul, status);
+          }
+          else if (datatype == TDOUBLE)
+          {
+              ffgsvd(infptr, 1, naxis, naxes, fpixel, lpixel, incre, 
+                  DOUBLENULLVALUE, tiledata, &anynul, status);
+          }
+          else if (datatype == TINT)
+          {
+              ffgsvk(infptr, 1, naxis, naxes, fpixel, lpixel, incre, 
+                  0, (int *) tiledata,  &anynul, status);
+          }
+          else if (datatype == TSHORT)
+          {
+              ffgsvi(infptr, 1, naxis, naxes, fpixel, lpixel, incre, 
+                  0, (short *) tiledata,  &anynul, status);
+          }
+          else if (datatype == TBYTE)
+          {
+              ffgsvb(infptr, 1, naxis, naxes, fpixel, lpixel, incre, 
+                  0, (unsigned char *) tiledata,  &anynul, status);
+          }
+          else 
+          {
+              ffpmsg("Error bad datatype of image tile to compress");
+              free(tiledata);
+              return (*status);
+          }
+
+          /* now compress the tile, and write to row of binary table */
+          /*   NOTE: we don't have to worry about the presence of null values in the
+	       array if it is an integer array:  the null value is simply encoded
+	       in the compressed array just like any other pixel value.  
+	       
+	       If it is a floating point array, then we need to check for null
+	       only if the anynul parameter returned a true value when reading the tile
+	  */
+          if (anynul && datatype == TFLOAT) {
+              imcomp_compress_tile(outfptr, row, datatype, tiledata, tilelen,
+                               tile[0], tile[1], 1, &fltnull, status);
+          } else if (anynul && datatype == TDOUBLE) {
+              imcomp_compress_tile(outfptr, row, datatype, tiledata, tilelen,
+                               tile[0], tile[1], 1, &dblnull, status);
+          } else {
+              imcomp_compress_tile(outfptr, row, datatype, tiledata, tilelen,
+                               tile[0], tile[1], 0, &dummy, status);
+          }
+
+          /* set flag if we found any null values */
+          if (anynul)
+              gotnulls = 1;
+
+          /* check for any error in the previous operations */
+          if (*status > 0)
+          {
+              ffpmsg("Error writing compressed image to table");
+              free(tiledata);
+              return (*status);
+          }
+
+	  row++;
+         }
+        }
+       }
+      }
+     }
+    }
+
+    free (tiledata);  /* finished with this buffer */
+
+    /* insert ZBLANK keyword if necessary; only for TFLOAT or TDOUBLE images */
+    if (gotnulls)
+    {
+          ffgcrd(outfptr, "ZCMPTYPE", card, status);
+          ffikyj(outfptr, "ZBLANK", COMPRESS_NULL_VALUE, 
+             "null value in the compressed integer array", status);
+    }
+
+    return (*status);
+}
+/*--------------------------------------------------------------------------*/
+int imcomp_compress_tile (fitsfile *outfptr,
+    long row,  /* tile number = row in the binary table that holds the compressed data */
+    int datatype, 
+    void *tiledata, 
+    long tilelen,
+    long tilenx,
+    long tileny,
+    int nullcheck,
+    void *nullflagval,
+    int *status)
+
+/*
+   This is the main compression routine.
+
+   This routine does the following to the input tile of pixels:
+        - if it is a float or double image, then it quantizes the pixels
+        - compresses the integer pixel values
+        - writes the compressed byte stream to the FITS file.
+
+   If the tile cannot be quantized than the raw float or double values
+   are losslessly compressed with gzip and then written to the output table.
+   
+   This input array may be modified by this routine.  If the array is of type TINT
+   or TFLOAT, and the compression type is HCOMPRESS, then it must have been 
+   allocated to be twice as large (8 bytes per pixel) to provide scratch space.
+
+  Note that this routine does not fully support the implicit datatype conversion that
+  is supported when writing to normal FITS images.  The datatype of the input array
+  must have the same datatype (either signed or unsigned) as the output (compressed)
+  FITS image in some cases.
+*/
+{
+    int *idata;		/* quantized integer data */
+    int cn_zblank, zbitpix, nullval;
+    int flag = 1;  /* true by default; only = 0 if float data couldn't be quantized */
+    int intlength;      /* size of integers to be compressed */
+    double scale, zero, actual_bzero;
+    long ii;
+    size_t clen;		/* size of cbuf */
+    short *cbuf;	/* compressed data */
+    int  nelem = 0;		/* number of bytes */
+    size_t gzip_nelem = 0;
+    unsigned int bzlen;
+    int ihcompscale;
+    float hcompscale;
+    double noise2, noise3, noise5;
+    double bscale[1] = {1.}, bzero[1] = {0.};	/* scaling parameters */
+    long  hcomp_len;
+    LONGLONG *lldata;
+
+    if (*status > 0)
+        return(*status);
+
+    /* check for special case of losslessly compressing floating point */
+    /* images.  Only compression algorithm that supports this is GZIP */
+    if ( (outfptr->Fptr)->quantize_level == NO_QUANTIZE) {
+       if (((outfptr->Fptr)->compress_type != GZIP_1) &&
+           ((outfptr->Fptr)->compress_type != GZIP_2)) {
+         ffpmsg("Lossless compression of floating point images must use GZIP");
+         return(*status = DATA_COMPRESSION_ERR);
+       }
+    }
+
+    /* free the previously saved tile if the input tile is for the same row */
+    if ((outfptr->Fptr)->tilerow == row) {
+        if ((outfptr->Fptr)->tiledata) {
+            free((outfptr->Fptr)->tiledata);
+        }
+	  
+        if ((outfptr->Fptr)->tilenullarray) {
+            free((outfptr->Fptr)->tilenullarray);
+        }
+
+        (outfptr->Fptr)->tiledata = 0;
+        (outfptr->Fptr)->tilenullarray = 0;
+        (outfptr->Fptr)->tilerow = 0;
+        (outfptr->Fptr)->tiledatasize = 0;
+        (outfptr->Fptr)->tiletype = 0;
+    }
+
+    if ( (outfptr->Fptr)->compress_type == NOCOMPRESS) {
+         /* Special case when using NOCOMPRESS for diagnostic purposes in fpack */
+         if (imcomp_write_nocompress_tile(outfptr, row, datatype, tiledata, tilelen, 
+	     nullcheck, nullflagval, status) > 0) {
+             return(*status);
+         }
+         return(*status);
+    }
+
+    /* =========================================================================== */
+    /* initialize various parameters */
+    idata = (int *) tiledata;   /* may overwrite the input tiledata in place */
+
+    /* zbitpix is the BITPIX keyword value in the uncompressed FITS image */
+    zbitpix = (outfptr->Fptr)->zbitpix;
+
+    /* if the tile/image has an integer datatype, see if a null value has */
+    /* been defined (with the BLANK keyword in a normal FITS image).  */
+    /* If so, and if the input tile array also contains null pixels, */
+    /* (represented by pixels that have a value = nullflagval) then  */
+    /* any pixels whose value = nullflagval, must be set to the value = nullval */
+    /* before the pixel array is compressed.  These null pixel values must */
+    /* not be inverse scaled by the BSCALE/BZERO values, if present. */
+
+    cn_zblank = (outfptr->Fptr)->cn_zblank;
+    nullval = (outfptr->Fptr)->zblank;
+
+    if (zbitpix > 0 && cn_zblank != -1)  /* If the integer image has no defined null */
+        nullcheck = 0;    /* value, then don't bother checking input array for nulls. */
+
+    /* if the BSCALE and BZERO keywords exist, then the input values must */
+    /* be inverse scaled by this factor, before the values are compressed. */
+    /* (The program may have turned off scaling, which over rides the keywords) */
+    
+    scale = (outfptr->Fptr)->cn_bscale;
+    zero  = (outfptr->Fptr)->cn_bzero;
+    actual_bzero = (outfptr->Fptr)->cn_actual_bzero;
+
+    /* =========================================================================== */
+    /* prepare the tile of pixel values for compression */
+    if (datatype == TSHORT) {
+       imcomp_convert_tile_tshort(outfptr, tiledata, tilelen, nullcheck, nullflagval,
+           nullval, zbitpix, scale, zero, actual_bzero, &intlength, status);
+    } else if (datatype == TUSHORT) {
+       imcomp_convert_tile_tushort(outfptr, tiledata, tilelen, nullcheck, nullflagval,
+           nullval, zbitpix, scale, zero, &intlength, status);
+    } else if (datatype == TBYTE) {
+       imcomp_convert_tile_tbyte(outfptr, tiledata, tilelen, nullcheck, nullflagval,
+           nullval, zbitpix, scale, zero,  &intlength, status);
+    } else if (datatype == TSBYTE) {
+       imcomp_convert_tile_tsbyte(outfptr, tiledata, tilelen, nullcheck, nullflagval,
+           nullval, zbitpix, scale, zero,  &intlength, status);
+    } else if (datatype == TINT) {
+       imcomp_convert_tile_tint(outfptr, tiledata, tilelen, nullcheck, nullflagval,
+           nullval, zbitpix, scale, zero, &intlength, status);
+    } else if (datatype == TUINT) {
+       imcomp_convert_tile_tuint(outfptr, tiledata, tilelen, nullcheck, nullflagval,
+           nullval, zbitpix, scale, zero, &intlength, status);
+    } else if (datatype == TLONG && sizeof(long) == 8) {
+           ffpmsg("Integer*8 Long datatype is not supported when writing to compressed images");
+           return(*status = BAD_DATATYPE);
+    } else if (datatype == TULONG && sizeof(long) == 8) {
+           ffpmsg("Unsigned integer*8 datatype is not supported when writing to compressed images");
+           return(*status = BAD_DATATYPE);
+    } else if (datatype == TFLOAT) {
+        imcomp_convert_tile_tfloat(outfptr, row, tiledata, tilelen, tilenx, tileny, nullcheck,
+        nullflagval, nullval, zbitpix, scale, zero, &intlength, &flag, bscale, bzero, status);
+    } else if (datatype == TDOUBLE) {
+       imcomp_convert_tile_tdouble(outfptr, row, tiledata, tilelen, tilenx, tileny, nullcheck,
+       nullflagval, nullval, zbitpix, scale, zero, &intlength, &flag, bscale, bzero, status);
+    } else {
+          ffpmsg("unsupported image datatype (imcomp_compress_tile)");
+          return(*status = BAD_DATATYPE);
+    }
+
+    if (*status > 0)
+      return(*status);      /* return if error occurs */
+
+    /* =========================================================================== */
+    if (flag)   /* now compress the integer data array */
+    {
+        /* allocate buffer for the compressed tile bytes */
+        clen = (outfptr->Fptr)->maxelem;
+        cbuf = (short *) calloc (clen, sizeof (unsigned char));
+
+        if (cbuf == NULL) {
+            ffpmsg("Memory allocation failure. (imcomp_compress_tile)");
+	    return (*status = MEMORY_ALLOCATION);
+        }
+
+        /* =========================================================================== */
+        if ( (outfptr->Fptr)->compress_type == RICE_1)
+        {
+            if (intlength == 2) {
+  	        nelem = fits_rcomp_short ((short *)idata, tilelen, (unsigned char *) cbuf,
+                       clen, (outfptr->Fptr)->rice_blocksize);
+            } else if (intlength == 1) {
+  	        nelem = fits_rcomp_byte ((signed char *)idata, tilelen, (unsigned char *) cbuf,
+                       clen, (outfptr->Fptr)->rice_blocksize);
+            } else {
+  	        nelem = fits_rcomp (idata, tilelen, (unsigned char *) cbuf,
+                       clen, (outfptr->Fptr)->rice_blocksize);
+            }
+
+	    if (nelem < 0)  /* data compression error condition */
+            {
+	        free (cbuf);
+                ffpmsg("error Rice compressing image tile (imcomp_compress_tile)");
+                return (*status = DATA_COMPRESSION_ERR);
+            }
+
+	    /* Write the compressed byte stream. */
+            ffpclb(outfptr, (outfptr->Fptr)->cn_compressed, row, 1,
+                     nelem, (unsigned char *) cbuf, status);
+        }
+
+        /* =========================================================================== */
+        else if ( (outfptr->Fptr)->compress_type == PLIO_1)
+        {
+              for (ii = 0; ii < tilelen; ii++)  {
+                if (idata[ii] < 0 || idata[ii] > 16777215)
+                {
+                   /* plio algorithn only supports positive 24 bit ints */
+                   ffpmsg("data out of range for PLIO compression (0 - 2**24)");
+                   return(*status = DATA_COMPRESSION_ERR);
+                }
+              }
+
+  	      nelem = pl_p2li (idata, 1, cbuf, tilelen);
+
+	      if (nelem < 0)  /* data compression error condition */
+              {
+	        free (cbuf);
+                ffpmsg("error PLIO compressing image tile (imcomp_compress_tile)");
+                return (*status = DATA_COMPRESSION_ERR);
+              }
+
+	      /* Write the compressed byte stream. */
+              ffpcli(outfptr, (outfptr->Fptr)->cn_compressed, row, 1,
+                     nelem, cbuf, status);
+        }
+
+        /* =========================================================================== */
+        else if ( ((outfptr->Fptr)->compress_type == GZIP_1) ||
+                  ((outfptr->Fptr)->compress_type == GZIP_2) )   {
+
+	    if ((outfptr->Fptr)->quantize_level == NO_QUANTIZE && datatype == TFLOAT) {
+	      /* Special case of losslessly compressing floating point pixels with GZIP */
+	      /* In this case we compress the input tile array directly */
+
+#if BYTESWAPPED
+               ffswap4((int*) tiledata, tilelen); 
+#endif
+               if ( (outfptr->Fptr)->compress_type == GZIP_2 )
+		    fits_shuffle_4bytes((char *) tiledata, tilelen, status);
+
+                compress2mem_from_mem((char *) tiledata, tilelen * sizeof(float),
+                    (char **) &cbuf,  &clen, realloc, &gzip_nelem, status);
+
+	    } else if ((outfptr->Fptr)->quantize_level == NO_QUANTIZE && datatype == TDOUBLE) {
+	      /* Special case of losslessly compressing double pixels with GZIP */
+	      /* In this case we compress the input tile array directly */
+
+#if BYTESWAPPED
+               ffswap8((double *) tiledata, tilelen); 
+#endif
+               if ( (outfptr->Fptr)->compress_type == GZIP_2 )
+		    fits_shuffle_8bytes((char *) tiledata, tilelen, status);
+
+                compress2mem_from_mem((char *) tiledata, tilelen * sizeof(double),
+                    (char **) &cbuf,  &clen, realloc, &gzip_nelem, status);
+
+	    } else {
+
+	        /* compress the integer idata array */
+
+#if BYTESWAPPED
+	       if (intlength == 2)
+                 ffswap2((short *) idata, tilelen); 
+	       else if (intlength == 4)
+                 ffswap4(idata, tilelen); 
+#endif
+
+               if (intlength == 2) {
+
+                  if ( (outfptr->Fptr)->compress_type == GZIP_2 )
+		    fits_shuffle_2bytes((char *) tiledata, tilelen, status);
+
+                  compress2mem_from_mem((char *) idata, tilelen * sizeof(short),
+                   (char **) &cbuf,  &clen, realloc, &gzip_nelem, status);
+
+               } else if (intlength == 1) {
+
+                  compress2mem_from_mem((char *) idata, tilelen * sizeof(unsigned char),
+                   (char **) &cbuf,  &clen, realloc, &gzip_nelem, status);
+
+               } else {
+
+                  if ( (outfptr->Fptr)->compress_type == GZIP_2 )
+		    fits_shuffle_4bytes((char *) tiledata, tilelen, status);
+
+                  compress2mem_from_mem((char *) idata, tilelen * sizeof(int),
+                   (char **) &cbuf,  &clen, realloc, &gzip_nelem, status);
+               }
+            }
+
+	    /* Write the compressed byte stream. */
+            ffpclb(outfptr, (outfptr->Fptr)->cn_compressed, row, 1,
+                     gzip_nelem, (unsigned char *) cbuf, status);
+
+        /* =========================================================================== */
+        } else if ( (outfptr->Fptr)->compress_type == BZIP2_1) {
+
+#if BYTESWAPPED
+	   if (intlength == 2)
+               ffswap2((short *) idata, tilelen); 
+	   else if (intlength == 4)
+               ffswap4(idata, tilelen); 
+#endif
+
+           bzlen = (unsigned int) clen;
+	   
+           /* call bzip2 with blocksize = 900K, verbosity = 0, and default workfactor */
+
+/*  bzip2 is not supported in the public release.  This is only for test purposes.
+           if (BZ2_bzBuffToBuffCompress( (char *) cbuf, &bzlen,
+	         (char *) idata, (unsigned int) (tilelen * intlength), 9, 0, 0) ) 
+*/
+	   {
+                   ffpmsg("bzip2 compression error");
+                   return(*status = DATA_COMPRESSION_ERR);
+           }
+
+	    /* Write the compressed byte stream. */
+            ffpclb(outfptr, (outfptr->Fptr)->cn_compressed, row, 1,
+                     bzlen, (unsigned char *) cbuf, status);
+
+        /* =========================================================================== */
+        }  else if ( (outfptr->Fptr)->compress_type == HCOMPRESS_1)     {
+	    /*
+	      if hcompscale is positive, then we have to multiply
+	      the value by the RMS background noise to get the 
+	      absolute scale value.  If negative, then it gives the
+	      absolute scale value directly.
+	    */
+            hcompscale = (outfptr->Fptr)->hcomp_scale;
+
+	    if (hcompscale > 0.) {
+	       fits_img_stats_int(idata, tilenx, tileny, nullcheck,
+	                nullval, 0,0,0,0,0,0,&noise2,&noise3,&noise5,status);
+
+		/* use the minimum of the 3 noise estimates */
+		if (noise2 != 0. && noise2 < noise3) noise3 = noise2;
+		if (noise5 != 0. && noise5 < noise3) noise3 = noise5;
+		
+		hcompscale = (float) (hcompscale * noise3);
+
+	    } else if (hcompscale < 0.) {
+
+		hcompscale = hcompscale * -1.0F;
+	    }
+
+	    ihcompscale = (int) (hcompscale + 0.5);
+
+            hcomp_len = clen;  /* allocated size of the buffer */
+	    
+            if (zbitpix == BYTE_IMG || zbitpix == SHORT_IMG) {
+                fits_hcompress(idata, tilenx, tileny, 
+		  ihcompscale, (char *) cbuf, &hcomp_len, status);
+
+            } else {
+                 /* have to convert idata to an I*8 array, in place */
+                 /* idata must have been allocated large enough to do this */
+                lldata = (LONGLONG *) idata;
+		
+                for (ii = tilelen - 1; ii >= 0; ii--) {
+		    lldata[ii] = idata[ii];
+		}
+
+                fits_hcompress64(lldata, tilenx, tileny, 
+		  ihcompscale, (char *) cbuf, &hcomp_len, status);
+            }
+
+	    /* Write the compressed byte stream. */
+            ffpclb(outfptr, (outfptr->Fptr)->cn_compressed, row, 1,
+                     hcomp_len, (unsigned char *) cbuf, status);
+        }
+
+        /* =========================================================================== */
+        if ((outfptr->Fptr)->cn_zscale > 0)
+        {
+              /* write the linear scaling parameters for this tile */
+	      ffpcld (outfptr, (outfptr->Fptr)->cn_zscale, row, 1, 1,
+                      bscale, status);
+	      ffpcld (outfptr, (outfptr->Fptr)->cn_zzero,  row, 1, 1,
+                      bzero,  status);
+        }
+
+        free(cbuf);  /* finished with this buffer */
+
+    /* =========================================================================== */
+    } else {    /* if flag == 0., floating point data couldn't be quantized */
+
+	 /* losslessly compress the data with gzip. */
+
+         /* if gzip2 compressed data column doesn't exist, create it */
+         if ((outfptr->Fptr)->cn_gzip_data < 1) {
+                 fits_insert_col(outfptr, 999, "GZIP_COMPRESSED_DATA", "1PB", status);
+
+                 if (*status <= 0)  /* save the number of this column */
+                       ffgcno(outfptr, CASEINSEN, "GZIP_COMPRESSED_DATA",
+                                &(outfptr->Fptr)->cn_gzip_data, status);
+         }
+ 
+         if (datatype == TFLOAT)  {
+               /* allocate buffer for the compressed tile bytes */
+	       /* make it 10% larger than the original uncompressed data */
+               clen = tilelen * sizeof(float) * 1.1;
+               cbuf = (short *) calloc (clen, sizeof (unsigned char));
+
+               if (cbuf == NULL)
+               {
+                   ffpmsg("Memory allocation error. (imcomp_compress_tile)");
+	           return (*status = MEMORY_ALLOCATION);
+               }
+
+	       /* convert null values to NaNs in place, if necessary */
+	       if (nullcheck == 1) {
+	           imcomp_float2nan((float *) tiledata, tilelen, (int *) tiledata,
+	               *(float *) (nullflagval), status);
+	       }
+
+#if BYTESWAPPED
+               ffswap4((int*) tiledata, tilelen); 
+#endif
+               compress2mem_from_mem((char *) tiledata, tilelen * sizeof(float),
+                    (char **) &cbuf,  &clen, realloc, &gzip_nelem, status);
+
+         } else if (datatype == TDOUBLE) {
+
+               /* allocate buffer for the compressed tile bytes */
+	       /* make it 10% larger than the original uncompressed data */
+               clen = tilelen * sizeof(double) * 1.1;
+               cbuf = (short *) calloc (clen, sizeof (unsigned char));
+
+               if (cbuf == NULL)
+               {
+                   ffpmsg("Memory allocation error. (imcomp_compress_tile)");
+	           return (*status = MEMORY_ALLOCATION);
+               }
+
+	       /* convert null values to NaNs in place, if necessary */
+	       if (nullcheck == 1) {
+	           imcomp_double2nan((double *) tiledata, tilelen, (LONGLONG *) tiledata,
+	               *(double *) (nullflagval), status);
+	       }
+
+#if BYTESWAPPED
+               ffswap8((double*) tiledata, tilelen); 
+#endif
+               compress2mem_from_mem((char *) tiledata, tilelen * sizeof(double),
+                    (char **) &cbuf,  &clen, realloc, &gzip_nelem, status);
+        }
+
+	/* Write the compressed byte stream. */
+        ffpclb(outfptr, (outfptr->Fptr)->cn_gzip_data, row, 1,
+             gzip_nelem, (unsigned char *) cbuf, status);
+
+        free(cbuf);  /* finished with this buffer */
+    }
+
+    return(*status);
+}
+
+/*--------------------------------------------------------------------------*/
+int imcomp_write_nocompress_tile(fitsfile *outfptr,
+    long row,
+    int datatype, 
+    void *tiledata, 
+    long tilelen,
+    int nullcheck,
+    void *nullflagval,
+    int *status)
+{
+    char coltype[4];
+
+    /* Write the uncompressed image tile pixels to the tile-compressed image file. */
+    /* This is a special case when using NOCOMPRESS for diagnostic purposes in fpack. */ 
+    /* Currently, this only supports a limited number of data types and */
+    /* does not fully support null-valued pixels in the image. */
+
+    if ((outfptr->Fptr)->cn_uncompressed < 1) {
+        /* uncompressed data column doesn't exist, so append new column to table */
+        if (datatype == TSHORT) {
+	    strcpy(coltype, "1PI");
+	} else if (datatype == TINT) {
+	    strcpy(coltype, "1PJ");
+	} else if (datatype == TFLOAT) {
+	    strcpy(coltype, "1PE");
+        } else {
+	    ffpmsg("NOCOMPRESSION option only supported for int*2, int*4, and float*4 images");
+            return(*status = DATA_COMPRESSION_ERR);
+        }
+
+        fits_insert_col(outfptr, 999, "UNCOMPRESSED_DATA", coltype, status); /* create column */
+    }
+
+    fits_get_colnum(outfptr, CASEINSEN, "UNCOMPRESSED_DATA",
+                    &(outfptr->Fptr)->cn_uncompressed, status);  /* save col. num. */
+    
+    fits_write_col(outfptr, datatype, (outfptr->Fptr)->cn_uncompressed, row, 1,
+                      tilelen, tiledata, status);  /* write the tile data */
+    return (*status);
+}
+ /*--------------------------------------------------------------------------*/
+int imcomp_convert_tile_tshort(
+    fitsfile *outfptr,
+    void *tiledata, 
+    long tilelen,
+    int nullcheck,
+    void *nullflagval,
+    int nullval,
+    int zbitpix,
+    double scale,
+    double zero,
+    double actual_bzero,
+    int *intlength,
+    int *status)
+{
+    /*  Prepare the input tile array of pixels for compression.
+    /*  Convert input integer*2 tile array in place to 4 or 8-byte ints for compression, */
+    /*  If needed, convert 4 or 8-byte ints and do null value substitution. */
+    /*  Note that the calling routine must have allocated the input array big enough */
+    /* to be able to do this.  */
+
+    short *sbuff;
+    int flagval, *idata;
+    long ii;
+    
+       /* We only support writing this integer*2 tile data to a FITS image with 
+          BITPIX = 16 and with BZERO = 0 and BSCALE = 1.  */
+	  
+       if (zbitpix != SHORT_IMG || scale != 1.0 || zero != 0.0) {
+           ffpmsg("Datatype conversion/scaling is not supported when writing to compressed images");
+           return(*status = DATA_COMPRESSION_ERR);
+       } 
+
+       sbuff = (short *) tiledata;
+       idata = (int *) tiledata;
+       
+       if ( (outfptr->Fptr)->compress_type == RICE_1 || (outfptr->Fptr)->compress_type == GZIP_1
+         || (outfptr->Fptr)->compress_type == GZIP_2 || (outfptr->Fptr)->compress_type == BZIP2_1 ) 
+       {
+           /* don't have to convert to int if using gzip, bzip2 or Rice compression */
+           *intlength = 2;
+             
+           if (nullcheck == 1) {
+               /* reset pixels equal to flagval to the FITS null value, prior to compression */
+               flagval = *(short *) (nullflagval);
+               if (flagval != nullval) {
+                  for (ii = tilelen - 1; ii >= 0; ii--) {
+	            if (sbuff[ii] == (short) flagval)
+		       sbuff[ii] = (short) nullval;
+                  }
+               }
+           }
+       } else if ((outfptr->Fptr)->compress_type == HCOMPRESS_1) {
+           /* have to convert to int if using HCOMPRESS */
+           *intlength = 4;
+
+           if (nullcheck == 1) {
+               /* reset pixels equal to flagval to the FITS null value, prior to compression */
+               flagval = *(short *) (nullflagval);
+               for (ii = tilelen - 1; ii >= 0; ii--) {
+	            if (sbuff[ii] == (short) flagval)
+		       idata[ii] = nullval;
+                    else
+                       idata[ii] = (int) sbuff[ii];
+               }
+           } else {  /* just do the data type conversion to int */
+               for (ii = tilelen - 1; ii >= 0; ii--) 
+                   idata[ii] = (int) sbuff[ii];
+           }
+       } else {
+           /* have to convert to int if using PLIO */
+           *intlength = 4;
+           if (zero == 0. && actual_bzero == 32768.) {
+             /* Here we are compressing unsigned 16-bit integers that have */
+	     /* been offset by -32768 using the standard FITS convention. */
+	     /* Since PLIO cannot deal with negative values, we must apply */
+	     /* the shift of 32786 to the values to make them all positive. */
+	     /* The inverse negative shift will be applied in */
+	     /* imcomp_decompress_tile when reading the compressed tile. */
+             if (nullcheck == 1) {
+               /* reset pixels equal to flagval to the FITS null value, prior to compression */
+               flagval = *(short *) (nullflagval);
+               for (ii = tilelen - 1; ii >= 0; ii--) {
+	            if (sbuff[ii] == (short) flagval)
+		       idata[ii] = nullval;
+                    else
+                       idata[ii] = (int) sbuff[ii] + 32768;
+               }
+             } else {  /* just do the data type conversion to int */
+               for (ii = tilelen - 1; ii >= 0; ii--) 
+                   idata[ii] = (int) sbuff[ii] + 32768;
+             }
+           } else {
+	     /* This is not an unsigned 16-bit integer array, so process normally */
+             if (nullcheck == 1) {
+               /* reset pixels equal to flagval to the FITS null value, prior to compression */
+               flagval = *(short *) (nullflagval);
+               for (ii = tilelen - 1; ii >= 0; ii--) {
+	            if (sbuff[ii] == (short) flagval)
+		       idata[ii] = nullval;
+                    else
+                       idata[ii] = (int) sbuff[ii];
+               }
+             } else {  /* just do the data type conversion to int */
+               for (ii = tilelen - 1; ii >= 0; ii--) 
+                   idata[ii] = (int) sbuff[ii];
+             }
+           }
+        }
+        return(*status);
+}
+ /*--------------------------------------------------------------------------*/
+int imcomp_convert_tile_tushort(
+    fitsfile *outfptr,
+    void *tiledata, 
+    long tilelen,
+    int nullcheck,
+    void *nullflagval,
+    int nullval,
+    int zbitpix,
+    double scale,
+    double zero,
+    int *intlength,
+    int *status)
+{
+    /*  Prepare the input  tile array of pixels for compression.
+    /*  Convert input integer*2 tile array in place to 4 or 8-byte ints for compression, */
+    /*  If needed, convert 4 or 8-byte ints and do null value substitution. */
+    /*  Note that the calling routine must have allocated the input array big enough */
+    /* to be able to do this.  */
+
+    unsigned short *usbuff;
+    short *sbuff;
+    int flagval, *idata;
+    long ii;
+    
+       /* datatype of input array is unsigned short.  We only support writing this datatype
+          to a FITS image with BITPIX = 16 and with BZERO = 0 and BSCALE = 32768.  */
+
+       if (zbitpix != SHORT_IMG || scale != 1.0 || zero != 32768.) {
+           ffpmsg("Implicit datatype conversion is not supported when writing to compressed images");
+           return(*status = DATA_COMPRESSION_ERR);
+       } 
+
+       usbuff = (unsigned short *) tiledata;
+       sbuff = (short *) tiledata;
+       idata = (int *) tiledata;
+
+       if ((outfptr->Fptr)->compress_type == RICE_1 || (outfptr->Fptr)->compress_type == GZIP_1
+        || (outfptr->Fptr)->compress_type == GZIP_2 || (outfptr->Fptr)->compress_type == BZIP2_1) 
+       {
+           /* don't have to convert to int if using gzip, bzip2, or Rice compression */
+           *intlength = 2;
+
+          /* offset the unsigned value by -32768 to a signed short value. */
+	  /* It is more efficient to do this by just flipping the most significant of the 16 bits */
+
+           if (nullcheck == 1) {
+               /* reset pixels equal to flagval to the FITS null value, prior to compression  */
+               flagval = *(unsigned short *) (nullflagval);
+               for (ii = tilelen - 1; ii >= 0; ii--) {
+	            if (usbuff[ii] == (unsigned short) flagval)
+		       sbuff[ii] = (short) nullval;
+                    else
+		       usbuff[ii] =  (usbuff[ii]) ^ 0x8000;
+               }
+           } else {
+               /* just offset the pixel values by 32768 (by flipping the MSB */
+               for (ii = tilelen - 1; ii >= 0; ii--)
+		       usbuff[ii] =  (usbuff[ii]) ^ 0x8000;
+           }
+       } else {
+           /* have to convert to int if using HCOMPRESS or PLIO */
+           *intlength = 4;
+
+           if (nullcheck == 1) {
+               /* offset the pixel values by 32768, and */
+               /* reset pixels equal to flagval to nullval */
+               flagval = *(unsigned short *) (nullflagval);
+               for (ii = tilelen - 1; ii >= 0; ii--) {
+	            if (usbuff[ii] == (unsigned short) flagval)
+		       idata[ii] = nullval;
+                    else
+		       idata[ii] = ((int) usbuff[ii]) - 32768;
+               }
+           } else {  /* just do the data type conversion to int */
+               for (ii = tilelen - 1; ii >= 0; ii--)
+		       idata[ii] = ((int) usbuff[ii]) - 32768;
+           }
+        }
+
+        return(*status);
+}
+ /*--------------------------------------------------------------------------*/
+int imcomp_convert_tile_tint(
+    fitsfile *outfptr,
+    void *tiledata, 
+    long tilelen,
+    int nullcheck,
+    void *nullflagval,
+    int nullval,
+    int zbitpix,
+    double scale,
+    double zero,
+    int *intlength,
+    int *status)
+{
+    /*  Prepare the input tile array of pixels for compression.
+    /*  Convert input integer*2 tile array in place to 4 or 8-byte ints for compression, */
+    /*  If needed, convert 4 or 8-byte ints and do null value substitution. */
+    /*  Note that the calling routine must have allocated the input array big enough */
+    /* to be able to do this.  */
+
+    int flagval, *idata;
+    long ii;
+    
+ 
+        /* datatype of input array is int.  We only support writing this datatype
+           to a FITS image with BITPIX = 32 and with BZERO = 0 and BSCALE = 1.  */
+
+       if (zbitpix != LONG_IMG || scale != 1.0 || zero != 0.) {
+           ffpmsg("Implicit datatype conversion is not supported when writing to compressed images");
+           return(*status = DATA_COMPRESSION_ERR);
+       } 
+
+       idata = (int *) tiledata;
+       *intlength = 4;
+
+       if (nullcheck == 1) {
+               /* no datatype conversion is required for any of the compression algorithms,
+	         except possibly for HCOMPRESS (to I*8), which is handled later.
+		 Just reset pixels equal to flagval to the FITS null value */
+               flagval = *(int *) (nullflagval);
+               if (flagval != nullval) {
+                  for (ii = tilelen - 1; ii >= 0; ii--) {
+	            if (idata[ii] == flagval)
+		       idata[ii] = nullval;
+                  }
+               }
+       }
+
+       return(*status);
+}
+ /*--------------------------------------------------------------------------*/
+int imcomp_convert_tile_tuint(
+    fitsfile *outfptr,
+    void *tiledata, 
+    long tilelen,
+    int nullcheck,
+    void *nullflagval,
+    int nullval,
+    int zbitpix,
+    double scale,
+    double zero,
+    int *intlength,
+    int *status)
+{
+    /*  Prepare the input tile array of pixels for compression.
+    /*  Convert input integer*2 tile array in place to 4 or 8-byte ints for compression, */
+    /*  If needed, convert 4 or 8-byte ints and do null value substitution. */
+    /*  Note that the calling routine must have allocated the input array big enough */
+    /* to be able to do this.  */
+
+    int flagval, *idata;
+    unsigned int *uintbuff, uintflagval;
+    long ii;
+    
+ 
+       /* datatype of input array is unsigned int.  We only support writing this datatype
+          to a FITS image with BITPIX = 32 and with BZERO = 0 and BSCALE = 2147483648.  */
+
+       if (zbitpix != LONG_IMG || scale != 1.0 || zero != 2147483648.) {
+           ffpmsg("Implicit datatype conversion is not supported when writing to compressed images");
+           return(*status = DATA_COMPRESSION_ERR);
+       } 
+
+       *intlength = 4;
+       idata = (int *) tiledata;
+       uintbuff = (unsigned int *) tiledata;
+
+       /* offset the unsigned value by -2147483648 to a signed int value. */
+       /* It is more efficient to do this by just flipping the most significant of the 32 bits */
+
+       if (nullcheck == 1) {
+               /* reset pixels equal to flagval to nullval and */
+               /* offset the other pixel values (by flipping the MSB) */
+               uintflagval = *(unsigned int *) (nullflagval);
+               for (ii = tilelen - 1; ii >= 0; ii--) {
+	            if (uintbuff[ii] == uintflagval)
+		       idata[ii] = nullval;
+                    else
+		       uintbuff[ii] = (uintbuff[ii]) ^ 0x80000000;
+               }
+       } else {
+               /* just offset the pixel values (by flipping the MSB) */
+               for (ii = tilelen - 1; ii >= 0; ii--)
+		       uintbuff[ii] = (uintbuff[ii]) ^ 0x80000000;
+       }
+
+       return(*status);
+}
+ /*--------------------------------------------------------------------------*/
+int imcomp_convert_tile_tbyte(
+    fitsfile *outfptr,
+    void *tiledata, 
+    long tilelen,
+    int nullcheck,
+    void *nullflagval,
+    int nullval,
+    int zbitpix,
+    double scale,
+    double zero,
+    int *intlength,
+    int *status)
+{
+    /*  Prepare the input tile array of pixels for compression.
+    /*  Convert input integer*2 tile array in place to 4 or 8-byte ints for compression, */
+    /*  If needed, convert 4 or 8-byte ints and do null value substitution. */
+    /*  Note that the calling routine must have allocated the input array big enough */
+    /* to be able to do this.  */
+
+    int flagval, *idata;
+    long ii;
+    unsigned char *usbbuff;
+        
+       /* datatype of input array is unsigned byte.  We only support writing this datatype
+          to a FITS image with BITPIX = 8 and with BZERO = 0 and BSCALE = 1.  */
+
+       if (zbitpix != BYTE_IMG || scale != 1.0 || zero != 0.) {
+           ffpmsg("Implicit datatype conversion is not supported when writing to compressed images");
+           return(*status = DATA_COMPRESSION_ERR);
+       } 
+
+       idata = (int *) tiledata;
+       usbbuff = (unsigned char *) tiledata;
+
+       if ( (outfptr->Fptr)->compress_type == RICE_1 || (outfptr->Fptr)->compress_type == GZIP_1
+         || (outfptr->Fptr)->compress_type == GZIP_2 || (outfptr->Fptr)->compress_type == BZIP2_1 ) 
+       {
+           /* don't have to convert to int if using gzip, bzip2, or Rice compression */
+           *intlength = 1;
+             
+           if (nullcheck == 1) {
+               /* reset pixels equal to flagval to the FITS null value, prior to compression */
+               flagval = *(unsigned char *) (nullflagval);
+               if (flagval != nullval) {
+                  for (ii = tilelen - 1; ii >= 0; ii--) {
+	            if (usbbuff[ii] == (unsigned char) flagval)
+		       usbbuff[ii] = (unsigned char) nullval;
+                    }
+               }
+           }
+       } else {
+           /* have to convert to int if using HCOMPRESS or PLIO */
+           *intlength = 4;
+
+           if (nullcheck == 1) {
+               /* reset pixels equal to flagval to the FITS null value, prior to compression */
+               flagval = *(unsigned char *) (nullflagval);
+               for (ii = tilelen - 1; ii >= 0; ii--) {
+	            if (usbbuff[ii] == (unsigned char) flagval)
+		       idata[ii] = nullval;
+                    else
+                       idata[ii] = (int) usbbuff[ii];
+               }
+           } else {  /* just do the data type conversion to int */
+               for (ii = tilelen - 1; ii >= 0; ii--) 
+                   idata[ii] = (int) usbbuff[ii];
+           }
+       }
+
+       return(*status);
+}
+ /*--------------------------------------------------------------------------*/
+int imcomp_convert_tile_tsbyte(
+    fitsfile *outfptr,
+    void *tiledata, 
+    long tilelen,
+    int nullcheck,
+    void *nullflagval,
+    int nullval,
+    int zbitpix,
+    double scale,
+    double zero,
+    int *intlength,
+    int *status)
+{
+    /*  Prepare the input tile array of pixels for compression.
+    /*  Convert input integer*2 tile array in place to 4 or 8-byte ints for compression, */
+    /*  If needed, convert 4 or 8-byte ints and do null value substitution. */
+    /*  Note that the calling routine must have allocated the input array big enough */
+    /* to be able to do this.  */
+
+    int flagval, *idata;
+    long ii;
+    signed char *sbbuff;
+ 
+       /* datatype of input array is signed byte.  We only support writing this datatype
+          to a FITS image with BITPIX = 8 and with BZERO = 0 and BSCALE = -128.  */
+
+       if (zbitpix != BYTE_IMG|| scale != 1.0 || zero != -128.) {
+           ffpmsg("Implicit datatype conversion is not supported when writing to compressed images");
+           return(*status = DATA_COMPRESSION_ERR);
+       }
+
+       idata = (int *) tiledata;
+       sbbuff = (signed char *) tiledata;
+
+       if ( (outfptr->Fptr)->compress_type == RICE_1 || (outfptr->Fptr)->compress_type == GZIP_1
+         || (outfptr->Fptr)->compress_type == GZIP_2 || (outfptr->Fptr)->compress_type == BZIP2_1 ) 
+       {
+           /* don't have to convert to int if using gzip, bzip2 or Rice compression */
+           *intlength = 1;
+             
+           if (nullcheck == 1) {
+               /* reset pixels equal to flagval to the FITS null value, prior to compression */
+               /* offset the other pixel values (by flipping the MSB) */
+
+               flagval = *(signed char *) (nullflagval);
+               for (ii = tilelen - 1; ii >= 0; ii--) {
+	            if (sbbuff[ii] == (signed char) flagval)
+		       sbbuff[ii] = (signed char) nullval;
+                    else
+		       sbbuff[ii] = (sbbuff[ii]) ^ 0x80;               }
+           } else {  /* just offset the pixel values (by flipping the MSB) */
+               for (ii = tilelen - 1; ii >= 0; ii--) 
+		       sbbuff[ii] = (sbbuff[ii]) ^ 0x80;
+           }
+
+       } else {
+           /* have to convert to int if using HCOMPRESS or PLIO */
+           *intlength = 4;
+
+           if (nullcheck == 1) {
+               /* reset pixels equal to flagval to the FITS null value, prior to compression */
+               flagval = *(signed char *) (nullflagval);
+               for (ii = tilelen - 1; ii >= 0; ii--) {
+	            if (sbbuff[ii] == (signed char) flagval)
+		       idata[ii] = nullval;
+                    else
+                       idata[ii] = ((int) sbbuff[ii]) + 128;
+               }
+           } else {  /* just do the data type conversion to int */
+               for (ii = tilelen - 1; ii >= 0; ii--) 
+                   idata[ii] = ((int) sbbuff[ii]) + 128;
+           }
+       }
+ 
+       return(*status);
+}
+ /*--------------------------------------------------------------------------*/
+int imcomp_convert_tile_tfloat(
+    fitsfile *outfptr,
+    long row,
+    void *tiledata, 
+    long tilelen,
+    long tilenx,
+    long tileny,
+    int nullcheck,
+    void *nullflagval,
+    int nullval,
+    int zbitpix,
+    double scale,
+    double zero,
+    int *intlength,
+    int *flag,
+    double *bscale,
+    double *bzero,
+    int *status)
+{
+    /*  Prepare the input tile array of pixels for compression.
+    /*  Convert input integer*2 tile array in place to 4 or 8-byte ints for compression, */
+    /*  If needed, convert 4 or 8-byte ints and do null value substitution. */
+    /*  Note that the calling routine must have allocated the input array big enough */
+    /* to be able to do this.  */
+
+    int flagval, *idata;
+    long irow, ii;
+    float floatnull;
+    unsigned char *usbbuff;
+    unsigned long dithersum;
+    int iminval = 0, imaxval = 0;  /* min and max quantized integers */
+
+           *intlength = 4;
+           idata = (int *) tiledata;
+
+          /* if the tile-compressed table contains zscale and zzero columns */
+          /* then scale and quantize the input floating point data.    */
+
+          if ((outfptr->Fptr)->cn_zscale > 0) {
+	    /* quantize the float values into integers */
+
+            if (nullcheck == 1)
+	      floatnull = *(float *) (nullflagval);
+	    else
+	      floatnull = FLOATNULLVALUE;  /* NaNs are represented by this, by default */
+
+            if ((outfptr->Fptr)->quantize_dither == SUBTRACTIVE_DITHER_1) {
+	      
+	          /* see if the dithering offset value needs to be initialized */                  
+	          if ((outfptr->Fptr)->request_dither_offset == 0 && (outfptr->Fptr)->dither_offset == 0) {
+
+		     /* This means randomly choose the dithering offset based on the system time. */
+		     /* The offset will have a value between 1 and 10000, inclusive. */
+		     /* The time function returns an integer value that is incremented each second. */
+		     /* The clock function returns the elapsed CPU time, in integer CLOCKS_PER_SEC units. */
+		     /* The CPU time returned by clock is typically (on linux PC) only good to 0.01 sec */
+		     /* Summing the 2 quantities may help avoid cases where 2 executions of the program */
+		     /* (perhaps in a multithreaded environoment) end up with exactly the same dither_offset */
+		     /* value.  The sum is incremented by the current HDU number in the file to provide */
+		     /* further randomization.  This randomization is desireable if multiple compressed */
+		     /* images will be summed (or differenced). In such cases, the benefits of dithering */
+		     /* may be lost if all the images use exactly the same sequence of random numbers when */
+		     /* calculating the dithering offsets. */	     
+		     
+		     (outfptr->Fptr)->dither_offset = 
+		       (( (int)time(NULL) + ( (int) clock() / (int) (CLOCKS_PER_SEC / 100)) + (outfptr->Fptr)->curhdu) % 10000) + 1;
+		     
+                     /* update the header keyword with this new value */
+		     fits_update_key(outfptr, TINT, "ZDITHER0", &((outfptr->Fptr)->dither_offset), 
+	                        NULL, status);
+
+	          } else if ((outfptr->Fptr)->request_dither_offset < 0 && (outfptr->Fptr)->dither_offset < 0) {
+
+		     /* this means randomly choose the dithering offset based on some hash function */
+		     /* of the first input tile of data to be quantized and compressed.  This ensures that */
+                     /* the same offset value is used for a given image every time it is compressed. */
+
+		     usbbuff = (unsigned char *) tiledata;
+		     dithersum = 0;
+		     for (ii = 0; ii < 4 * tilelen; ii++) {
+		         dithersum += usbbuff[ii];  /* doesn't matter if there is an integer overflow */
+	             }
+		     (outfptr->Fptr)->dither_offset = ((int) (dithersum % 10000)) + 1;
+		
+                     /* update the header keyword with this new value */
+		     fits_update_key(outfptr, TINT, "ZDITHER0", &((outfptr->Fptr)->dither_offset), 
+	                        NULL, status);
+		  }
+
+                  /* subtract 1 to convert from 1-based to 0-based element number */
+	          irow = row + (outfptr->Fptr)->dither_offset - 1; /* dither the quantized values */
+
+	      } else {
+	          irow = 0;  /* do not dither the quantized values */
+              }
+	      
+              *flag = fits_quantize_float (irow, (float *) tiledata, tilenx, tileny,
+                   nullcheck, floatnull, (outfptr->Fptr)->quantize_level, idata,
+                   bscale, bzero, &iminval, &imaxval);
+
+              if (*flag > 1)
+		   return(*status = *flag);
+          }
+          else if ((outfptr->Fptr)->quantize_level != NO_QUANTIZE)
+	  {
+	    /* if floating point pixels are not being losslessly compressed, then */
+	    /* input float data is implicitly converted (truncated) to integers */
+            if ((scale != 1. || zero != 0.))  /* must scale the values */
+	       imcomp_nullscalefloats((float *) tiledata, tilelen, idata, scale, zero,
+	           nullcheck, *(float *) (nullflagval), nullval, status);
+             else
+	       imcomp_nullfloats((float *) tiledata, tilelen, idata,
+	           nullcheck, *(float *) (nullflagval), nullval,  status);
+          }
+          else if ((outfptr->Fptr)->quantize_level == NO_QUANTIZE)
+	  {
+	      /* just convert null values to NaNs in place, if necessary, then do lossless gzip compression */
+		if (nullcheck == 1) {
+	            imcomp_float2nan((float *) tiledata, tilelen, (int *) tiledata,
+	                *(float *) (nullflagval), status);
+		}
+          }
+
+          return(*status);
+}
+ /*--------------------------------------------------------------------------*/
+int imcomp_convert_tile_tdouble(
+    fitsfile *outfptr,
+    long row,
+    void *tiledata, 
+    long tilelen,
+    long tilenx,
+    long tileny,
+    int nullcheck,
+    void *nullflagval,
+    int nullval,
+    int zbitpix,
+    double scale,
+    double zero,
+    int *intlength,
+    int *flag,
+    double *bscale,
+    double *bzero,
+    int *status)
+{
+    /*  Prepare the input tile array of pixels for compression.
+    /*  Convert input integer*2 tile array in place to 4 or 8-byte ints for compression, */
+    /*  If needed, convert 4 or 8-byte ints and do null value substitution. */
+    /*  Note that the calling routine must have allocated the input array big enough */
+    /* to be able to do this.  */
+
+    int flagval, *idata;
+    long irow, ii;
+    double doublenull;
+    unsigned char *usbbuff;
+    unsigned long dithersum;
+    int iminval = 0, imaxval = 0;  /* min and max quantized integers */
+
+           *intlength = 4;
+           idata = (int *) tiledata;
+
+          /* if the tile-compressed table contains zscale and zzero columns */
+          /* then scale and quantize the input floating point data.    */
+          /* Otherwise, just truncate the floats to integers.          */
+
+          if ((outfptr->Fptr)->cn_zscale > 0)
+          {
+            if (nullcheck == 1)
+	      doublenull = *(double *) (nullflagval);
+	    else
+	      doublenull = DOUBLENULLVALUE;
+	      
+            /* quantize the double values into integers */
+              if ((outfptr->Fptr)->quantize_dither == SUBTRACTIVE_DITHER_1) {
+
+	          /* see if the dithering offset value needs to be initialized (see above) */                  
+	          if ((outfptr->Fptr)->request_dither_offset == 0 && (outfptr->Fptr)->dither_offset == 0) {
+
+		     (outfptr->Fptr)->dither_offset = 
+		       (( (int)time(NULL) + ( (int) clock() / (int) (CLOCKS_PER_SEC / 100)) + (outfptr->Fptr)->curhdu) % 10000) + 1;
+		     
+                     /* update the header keyword with this new value */
+		     fits_update_key(outfptr, TINT, "ZDITHER0", &((outfptr->Fptr)->dither_offset), 
+	                        NULL, status);
+
+	          } else if ((outfptr->Fptr)->request_dither_offset < 0 && (outfptr->Fptr)->dither_offset < 0) {
+
+		     usbbuff = (unsigned char *) tiledata;
+		     dithersum = 0;
+		     for (ii = 0; ii < 8 * tilelen; ii++) {
+		         dithersum += usbbuff[ii];
+	             }
+		     (outfptr->Fptr)->dither_offset = ((int) (dithersum % 10000)) + 1;
+		
+                     /* update the header keyword with this new value */
+		     fits_update_key(outfptr, TINT, "ZDITHER0", &((outfptr->Fptr)->dither_offset), 
+	                        NULL, status);
+		  }
+
+	          irow = row + (outfptr->Fptr)->dither_offset - 1; /* dither the quantized values */
+
+	      } else {
+	          irow = 0;  /* do not dither the quantized values */
+	      }
+
+            *flag = fits_quantize_double (irow, (double *) tiledata, tilenx, tileny,
+               nullcheck, doublenull, (outfptr->Fptr)->quantize_level, idata,
+               bscale, bzero, &iminval, &imaxval);
+
+            if (*flag > 1)
+		return(*status = *flag);
+          }
+          else if ((outfptr->Fptr)->quantize_level != NO_QUANTIZE)
+	  {
+	    /* if floating point pixels are not being losslessly compressed, then */
+	    /* input float data is implicitly converted (truncated) to integers */
+             if ((scale != 1. || zero != 0.))  /* must scale the values */
+	       imcomp_nullscaledoubles((double *) tiledata, tilelen, idata, scale, zero,
+	           nullcheck, *(double *) (nullflagval), nullval, status);
+             else
+	       imcomp_nulldoubles((double *) tiledata, tilelen, idata,
+	           nullcheck, *(double *) (nullflagval), nullval,  status);
+          }
+          else if ((outfptr->Fptr)->quantize_level == NO_QUANTIZE)
+	  {
+	      /* just convert null values to NaNs in place, if necessary, then do lossless gzip compression */
+		if (nullcheck == 1) {
+	            imcomp_double2nan((double *) tiledata, tilelen, (LONGLONG *) tiledata,
+	                *(double *) (nullflagval), status);
+		}
+          }
+ 
+          return(*status);
+}
+/*---------------------------------------------------------------------------*/
+int imcomp_nullscale(
+     int *idata, 
+     long tilelen,
+     int nullflagval,
+     int nullval,
+     double scale,
+     double zero,
+     int *status)
+/*
+   do null value substitution AND scaling of the integer array.
+   If array value = nullflagval, then set the value to nullval.
+   Otherwise, inverse scale the integer value.
+*/
+{
+    long ii;
+    double dvalue;
+    
+    for (ii=0; ii < tilelen; ii++)
+    {
+        if (idata[ii] == nullflagval)
+	    idata[ii] = nullval;
+	else 
+	{
+            dvalue = (idata[ii] - zero) / scale;
+
+            if (dvalue < DINT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                idata[ii] = INT32_MIN;
+            }
+            else if (dvalue > DINT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                idata[ii] = INT32_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0)
+                    idata[ii] = (int) (dvalue + .5);
+                else
+                    idata[ii] = (int) (dvalue - .5);
+            }
+        }
+    }
+    return(*status);
+}
+/*---------------------------------------------------------------------------*/
+int imcomp_nullvalues(
+     int *idata, 
+     long tilelen,
+     int nullflagval,
+     int nullval,
+     int *status)
+/*
+   do null value substitution.
+   If array value = nullflagval, then set the value to nullval.
+*/
+{
+    long ii;
+    
+    for (ii=0; ii < tilelen; ii++)
+    {
+        if (idata[ii] == nullflagval)
+	    idata[ii] = nullval;
+    }
+    return(*status);
+}
+/*---------------------------------------------------------------------------*/
+int imcomp_scalevalues(
+     int *idata, 
+     long tilelen,
+     double scale,
+     double zero,
+     int *status)
+/*
+   do inverse scaling the integer values.
+*/
+{
+    long ii;
+    double dvalue;
+    
+    for (ii=0; ii < tilelen; ii++)
+    {
+            dvalue = (idata[ii] - zero) / scale;
+
+            if (dvalue < DINT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                idata[ii] = INT32_MIN;
+            }
+            else if (dvalue > DINT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                idata[ii] = INT32_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0)
+                    idata[ii] = (int) (dvalue + .5);
+                else
+                    idata[ii] = (int) (dvalue - .5);
+            }
+    }
+    return(*status);
+}
+/*---------------------------------------------------------------------------*/
+int imcomp_nullscalei2(
+     short *idata, 
+     long tilelen,
+     short nullflagval,
+     short nullval,
+     double scale,
+     double zero,
+     int *status)
+/*
+   do null value substitution AND scaling of the integer array.
+   If array value = nullflagval, then set the value to nullval.
+   Otherwise, inverse scale the integer value.
+*/
+{
+    long ii;
+    double dvalue;
+    
+    for (ii=0; ii < tilelen; ii++)
+    {
+        if (idata[ii] == nullflagval)
+	    idata[ii] = nullval;
+	else 
+	{
+            dvalue = (idata[ii] - zero) / scale;
+
+            if (dvalue < DSHRT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                idata[ii] = SHRT_MIN;
+            }
+            else if (dvalue > DSHRT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                idata[ii] = SHRT_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0)
+                    idata[ii] = (int) (dvalue + .5);
+                else
+                    idata[ii] = (int) (dvalue - .5);
+            }
+        }
+    }
+    return(*status);
+}
+/*---------------------------------------------------------------------------*/
+int imcomp_nullvaluesi2(
+     short *idata, 
+     long tilelen,
+     short nullflagval,
+     short nullval,
+     int *status)
+/*
+   do null value substitution.
+   If array value = nullflagval, then set the value to nullval.
+*/
+{
+    long ii;
+    
+    for (ii=0; ii < tilelen; ii++)
+    {
+        if (idata[ii] == nullflagval)
+	    idata[ii] = nullval;
+    }
+    return(*status);
+}
+/*---------------------------------------------------------------------------*/
+int imcomp_scalevaluesi2(
+     short *idata, 
+     long tilelen,
+     double scale,
+     double zero,
+     int *status)
+/*
+   do inverse scaling the integer values.
+*/
+{
+    long ii;
+    double dvalue;
+    
+    for (ii=0; ii < tilelen; ii++)
+    {
+            dvalue = (idata[ii] - zero) / scale;
+
+            if (dvalue < DSHRT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                idata[ii] = SHRT_MIN;
+            }
+            else if (dvalue > DSHRT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                idata[ii] = SHRT_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0)
+                    idata[ii] = (int) (dvalue + .5);
+                else
+                    idata[ii] = (int) (dvalue - .5);
+            }
+    }
+    return(*status);
+}
+/*---------------------------------------------------------------------------*/
+int imcomp_nullfloats(
+     float *fdata,
+     long tilelen,
+     int *idata, 
+     int nullcheck,
+     float nullflagval,
+     int nullval,
+     int *status)
+/*
+   do null value substitution  of the float array.
+   If array value = nullflagval, then set the output value to FLOATNULLVALUE.
+*/
+{
+    long ii;
+    double dvalue;
+    
+    if (nullcheck == 1) /* must check for null values */
+    {
+      for (ii=0; ii < tilelen; ii++)
+      {
+        if (fdata[ii] == nullflagval)
+	    idata[ii] = nullval;
+	else 
+	{
+            dvalue = fdata[ii];
+
+            if (dvalue < DINT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                idata[ii] = INT32_MIN;
+            }
+            else if (dvalue > DINT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                idata[ii] = INT32_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0)
+                    idata[ii] = (int) (dvalue + .5);
+                else
+                    idata[ii] = (int) (dvalue - .5);
+            }
+        }
+      }
+    }
+    else  /* don't have to worry about null values */
+    {
+      for (ii=0; ii < tilelen; ii++)
+      {
+            dvalue = fdata[ii];
+
+            if (dvalue < DINT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                idata[ii] = INT32_MIN;
+            }
+            else if (dvalue > DINT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                idata[ii] = INT32_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0)
+                    idata[ii] = (int) (dvalue + .5);
+                else
+                    idata[ii] = (int) (dvalue - .5);
+            }
+      }
+    }
+    return(*status);
+}
+/*---------------------------------------------------------------------------*/
+int imcomp_nullscalefloats(
+     float *fdata,
+     long tilelen,
+     int *idata, 
+     double scale,
+     double zero,
+     int nullcheck,
+     float nullflagval,
+     int nullval,
+     int *status)
+/*
+   do null value substitution  of the float array.
+   If array value = nullflagval, then set the output value to FLOATNULLVALUE.
+   Otherwise, inverse scale the integer value.
+*/
+{
+    long ii;
+    double dvalue;
+    
+    if (nullcheck == 1) /* must check for null values */
+    {
+      for (ii=0; ii < tilelen; ii++)
+      {
+        if (fdata[ii] == nullflagval)
+	    idata[ii] = nullval;
+	else 
+	{
+            dvalue = (fdata[ii] - zero) / scale;
+
+            if (dvalue < DINT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                idata[ii] = INT32_MIN;
+            }
+            else if (dvalue > DINT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                idata[ii] = INT32_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0.)
+                    idata[ii] = (int) (dvalue + .5);
+                else
+                    idata[ii] = (int) (dvalue - .5);
+            }
+        }
+      }
+    }
+    else  /* don't have to worry about null values */
+    {
+      for (ii=0; ii < tilelen; ii++)
+      {
+            dvalue = (fdata[ii] - zero) / scale;
+
+            if (dvalue < DINT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                idata[ii] = INT32_MIN;
+            }
+            else if (dvalue > DINT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                idata[ii] = INT32_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0.)
+                    idata[ii] = (int) (dvalue + .5);
+                else
+                    idata[ii] = (int) (dvalue - .5);
+            }
+      }
+    }
+    return(*status);
+}
+/*---------------------------------------------------------------------------*/
+int imcomp_nulldoubles(
+     double *fdata,
+     long tilelen,
+     int *idata, 
+     int nullcheck,
+     double nullflagval,
+     int nullval,
+     int *status)
+/*
+   do null value substitution  of the float array.
+   If array value = nullflagval, then set the output value to FLOATNULLVALUE.
+   Otherwise, inverse scale the integer value.
+*/
+{
+    long ii;
+    double dvalue;
+    
+    if (nullcheck == 1) /* must check for null values */
+    {
+      for (ii=0; ii < tilelen; ii++)
+      {
+        if (fdata[ii] == nullflagval)
+	    idata[ii] = nullval;
+	else 
+	{
+            dvalue = fdata[ii];
+
+            if (dvalue < DINT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                idata[ii] = INT32_MIN;
+            }
+            else if (dvalue > DINT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                idata[ii] = INT32_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0.)
+                    idata[ii] = (int) (dvalue + .5);
+                else
+                    idata[ii] = (int) (dvalue - .5);
+            }
+        }
+      }
+    }
+    else  /* don't have to worry about null values */
+    {
+      for (ii=0; ii < tilelen; ii++)
+      {
+            dvalue = fdata[ii];
+
+            if (dvalue < DINT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                idata[ii] = INT32_MIN;
+            }
+            else if (dvalue > DINT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                idata[ii] = INT32_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0.)
+                    idata[ii] = (int) (dvalue + .5);
+                else
+                    idata[ii] = (int) (dvalue - .5);
+            }
+      }
+    }
+    return(*status);
+}
+/*---------------------------------------------------------------------------*/
+int imcomp_nullscaledoubles(
+     double *fdata,
+     long tilelen,
+     int *idata, 
+     double scale,
+     double zero,
+     int nullcheck,
+     double nullflagval,
+     int nullval,
+     int *status)
+/*
+   do null value substitution  of the float array.
+   If array value = nullflagval, then set the output value to FLOATNULLVALUE.
+   Otherwise, inverse scale the integer value.
+*/
+{
+    long ii;
+    double dvalue;
+    
+    if (nullcheck == 1) /* must check for null values */
+    {
+      for (ii=0; ii < tilelen; ii++)
+      {
+        if (fdata[ii] == nullflagval)
+	    idata[ii] = nullval;
+	else 
+	{
+            dvalue = (fdata[ii] - zero) / scale;
+
+            if (dvalue < DINT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                idata[ii] = INT32_MIN;
+            }
+            else if (dvalue > DINT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                idata[ii] = INT32_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0.)
+                    idata[ii] = (int) (dvalue + .5);
+                else
+                    idata[ii] = (int) (dvalue - .5);
+            }
+        }
+      }
+    }
+    else  /* don't have to worry about null values */
+    {
+      for (ii=0; ii < tilelen; ii++)
+      {
+            dvalue = (fdata[ii] - zero) / scale;
+
+            if (dvalue < DINT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                idata[ii] = INT32_MIN;
+            }
+            else if (dvalue > DINT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                idata[ii] = INT32_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0.)
+                    idata[ii] = (int) (dvalue + .5);
+                else
+                    idata[ii] = (int) (dvalue - .5);
+            }
+      }
+    }
+    return(*status);
+}
+/*---------------------------------------------------------------------------*/
+int fits_write_compressed_img(fitsfile *fptr,   /* I - FITS file pointer     */
+            int  datatype,   /* I - datatype of the array to be written      */
+            long  *infpixel, /* I - 'bottom left corner' of the subsection   */
+            long  *inlpixel, /* I - 'top right corner' of the subsection     */
+            int  nullcheck,  /* I - 0 for no null checking                   */
+                             /*     1: pixels that are = nullval will be     */
+                             /*     written with the FITS null pixel value   */
+                             /*     (floating point arrays only)             */
+            void *array,     /* I - array of values to be written            */
+            void *nullval,   /* I - undefined pixel value                    */
+            int  *status)    /* IO - error status                            */
+/*
+   Write a section of a compressed image.
+*/
+{
+    int naxis[MAX_COMPRESS_DIM], tiledim[MAX_COMPRESS_DIM];
+    long tilesize[MAX_COMPRESS_DIM], thistilesize[MAX_COMPRESS_DIM];
+    long ftile[MAX_COMPRESS_DIM], ltile[MAX_COMPRESS_DIM];
+    long tfpixel[MAX_COMPRESS_DIM], tlpixel[MAX_COMPRESS_DIM];
+    long rowdim[MAX_COMPRESS_DIM], offset[MAX_COMPRESS_DIM],ntemp;
+    long fpixel[MAX_COMPRESS_DIM], lpixel[MAX_COMPRESS_DIM];
+    int ii, i5, i4, i3, i2, i1, i0, ndim, irow, pixlen, tilenul;
+    int  tstatus, buffpixsiz;
+    void *buffer;
+    char *bnullarray = 0, card[FLEN_CARD];
+
+    if (*status > 0) 
+        return(*status);
+
+    if (!fits_is_compressed_image(fptr, status) )
+    {
+        ffpmsg("CHDU is not a compressed image (fits_write_compressed_img)");
+        return(*status = DATA_COMPRESSION_ERR);
+    }
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    /* rescan header if data structure is undefined */
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+        if ( ffrdef(fptr, status) > 0)               
+            return(*status);
+
+
+    /* ===================================================================== */
+
+
+    if (datatype == TSHORT || datatype == TUSHORT)
+    {
+       pixlen = sizeof(short);
+    }
+    else if (datatype == TINT || datatype == TUINT)
+    {
+       pixlen = sizeof(int);
+    }
+    else if (datatype == TBYTE || datatype == TSBYTE)
+    {
+       pixlen = 1;
+    }
+    else if (datatype == TLONG || datatype == TULONG)
+    {
+       pixlen = sizeof(long);
+    }
+    else if (datatype == TFLOAT)
+    {
+       pixlen = sizeof(float);
+    }
+    else if (datatype == TDOUBLE)
+    {
+       pixlen = sizeof(double);
+    }
+    else
+    {
+        ffpmsg("unsupported datatype for compressing image");
+        return(*status = BAD_DATATYPE);
+    }
+
+    /* ===================================================================== */
+
+    /* allocate scratch space for processing one tile of the image */
+    buffpixsiz = pixlen;  /* this is the minimum pixel size */
+    
+    if ( (fptr->Fptr)->compress_type == HCOMPRESS_1) { /* need 4 or 8 bytes per pixel */
+        if ((fptr->Fptr)->zbitpix == BYTE_IMG ||
+	    (fptr->Fptr)->zbitpix == SHORT_IMG )
+                buffpixsiz = maxvalue(buffpixsiz, 4);
+        else
+	        buffpixsiz = 8;
+    }
+    else if ( (fptr->Fptr)->compress_type == PLIO_1) { /* need 4 bytes per pixel */
+                buffpixsiz = maxvalue(buffpixsiz, 4);
+    }
+    else if ( (fptr->Fptr)->compress_type == RICE_1  ||
+              (fptr->Fptr)->compress_type == GZIP_1 ||
+              (fptr->Fptr)->compress_type == GZIP_2 ||
+              (fptr->Fptr)->compress_type == BZIP2_1) {  /* need 1, 2, or 4 bytes per pixel */
+        if ((fptr->Fptr)->zbitpix == BYTE_IMG)
+            buffpixsiz = maxvalue(buffpixsiz, 1);
+        else if ((fptr->Fptr)->zbitpix == SHORT_IMG)
+            buffpixsiz = maxvalue(buffpixsiz, 2);
+        else 
+            buffpixsiz = maxvalue(buffpixsiz, 4);
+    }
+    else
+    {
+        ffpmsg("unsupported image compression algorithm");
+        return(*status = BAD_DATATYPE);
+    }
+    
+    /* cast to double to force alignment on 8-byte addresses */
+    buffer = (double *) calloc ((fptr->Fptr)->maxtilelen, buffpixsiz);
+
+    if (buffer == NULL)
+    {
+	    ffpmsg("Out of memory (fits_write_compress_img)");
+	    return (*status = MEMORY_ALLOCATION);
+    }
+
+    /* ===================================================================== */
+
+    /* initialize all the arrays */
+    for (ii = 0; ii < MAX_COMPRESS_DIM; ii++)
+    {
+        naxis[ii] = 1;
+        tiledim[ii] = 1;
+        tilesize[ii] = 1;
+        ftile[ii] = 1;
+        ltile[ii] = 1;
+        rowdim[ii] = 1;
+    }
+
+    ndim = (fptr->Fptr)->zndim;
+    ntemp = 1;
+    for (ii = 0; ii < ndim; ii++)
+    {
+        fpixel[ii] = infpixel[ii];
+        lpixel[ii] = inlpixel[ii];
+
+        /* calc number of tiles in each dimension, and tile containing */
+        /* the first and last pixel we want to read in each dimension  */
+        naxis[ii] = (fptr->Fptr)->znaxis[ii];
+        if (fpixel[ii] < 1)
+        {
+            free(buffer);
+            return(*status = BAD_PIX_NUM);
+        }
+
+        tilesize[ii] = (fptr->Fptr)->tilesize[ii];
+        tiledim[ii] = (naxis[ii] - 1) / tilesize[ii] + 1;
+        ftile[ii]   = (fpixel[ii] - 1)   / tilesize[ii] + 1;
+        ltile[ii]   = minvalue((lpixel[ii] - 1) / tilesize[ii] + 1, 
+                                tiledim[ii]);
+        rowdim[ii]  = ntemp;  /* total tiles in each dimension */
+        ntemp *= tiledim[ii];
+    }
+
+    /* support up to 6 dimensions for now */
+    /* tfpixel and tlpixel are the first and last image pixels */
+    /* along each dimension of the compression tile */
+    for (i5 = ftile[5]; i5 <= ltile[5]; i5++)
+    {
+     tfpixel[5] = (i5 - 1) * tilesize[5] + 1;
+     tlpixel[5] = minvalue(tfpixel[5] + tilesize[5] - 1, 
+                            naxis[5]);
+     thistilesize[5] = tlpixel[5] - tfpixel[5] + 1;
+     offset[5] = (i5 - 1) * rowdim[5];
+     for (i4 = ftile[4]; i4 <= ltile[4]; i4++)
+     {
+      tfpixel[4] = (i4 - 1) * tilesize[4] + 1;
+      tlpixel[4] = minvalue(tfpixel[4] + tilesize[4] - 1, 
+                            naxis[4]);
+      thistilesize[4] = thistilesize[5] * (tlpixel[4] - tfpixel[4] + 1);
+      offset[4] = (i4 - 1) * rowdim[4] + offset[5];
+      for (i3 = ftile[3]; i3 <= ltile[3]; i3++)
+      {
+        tfpixel[3] = (i3 - 1) * tilesize[3] + 1;
+        tlpixel[3] = minvalue(tfpixel[3] + tilesize[3] - 1, 
+                              naxis[3]);
+        thistilesize[3] = thistilesize[4] * (tlpixel[3] - tfpixel[3] + 1);
+        offset[3] = (i3 - 1) * rowdim[3] + offset[4];
+        for (i2 = ftile[2]; i2 <= ltile[2]; i2++)
+        {
+          tfpixel[2] = (i2 - 1) * tilesize[2] + 1;
+          tlpixel[2] = minvalue(tfpixel[2] + tilesize[2] - 1, 
+                                naxis[2]);
+          thistilesize[2] = thistilesize[3] * (tlpixel[2] - tfpixel[2] + 1);
+          offset[2] = (i2 - 1) * rowdim[2] + offset[3];
+          for (i1 = ftile[1]; i1 <= ltile[1]; i1++)
+          {
+            tfpixel[1] = (i1 - 1) * tilesize[1] + 1;
+            tlpixel[1] = minvalue(tfpixel[1] + tilesize[1] - 1, 
+                                  naxis[1]);
+            thistilesize[1] = thistilesize[2] * (tlpixel[1] - tfpixel[1] + 1);
+            offset[1] = (i1 - 1) * rowdim[1] + offset[2];
+            for (i0 = ftile[0]; i0 <= ltile[0]; i0++)
+            {
+              tfpixel[0] = (i0 - 1) * tilesize[0] + 1;
+              tlpixel[0] = minvalue(tfpixel[0] + tilesize[0] - 1, 
+                                    naxis[0]);
+              thistilesize[0] = thistilesize[1] * (tlpixel[0] - tfpixel[0] + 1);
+              /* calculate row of table containing this tile */
+              irow = i0 + offset[1];
+
+              /* read and uncompress this row (tile) of the table */
+              /* also do type conversion and undefined pixel substitution */
+              /* at this point */
+              imcomp_decompress_tile(fptr, irow, thistilesize[0],
+                    datatype, nullcheck, nullval, buffer, bnullarray, &tilenul,
+                     status);
+
+              if (*status == NO_COMPRESSED_TILE)
+              {
+                   /* tile doesn't exist, so initialize to zero */
+                   memset(buffer, 0, pixlen * thistilesize[0]);
+                   *status = 0;
+              }
+
+              /* copy the intersecting pixels to this tile from the input */
+              imcomp_merge_overlap(buffer, pixlen, ndim, tfpixel, tlpixel, 
+                     bnullarray, array, fpixel, lpixel, nullcheck, status);
+
+              /* compress the tile again, and write it back to the FITS file */
+              imcomp_compress_tile (fptr, irow, datatype, buffer, 
+                                    thistilesize[0],
+				    tlpixel[0] - tfpixel[0] + 1,
+				    tlpixel[1] - tfpixel[1] + 1,
+				    nullcheck, nullval, 
+				    status);
+            }
+          }
+        }
+      }
+     }
+    }
+    free(buffer);
+    
+
+    if ((fptr->Fptr)->zbitpix < 0 && nullcheck != 0) { 
+/*
+     This is a floating point FITS image with possible null values.
+     It is too messy to test if any null values are actually written, so 
+     just assume so.  We need to make sure that the
+     ZBLANK keyword is present in the compressed image header.  If it is not
+     there then we need to insert the keyword. 
+*/   
+        tstatus = 0;
+        ffgcrd(fptr, "ZBLANK", card, &tstatus);
+
+	if (tstatus) {   /* have to insert the ZBLANK keyword */
+           ffgcrd(fptr, "ZCMPTYPE", card, status);
+           ffikyj(fptr, "ZBLANK", COMPRESS_NULL_VALUE, 
+                "null value in the compressed integer array", status);
+	
+           /* set this value into the internal structure; it is used if */
+	   /* the program reads back the values from the array */
+	 
+          (fptr->Fptr)->zblank = COMPRESS_NULL_VALUE;
+          (fptr->Fptr)->cn_zblank = -1;  /* flag for a constant ZBLANK */
+        }  
+    }  
+    
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_write_compressed_pixels(fitsfile *fptr, /* I - FITS file pointer   */
+            int  datatype,  /* I - datatype of the array to be written      */
+            LONGLONG   fpixel,  /* I - 'first pixel to write          */
+            LONGLONG   npixel,  /* I - number of pixels to write      */
+            int  nullcheck,  /* I - 0 for no null checking                   */
+                             /*     1: pixels that are = nullval will be     */
+                             /*     written with the FITS null pixel value   */
+                             /*     (floating point arrays only)             */
+            void *array,      /* I - array of values to write                */
+            void *nullval,    /* I - value used to represent undefined pixels*/
+            int  *status)     /* IO - error status                           */
+/*
+   Write a consecutive set of pixels to a compressed image.  This routine
+   interpretes the n-dimensional image as a long one-dimensional array. 
+   This is actually a rather inconvenient way to write compressed images in
+   general, and could be rather inefficient if the requested pixels to be
+   written are located in many different image compression tiles.    
+
+   The general strategy used here is to write the requested pixels in blocks
+   that correspond to rectangular image sections.  
+*/
+{
+    int naxis, ii, bytesperpixel;
+    long naxes[MAX_COMPRESS_DIM], nread;
+    LONGLONG tfirst, tlast, last0, last1, dimsize[MAX_COMPRESS_DIM];
+    long nplane, firstcoord[MAX_COMPRESS_DIM], lastcoord[MAX_COMPRESS_DIM];
+    char *arrayptr;
+
+    if (*status > 0)
+        return(*status);
+
+    arrayptr = (char *) array;
+
+    /* get size of array pixels, in bytes */
+    bytesperpixel = ffpxsz(datatype);
+
+    for (ii = 0; ii < MAX_COMPRESS_DIM; ii++)
+    {
+        naxes[ii] = 1;
+        firstcoord[ii] = 0;
+        lastcoord[ii] = 0;
+    }
+
+    /*  determine the dimensions of the image to be written */
+    ffgidm(fptr, &naxis, status);
+    ffgisz(fptr, MAX_COMPRESS_DIM, naxes, status);
+
+    /* calc the cumulative number of pixels in each successive dimension */
+    dimsize[0] = 1;
+    for (ii = 1; ii < MAX_COMPRESS_DIM; ii++)
+         dimsize[ii] = dimsize[ii - 1] * naxes[ii - 1];
+
+    /*  determine the coordinate of the first and last pixel in the image */
+    /*  Use zero based indexes here */
+    tfirst = fpixel - 1;
+    tlast = tfirst + npixel - 1;
+    for (ii = naxis - 1; ii >= 0; ii--)
+    {
+        firstcoord[ii] = (long) (tfirst / dimsize[ii]);
+        lastcoord[ii]  = (long) (tlast / dimsize[ii]);
+        tfirst = tfirst - firstcoord[ii] * dimsize[ii];
+        tlast = tlast - lastcoord[ii] * dimsize[ii];
+    }
+
+    /* to simplify things, treat 1-D, 2-D, and 3-D images as separate cases */
+
+    if (naxis == 1)
+    {
+        /* Simple: just write the requested range of pixels */
+
+        firstcoord[0] = firstcoord[0] + 1;
+        lastcoord[0] = lastcoord[0] + 1;
+        fits_write_compressed_img(fptr, datatype, firstcoord, lastcoord,
+            nullcheck, array, nullval, status);
+        return(*status);
+    }
+    else if (naxis == 2)
+    {
+        nplane = 0;  /* write 1st (and only) plane of the image */
+        fits_write_compressed_img_plane(fptr, datatype, bytesperpixel,
+          nplane, firstcoord, lastcoord, naxes, nullcheck,
+          array, nullval, &nread, status);
+    }
+    else if (naxis == 3)
+    {
+        /* test for special case: writing an integral number of planes */
+        if (firstcoord[0] == 0 && firstcoord[1] == 0 &&
+            lastcoord[0] == naxes[0] - 1 && lastcoord[1] == naxes[1] - 1)
+        {
+            for (ii = 0; ii < MAX_COMPRESS_DIM; ii++)
+            {
+                /* convert from zero base to 1 base */
+                (firstcoord[ii])++;
+                (lastcoord[ii])++;
+            }
+
+            /* we can write the contiguous block of pixels in one go */
+            fits_write_compressed_img(fptr, datatype, firstcoord, lastcoord,
+                nullcheck, array, nullval, status);
+            return(*status);
+        }
+
+        /* save last coordinate in temporary variables */
+        last0 = lastcoord[0];
+        last1 = lastcoord[1];
+
+        if (firstcoord[2] < lastcoord[2])
+        {
+            /* we will write up to the last pixel in all but the last plane */
+            lastcoord[0] = naxes[0] - 1;
+            lastcoord[1] = naxes[1] - 1;
+        }
+
+        /* write one plane of the cube at a time, for simplicity */
+        for (nplane = firstcoord[2]; nplane <= lastcoord[2]; nplane++)
+        {
+            if (nplane == lastcoord[2])
+            {
+                lastcoord[0] = (long) last0;
+                lastcoord[1] = (long) last1;
+            }
+
+            fits_write_compressed_img_plane(fptr, datatype, bytesperpixel,
+              nplane, firstcoord, lastcoord, naxes, nullcheck,
+              arrayptr, nullval, &nread, status);
+
+            /* for all subsequent planes, we start with the first pixel */
+            firstcoord[0] = 0;
+            firstcoord[1] = 0;
+
+            /* increment pointers to next elements to be written */
+            arrayptr = arrayptr + nread * bytesperpixel;
+        }
+    }
+    else
+    {
+        ffpmsg("only 1D, 2D, or 3D images are currently supported");
+        return(*status = DATA_COMPRESSION_ERR);
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_write_compressed_img_plane(fitsfile *fptr, /* I - FITS file    */
+            int  datatype,  /* I - datatype of the array to be written    */
+            int  bytesperpixel, /* I - number of bytes per pixel in array */
+            long   nplane,  /* I - which plane of the cube to write      */
+            long *firstcoord, /* I coordinate of first pixel to write */
+            long *lastcoord,  /* I coordinate of last pixel to write */
+            long *naxes,     /* I size of each image dimension */
+            int  nullcheck,  /* I - 0 for no null checking                   */
+                             /*     1: pixels that are = nullval will be     */
+                             /*     written with the FITS null pixel value   */
+                             /*     (floating point arrays only)             */
+            void *array,      /* I - array of values that are written        */
+            void *nullval,    /* I - value for undefined pixels              */
+            long *nread,      /* O - total number of pixels written          */
+            int  *status)     /* IO - error status                           */
+
+   /*
+           in general we have to write the first partial row of the image,
+           followed by the middle complete rows, followed by the last
+           partial row of the image.  If the first or last rows are complete,
+           then write them at the same time as all the middle rows.
+    */
+{
+    /* bottom left coord. and top right coord. */
+    long blc[MAX_COMPRESS_DIM], trc[MAX_COMPRESS_DIM]; 
+    char *arrayptr;
+
+    *nread = 0;
+
+    arrayptr = (char *) array;
+
+    blc[2] = nplane + 1;
+    trc[2] = nplane + 1;
+
+    if (firstcoord[0] != 0)
+    { 
+            /* have to read a partial first row */
+            blc[0] = firstcoord[0] + 1;
+            blc[1] = firstcoord[1] + 1;
+            trc[1] = blc[1];  
+            if (lastcoord[1] == firstcoord[1])
+               trc[0] = lastcoord[0] + 1; /* 1st and last pixels in same row */
+            else
+               trc[0] = naxes[0];  /* read entire rest of the row */
+
+            fits_write_compressed_img(fptr, datatype, blc, trc,
+                nullcheck, arrayptr, nullval, status);
+
+            *nread = *nread + trc[0] - blc[0] + 1;
+
+            if (lastcoord[1] == firstcoord[1])
+            {
+               return(*status);  /* finished */
+            }
+
+            /* set starting coord to beginning of next line */
+            firstcoord[0] = 0;
+            firstcoord[1] += 1;
+            arrayptr = arrayptr + (trc[0] - blc[0] + 1) * bytesperpixel;
+    }
+
+    /* write contiguous complete rows of the image, if any */
+    blc[0] = 1;
+    blc[1] = firstcoord[1] + 1;
+    trc[0] = naxes[0];
+
+    if (lastcoord[0] + 1 == naxes[0])
+    {
+            /* can write the last complete row, too */
+            trc[1] = lastcoord[1] + 1;
+    }
+    else
+    {
+            /* last row is incomplete; have to read it separately */
+            trc[1] = lastcoord[1];
+    }
+
+    if (trc[1] >= blc[1])  /* must have at least one whole line to read */
+    {
+        fits_write_compressed_img(fptr, datatype, blc, trc,
+                nullcheck, arrayptr, nullval, status);
+
+        *nread = *nread + (trc[1] - blc[1] + 1) * naxes[0];
+
+        if (lastcoord[1] + 1 == trc[1])
+               return(*status);  /* finished */
+
+        /* increment pointers for the last partial row */
+        arrayptr = arrayptr + (trc[1] - blc[1] + 1) * naxes[0] * bytesperpixel;
+
+     }
+
+    if (trc[1] == lastcoord[1] + 1)
+        return(*status);           /* all done */
+
+    /* set starting and ending coord to last line */
+
+    trc[0] = lastcoord[0] + 1;
+    trc[1] = lastcoord[1] + 1;
+    blc[1] = trc[1];
+
+    fits_write_compressed_img(fptr, datatype, blc, trc,
+                nullcheck, arrayptr, nullval, status);
+
+    *nread = *nread + trc[0] - blc[0] + 1;
+
+    return(*status);
+}
+
+/* ######################################################################## */
+/* ###                 Image Decompression Routines                     ### */
+/* ######################################################################## */
+
+/*--------------------------------------------------------------------------*/
+int fits_img_decompress (fitsfile *infptr, /* image (bintable) to uncompress */
+              fitsfile *outfptr,   /* empty HDU for output uncompressed image */
+              int *status)         /* IO - error status               */
+
+/* 
+  This routine decompresses the whole image and writes it to the output file.
+*/
+
+{
+    int ii, datatype = 0;
+    int nullcheck, anynul;
+    LONGLONG fpixel[MAX_COMPRESS_DIM], lpixel[MAX_COMPRESS_DIM];
+    long inc[MAX_COMPRESS_DIM];
+    long imgsize;
+    float *nulladdr, fnulval;
+    double dnulval;
+
+    if (fits_img_decompress_header(infptr, outfptr, status) > 0)
+    {
+    	return (*status);
+    }
+
+    /* force a rescan of the output header keywords, then reset the scaling */
+    /* in case the BSCALE and BZERO keywords are present, so that the       */
+    /* decompressed values won't be scaled when written to the output image */
+    ffrdef(outfptr, status);
+    ffpscl(outfptr, 1.0, 0.0, status);
+    ffpscl(infptr, 1.0, 0.0, status);
+
+    /* initialize; no null checking is needed for integer images */
+    nullcheck = 0;
+    nulladdr =  &fnulval;
+
+    /* determine datatype for image */
+    if ((infptr->Fptr)->zbitpix == BYTE_IMG)
+    {
+        datatype = TBYTE;
+    }
+    else if ((infptr->Fptr)->zbitpix == SHORT_IMG)
+    {
+        datatype = TSHORT;
+    }
+    else if ((infptr->Fptr)->zbitpix == LONG_IMG)
+    {
+        datatype = TINT;
+    }
+    else if ((infptr->Fptr)->zbitpix == FLOAT_IMG)
+    {
+        /* In the case of float images we must check for NaNs  */
+        nullcheck = 1;
+        fnulval = FLOATNULLVALUE;
+        nulladdr =  &fnulval;
+        datatype = TFLOAT;
+    }
+    else if ((infptr->Fptr)->zbitpix == DOUBLE_IMG)
+    {
+        /* In the case of double images we must check for NaNs  */
+        nullcheck = 1;
+        dnulval = DOUBLENULLVALUE;
+        nulladdr = (float *) &dnulval;
+        datatype = TDOUBLE;
+    }
+
+    /* calculate size of the image (in pixels) */
+    imgsize = 1;
+    for (ii = 0; ii < (infptr->Fptr)->zndim; ii++)
+    {
+        imgsize *= (infptr->Fptr)->znaxis[ii];
+        fpixel[ii] = 1;              /* Set first and last pixel to */
+        lpixel[ii] = (infptr->Fptr)->znaxis[ii]; /* include the entire image. */
+        inc[ii] = 1;
+    }
+
+    /* uncompress the input image and write to output image, one tile at a time */
+
+    fits_read_write_compressed_img(infptr, datatype, fpixel, lpixel, inc,  
+            nullcheck, nulladdr, &anynul, outfptr, status);
+
+    return (*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_decompress_img (fitsfile *infptr, /* image (bintable) to uncompress */
+              fitsfile *outfptr,   /* empty HDU for output uncompressed image */
+              int *status)         /* IO - error status               */
+
+/* 
+  THIS IS AN OBSOLETE ROUTINE.  USE fits_img_decompress instead!!!
+  
+  This routine decompresses the whole image and writes it to the output file.
+*/
+
+{
+    double *data;
+    int ii, datatype = 0, byte_per_pix = 0;
+    int nullcheck, anynul;
+    LONGLONG fpixel[MAX_COMPRESS_DIM], lpixel[MAX_COMPRESS_DIM];
+    long inc[MAX_COMPRESS_DIM];
+    long imgsize, memsize;
+    float *nulladdr, fnulval;
+    double dnulval;
+
+    if (*status > 0)
+        return(*status);
+
+    if (!fits_is_compressed_image(infptr, status) )
+    {
+        ffpmsg("CHDU is not a compressed image (fits_decompress_img)");
+        return(*status = DATA_DECOMPRESSION_ERR);
+    }
+
+    /* create an empty output image with the correct dimensions */
+    if (ffcrim(outfptr, (infptr->Fptr)->zbitpix, (infptr->Fptr)->zndim, 
+       (infptr->Fptr)->znaxis, status) > 0)
+    {
+        ffpmsg("error creating output decompressed image HDU");
+    	return (*status);
+    }
+    /* Copy the table header to the image header. */
+    if (imcomp_copy_imheader(infptr, outfptr, status) > 0)
+    {
+        ffpmsg("error copying header of compressed image");
+    	return (*status);
+    }
+
+    /* force a rescan of the output header keywords, then reset the scaling */
+    /* in case the BSCALE and BZERO keywords are present, so that the       */
+    /* decompressed values won't be scaled when written to the output image */
+    ffrdef(outfptr, status);
+    ffpscl(outfptr, 1.0, 0.0, status);
+    ffpscl(infptr, 1.0, 0.0, status);
+
+    /* initialize; no null checking is needed for integer images */
+    nullcheck = 0;
+    nulladdr =  &fnulval;
+
+    /* determine datatype for image */
+    if ((infptr->Fptr)->zbitpix == BYTE_IMG)
+    {
+        datatype = TBYTE;
+        byte_per_pix = 1;
+    }
+    else if ((infptr->Fptr)->zbitpix == SHORT_IMG)
+    {
+        datatype = TSHORT;
+        byte_per_pix = sizeof(short);
+    }
+    else if ((infptr->Fptr)->zbitpix == LONG_IMG)
+    {
+        datatype = TINT;
+        byte_per_pix = sizeof(int);
+    }
+    else if ((infptr->Fptr)->zbitpix == FLOAT_IMG)
+    {
+        /* In the case of float images we must check for NaNs  */
+        nullcheck = 1;
+        fnulval = FLOATNULLVALUE;
+        nulladdr =  &fnulval;
+        datatype = TFLOAT;
+        byte_per_pix = sizeof(float);
+    }
+    else if ((infptr->Fptr)->zbitpix == DOUBLE_IMG)
+    {
+        /* In the case of double images we must check for NaNs  */
+        nullcheck = 1;
+        dnulval = DOUBLENULLVALUE;
+        nulladdr = (float *) &dnulval;
+        datatype = TDOUBLE;
+        byte_per_pix = sizeof(double);
+    }
+
+    /* calculate size of the image (in pixels) */
+    imgsize = 1;
+    for (ii = 0; ii < (infptr->Fptr)->zndim; ii++)
+    {
+        imgsize *= (infptr->Fptr)->znaxis[ii];
+        fpixel[ii] = 1;              /* Set first and last pixel to */
+        lpixel[ii] = (infptr->Fptr)->znaxis[ii]; /* include the entire image. */
+        inc[ii] = 1;
+    }
+    /* Calc equivalent number of double pixels same size as whole the image. */
+    /* We use double datatype to force the memory to be aligned properly */
+    memsize = ((imgsize * byte_per_pix) - 1) / sizeof(double) + 1;
+
+    /* allocate memory for the image */
+    data = (double*) calloc (memsize, sizeof(double));
+    if (!data)
+    { 
+        ffpmsg("Couldn't allocate memory for the uncompressed image");
+        return(*status = MEMORY_ALLOCATION);
+    }
+
+    /* uncompress the entire image into memory */
+    /* This routine should be enhanced sometime to only need enough */
+    /* memory to uncompress one tile at a time.  */
+    fits_read_compressed_img(infptr, datatype, fpixel, lpixel, inc,  
+            nullcheck, nulladdr, data, NULL, &anynul, status);
+
+    /* write the image to the output file */
+    if (anynul)
+        fits_write_imgnull(outfptr, datatype, 1, imgsize, data, nulladdr, 
+                          status);
+    else
+        fits_write_img(outfptr, datatype, 1, imgsize, data, status);
+
+    free(data);
+    return (*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_img_decompress_header(fitsfile *infptr, /* image (bintable) to uncompress */
+              fitsfile *outfptr,   /* empty HDU for output uncompressed image */
+              int *status)         /* IO - error status               */
+
+/* 
+  This routine reads the header of the input tile compressed image and 
+  converts it to that of a standard uncompress FITS image.
+*/
+
+{
+    int writeprime = 0;
+    int hdupos, inhdupos, numkeys;
+    int nullprime = 0, copyprime = 0, norec = 0, tstatus;
+    char card[FLEN_CARD];
+    int ii, datatype = 0, naxis, bitpix;
+    long naxes[MAX_COMPRESS_DIM];
+
+    if (*status > 0)
+        return(*status);
+    else if (*status == -1) {
+        *status = 0;
+	writeprime = 1;
+    }
+
+    if (!fits_is_compressed_image(infptr, status) )
+    {
+        ffpmsg("CHDU is not a compressed image (fits_img_decompress)");
+        return(*status = DATA_DECOMPRESSION_ERR);
+    }
+
+    /* get information about the state of the output file; does it already */
+    /* contain any keywords and HDUs?  */
+    fits_get_hdu_num(infptr, &inhdupos);  /* Get the current output HDU position */
+    fits_get_hdu_num(outfptr, &hdupos);  /* Get the current output HDU position */
+    fits_get_hdrspace(outfptr, &numkeys, 0, status);
+
+    /* Was the input compressed HDU originally the primary array image? */
+    tstatus = 0;
+    if (!fits_read_card(infptr, "ZSIMPLE", card, &tstatus)) { 
+      /* yes, input HDU was a primary array (not an IMAGE extension) */
+      /* Now determine if we can uncompress it into the primary array of */
+      /* the output file.  This is only possible if the output file */
+      /* currently only contains a null primary array, with no addition */
+      /* header keywords and with no following extension in the FITS file. */
+      
+      if (hdupos == 1) {  /* are we positioned at the primary array? */
+            if (numkeys == 0) { /* primary HDU is completely empty */
+	        nullprime = 1;
+            } else {
+                fits_get_img_param(outfptr, MAX_COMPRESS_DIM, &bitpix, &naxis, naxes, status);
+	
+	        if (naxis == 0) { /* is this a null image? */
+                   nullprime = 1;
+
+		   if (inhdupos == 2)  /* must be at the first extension */
+		      copyprime = 1;
+		}
+           }
+      }
+    } 
+
+    if (nullprime) {  
+       /* We will delete the existing keywords in the null primary array
+          and uncompress the input image into the primary array of the output.
+	  Some of these keywords may be added back to the uncompressed image
+	  header later.
+       */
+
+       for (ii = numkeys; ii > 0; ii--)
+          fits_delete_record(outfptr, ii, status);
+
+    } else  {
+
+       /* if the ZTENSION keyword doesn't exist, then we have to 
+          write the required keywords manually */
+       tstatus = 0;
+       if (fits_read_card(infptr, "ZTENSION", card, &tstatus)) {
+
+          /* create an empty output image with the correct dimensions */
+          if (ffcrim(outfptr, (infptr->Fptr)->zbitpix, (infptr->Fptr)->zndim, 
+             (infptr->Fptr)->znaxis, status) > 0)
+          {
+             ffpmsg("error creating output decompressed image HDU");
+    	     return (*status);
+          }
+
+	  norec = 1;  /* the required keywords have already been written */
+
+       } else {  /* the input compressed image does have ZTENSION keyword */
+       
+          if (writeprime) {  /* convert the image extension to a primary array */
+	      /* have to write the required keywords manually */
+
+              /* create an empty output image with the correct dimensions */
+              if (ffcrim(outfptr, (infptr->Fptr)->zbitpix, (infptr->Fptr)->zndim, 
+                 (infptr->Fptr)->znaxis, status) > 0)
+              {
+                 ffpmsg("error creating output decompressed image HDU");
+    	         return (*status);
+              }
+
+	      norec = 1;  /* the required keywords have already been written */
+
+          } else {  /* write the input compressed image to an image extension */
+
+              if (numkeys == 0) {  /* the output file is currently completely empty */
+	  
+	         /* In this case, the input is a compressed IMAGE extension. */
+	         /* Since the uncompressed output file is currently completely empty, */
+	         /* we need to write a null primary array before uncompressing the */
+                 /* image extension */
+	     
+                 ffcrim(outfptr, 8, 0, naxes, status); /* naxes is not used */
+	     
+	         /* now create the empty extension to uncompress into */
+                 if (fits_create_hdu(outfptr, status) > 0)
+                 {
+                      ffpmsg("error creating output decompressed image HDU");
+    	              return (*status);
+                 }
+	  
+	      } else {
+                  /* just create a new empty extension, then copy all the required */
+	          /* keywords into it.  */
+                 fits_create_hdu(outfptr, status);
+	      }
+           }
+       }
+
+    }
+
+    if (*status > 0)  {
+        ffpmsg("error creating output decompressed image HDU");
+    	return (*status);
+    }
+
+    /* Copy the table header to the image header. */
+
+    if (imcomp_copy_comp2img(infptr, outfptr, norec, status) > 0)
+    {
+        ffpmsg("error copying header keywords from compressed image");
+    }
+
+    if (copyprime) {  
+	/* append any unexpected keywords from the primary array.
+	   This includes any keywords except SIMPLE, BITPIX, NAXIS,
+	   EXTEND, COMMENT, HISTORY, CHECKSUM, and DATASUM.
+	*/
+
+        fits_movabs_hdu(infptr, 1, NULL, status);  /* move to primary array */
+	
+        /* do this so that any new keywords get written before any blank
+	   keywords that may have been appended by imcomp_copy_comp2img  */
+        fits_set_hdustruc(outfptr, status);
+
+        if (imcomp_copy_prime2img(infptr, outfptr, status) > 0)
+        {
+            ffpmsg("error copying primary keywords from compressed file");
+        }
+
+        fits_movabs_hdu(infptr, 2, NULL, status); /* move back to where we were */
+    }
+
+    return (*status);
+}
+/*---------------------------------------------------------------------------*/
+int fits_read_compressed_img(fitsfile *fptr,   /* I - FITS file pointer      */
+            int  datatype,  /* I - datatype of the array to be returned      */
+            LONGLONG  *infpixel, /* I - 'bottom left corner' of the subsection    */
+            LONGLONG  *inlpixel, /* I - 'top right corner' of the subsection      */
+            long  *ininc,    /* I - increment to be applied in each dimension */
+            int  nullcheck,  /* I - 0 for no null checking                   */
+                              /*     1: set undefined pixels = nullval       */
+                              /*     2: set nullarray=1 for undefined pixels */
+            void *nullval,    /* I - value for undefined pixels              */
+            void *array,      /* O - array of values that are returned       */
+            char *nullarray,  /* O - array of flags = 1 if nullcheck = 2     */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+   Read a section of a compressed image;  Note: lpixel may be larger than the 
+   size of the uncompressed image.  Only the pixels within the image will be
+   returned.
+*/
+{
+    int naxis[MAX_COMPRESS_DIM], tiledim[MAX_COMPRESS_DIM];
+    long tilesize[MAX_COMPRESS_DIM], thistilesize[MAX_COMPRESS_DIM];
+    long ftile[MAX_COMPRESS_DIM], ltile[MAX_COMPRESS_DIM];
+    long tfpixel[MAX_COMPRESS_DIM], tlpixel[MAX_COMPRESS_DIM];
+    long rowdim[MAX_COMPRESS_DIM], offset[MAX_COMPRESS_DIM],ntemp;
+    long fpixel[MAX_COMPRESS_DIM], lpixel[MAX_COMPRESS_DIM];
+    long inc[MAX_COMPRESS_DIM];
+    int ii, i5, i4, i3, i2, i1, i0, ndim, irow, pixlen, tilenul;
+    void *buffer;
+    char *bnullarray = 0;
+    double testnullval = 0.;
+
+    if (*status > 0) 
+        return(*status);
+
+    if (!fits_is_compressed_image(fptr, status) )
+    {
+        ffpmsg("CHDU is not a compressed image (fits_read_compressed_img)");
+        return(*status = DATA_DECOMPRESSION_ERR);
+    }
+
+    /* get temporary space for uncompressing one image tile */
+    if (datatype == TSHORT)
+    {
+       buffer =  malloc ((fptr->Fptr)->maxtilelen * sizeof (short)); 
+       pixlen = sizeof(short);
+       if (nullval)
+           testnullval = *(short *) nullval;
+    }
+    else if (datatype == TINT)
+    {
+       buffer =  malloc ((fptr->Fptr)->maxtilelen * sizeof (int));
+       pixlen = sizeof(int);
+       if (nullval)
+           testnullval = *(int *) nullval;
+    }
+    else if (datatype == TLONG)
+    {
+       buffer =  malloc ((fptr->Fptr)->maxtilelen * sizeof (long));
+       pixlen = sizeof(long);
+       if (nullval)
+           testnullval = *(long *) nullval;
+    }
+    else if (datatype == TFLOAT)
+    {
+       buffer =  malloc ((fptr->Fptr)->maxtilelen * sizeof (float));
+       pixlen = sizeof(float);
+       if (nullval)
+           testnullval = *(float *) nullval;
+    }
+    else if (datatype == TDOUBLE)
+    {
+       buffer =  malloc ((fptr->Fptr)->maxtilelen * sizeof (double));
+       pixlen = sizeof(double);
+       if (nullval)
+           testnullval = *(double *) nullval;
+    }
+    else if (datatype == TUSHORT)
+    {
+       buffer =  malloc ((fptr->Fptr)->maxtilelen * sizeof (unsigned short));
+       pixlen = sizeof(short);
+       if (nullval)
+           testnullval = *(unsigned short *) nullval;
+    }
+    else if (datatype == TUINT)
+    {
+       buffer =  malloc ((fptr->Fptr)->maxtilelen * sizeof (unsigned int));
+       pixlen = sizeof(int);
+       if (nullval)
+           testnullval = *(unsigned int *) nullval;
+    }
+    else if (datatype == TULONG)
+    {
+       buffer =  malloc ((fptr->Fptr)->maxtilelen * sizeof (unsigned long));
+       pixlen = sizeof(long);
+       if (nullval)
+           testnullval = *(unsigned long *) nullval;
+    }
+    else if (datatype == TBYTE || datatype == TSBYTE)
+    {
+       buffer =  malloc ((fptr->Fptr)->maxtilelen * sizeof (char));
+       pixlen = 1;
+       if (nullval)
+           testnullval = *(unsigned char *) nullval;
+    }
+    else
+    {
+        ffpmsg("unsupported datatype for uncompressing image");
+        return(*status = BAD_DATATYPE);
+    }
+
+    /* If nullcheck ==1 and nullval == 0, then this means that the */
+    /* calling routine does not want to check for null pixels in the array */
+    if (nullcheck == 1 && testnullval == 0.)
+        nullcheck = 0;
+
+    if (buffer == NULL)
+    {
+	    ffpmsg("Out of memory (fits_read_compress_img)");
+	    return (*status = MEMORY_ALLOCATION);
+    }
+	
+    /* allocate memory for a null flag array, if needed */
+    if (nullcheck == 2)
+    {
+        bnullarray = calloc ((fptr->Fptr)->maxtilelen, sizeof (char));
+
+        if (bnullarray == NULL)
+        {
+	    ffpmsg("Out of memory (fits_read_compress_img)");
+            free(buffer);
+	    return (*status = MEMORY_ALLOCATION);
+        }
+    }
+
+    /* initialize all the arrays */
+    for (ii = 0; ii < MAX_COMPRESS_DIM; ii++)
+    {
+        naxis[ii] = 1;
+        tiledim[ii] = 1;
+        tilesize[ii] = 1;
+        ftile[ii] = 1;
+        ltile[ii] = 1;
+        rowdim[ii] = 1;
+    }
+
+    ndim = (fptr->Fptr)->zndim;
+    ntemp = 1;
+    for (ii = 0; ii < ndim; ii++)
+    {
+        /* support for mirror-reversed image sections */
+        if (infpixel[ii] <= inlpixel[ii])
+        {
+           fpixel[ii] = (long) infpixel[ii];
+           lpixel[ii] = (long) inlpixel[ii];
+           inc[ii]    = ininc[ii];
+        }
+        else
+        {
+           fpixel[ii] = (long) inlpixel[ii];
+           lpixel[ii] = (long) infpixel[ii];
+           inc[ii]    = -ininc[ii];
+        }
+
+        /* calc number of tiles in each dimension, and tile containing */
+        /* the first and last pixel we want to read in each dimension  */
+        naxis[ii] = (fptr->Fptr)->znaxis[ii];
+        if (fpixel[ii] < 1)
+        {
+            if (nullcheck == 2)
+            {
+                free(bnullarray);
+            }
+            free(buffer);
+            return(*status = BAD_PIX_NUM);
+        }
+
+        tilesize[ii] = (fptr->Fptr)->tilesize[ii];
+        tiledim[ii] = (naxis[ii] - 1) / tilesize[ii] + 1;
+        ftile[ii]   = (fpixel[ii] - 1)   / tilesize[ii] + 1;
+        ltile[ii]   = minvalue((lpixel[ii] - 1) / tilesize[ii] + 1, 
+                                tiledim[ii]);
+        rowdim[ii]  = ntemp;  /* total tiles in each dimension */
+        ntemp *= tiledim[ii];
+    }
+
+    if (anynul)
+       *anynul = 0;  /* initialize */
+
+    /* support up to 6 dimensions for now */
+    /* tfpixel and tlpixel are the first and last image pixels */
+    /* along each dimension of the compression tile */
+    for (i5 = ftile[5]; i5 <= ltile[5]; i5++)
+    {
+     tfpixel[5] = (i5 - 1) * tilesize[5] + 1;
+     tlpixel[5] = minvalue(tfpixel[5] + tilesize[5] - 1, 
+                            naxis[5]);
+     thistilesize[5] = tlpixel[5] - tfpixel[5] + 1;
+     offset[5] = (i5 - 1) * rowdim[5];
+     for (i4 = ftile[4]; i4 <= ltile[4]; i4++)
+     {
+      tfpixel[4] = (i4 - 1) * tilesize[4] + 1;
+      tlpixel[4] = minvalue(tfpixel[4] + tilesize[4] - 1, 
+                            naxis[4]);
+      thistilesize[4] = thistilesize[5] * (tlpixel[4] - tfpixel[4] + 1);
+      offset[4] = (i4 - 1) * rowdim[4] + offset[5];
+      for (i3 = ftile[3]; i3 <= ltile[3]; i3++)
+      {
+        tfpixel[3] = (i3 - 1) * tilesize[3] + 1;
+        tlpixel[3] = minvalue(tfpixel[3] + tilesize[3] - 1, 
+                              naxis[3]);
+        thistilesize[3] = thistilesize[4] * (tlpixel[3] - tfpixel[3] + 1);
+        offset[3] = (i3 - 1) * rowdim[3] + offset[4];
+        for (i2 = ftile[2]; i2 <= ltile[2]; i2++)
+        {
+          tfpixel[2] = (i2 - 1) * tilesize[2] + 1;
+          tlpixel[2] = minvalue(tfpixel[2] + tilesize[2] - 1, 
+                                naxis[2]);
+          thistilesize[2] = thistilesize[3] * (tlpixel[2] - tfpixel[2] + 1);
+          offset[2] = (i2 - 1) * rowdim[2] + offset[3];
+          for (i1 = ftile[1]; i1 <= ltile[1]; i1++)
+          {
+            tfpixel[1] = (i1 - 1) * tilesize[1] + 1;
+            tlpixel[1] = minvalue(tfpixel[1] + tilesize[1] - 1, 
+                                  naxis[1]);
+            thistilesize[1] = thistilesize[2] * (tlpixel[1] - tfpixel[1] + 1);
+            offset[1] = (i1 - 1) * rowdim[1] + offset[2];
+            for (i0 = ftile[0]; i0 <= ltile[0]; i0++)
+            {
+              tfpixel[0] = (i0 - 1) * tilesize[0] + 1;
+              tlpixel[0] = minvalue(tfpixel[0] + tilesize[0] - 1, 
+                                    naxis[0]);
+              thistilesize[0] = thistilesize[1] * (tlpixel[0] - tfpixel[0] + 1);
+              /* calculate row of table containing this tile */
+              irow = i0 + offset[1];
+
+/*
+printf("row %d, %d %d, %d %d, %d %d; %d\n",
+              irow, tfpixel[0],tlpixel[0],tfpixel[1],tlpixel[1],tfpixel[2],tlpixel[2],
+	      thistilesize[0]);
+*/   
+              /* read and uncompress this row (tile) of the table */
+              /* also do type conversion and undefined pixel substitution */
+              /* at this point */
+
+              imcomp_decompress_tile(fptr, irow, thistilesize[0],
+                    datatype, nullcheck, nullval, buffer, bnullarray, &tilenul,
+                     status);
+
+              if (tilenul && anynul)
+                  *anynul = 1;  /* there are null pixels */
+/*
+printf(" pixlen=%d, ndim=%d, %d %d %d, %d %d %d, %d %d %d\n",
+     pixlen, ndim, fpixel[0],lpixel[0],inc[0],fpixel[1],lpixel[1],inc[1],
+     fpixel[2],lpixel[2],inc[2]);
+*/
+              /* copy the intersecting pixels from this tile to the output */
+              imcomp_copy_overlap(buffer, pixlen, ndim, tfpixel, tlpixel, 
+                     bnullarray, array, fpixel, lpixel, inc, nullcheck, 
+                     nullarray, status);
+            }
+          }
+        }
+      }
+     }
+    }
+    if (nullcheck == 2)
+    {
+        free(bnullarray);
+    }
+    free(buffer);
+
+    return(*status);
+}
+/*---------------------------------------------------------------------------*/
+int fits_read_write_compressed_img(fitsfile *fptr,   /* I - FITS file pointer      */
+            int  datatype,  /* I - datatype of the array to be returned      */
+            LONGLONG  *infpixel, /* I - 'bottom left corner' of the subsection    */
+            LONGLONG  *inlpixel, /* I - 'top right corner' of the subsection      */
+            long  *ininc,    /* I - increment to be applied in each dimension */
+            int  nullcheck,  /* I - 0 for no null checking                   */
+                              /*     1: set undefined pixels = nullval       */
+            void *nullval,    /* I - value for undefined pixels              */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            fitsfile *outfptr,   /* I - FITS file pointer                    */
+            int  *status)     /* IO - error status                           */
+/*
+   This is similar to fits_read_compressed_img, except that it writes
+   the pixels to the output image, on a tile by tile basis instead of returning
+   the array.
+*/
+{
+    int naxis[MAX_COMPRESS_DIM], tiledim[MAX_COMPRESS_DIM];
+    long tilesize[MAX_COMPRESS_DIM], thistilesize[MAX_COMPRESS_DIM];
+    long ftile[MAX_COMPRESS_DIM], ltile[MAX_COMPRESS_DIM];
+    long tfpixel[MAX_COMPRESS_DIM], tlpixel[MAX_COMPRESS_DIM];
+    long rowdim[MAX_COMPRESS_DIM], offset[MAX_COMPRESS_DIM],ntemp;
+    long fpixel[MAX_COMPRESS_DIM], lpixel[MAX_COMPRESS_DIM];
+    long inc[MAX_COMPRESS_DIM];
+    int ii, i5, i4, i3, i2, i1, i0, ndim, irow, pixlen, tilenul;
+    void *buffer;
+    char *bnullarray = 0;
+    double testnullval = 0.;
+    LONGLONG firstelem;
+
+    if (*status > 0) 
+        return(*status);
+
+    if (!fits_is_compressed_image(fptr, status) )
+    {
+        ffpmsg("CHDU is not a compressed image (fits_read_compressed_img)");
+        return(*status = DATA_DECOMPRESSION_ERR);
+    }
+
+    /* get temporary space for uncompressing one image tile */
+    if (datatype == TSHORT)
+    {
+       buffer =  malloc ((fptr->Fptr)->maxtilelen * sizeof (short)); 
+       pixlen = sizeof(short);
+       if (nullval)
+           testnullval = *(short *) nullval;
+    }
+    else if (datatype == TINT)
+    {
+       buffer =  malloc ((fptr->Fptr)->maxtilelen * sizeof (int));
+       pixlen = sizeof(int);
+       if (nullval)
+           testnullval = *(int *) nullval;
+    }
+    else if (datatype == TLONG)
+    {
+       buffer =  malloc ((fptr->Fptr)->maxtilelen * sizeof (long));
+       pixlen = sizeof(long);
+       if (nullval)
+           testnullval = *(long *) nullval;
+    }
+    else if (datatype == TFLOAT)
+    {
+       buffer =  malloc ((fptr->Fptr)->maxtilelen * sizeof (float));
+       pixlen = sizeof(float);
+       if (nullval)
+           testnullval = *(float *) nullval;
+    }
+    else if (datatype == TDOUBLE)
+    {
+       buffer =  malloc ((fptr->Fptr)->maxtilelen * sizeof (double));
+       pixlen = sizeof(double);
+       if (nullval)
+           testnullval = *(double *) nullval;
+    }
+    else if (datatype == TUSHORT)
+    {
+       buffer =  malloc ((fptr->Fptr)->maxtilelen * sizeof (unsigned short));
+       pixlen = sizeof(short);
+       if (nullval)
+           testnullval = *(unsigned short *) nullval;
+    }
+    else if (datatype == TUINT)
+    {
+       buffer =  malloc ((fptr->Fptr)->maxtilelen * sizeof (unsigned int));
+       pixlen = sizeof(int);
+       if (nullval)
+           testnullval = *(unsigned int *) nullval;
+    }
+    else if (datatype == TULONG)
+    {
+       buffer =  malloc ((fptr->Fptr)->maxtilelen * sizeof (unsigned long));
+       pixlen = sizeof(long);
+       if (nullval)
+           testnullval = *(unsigned long *) nullval;
+    }
+    else if (datatype == TBYTE || datatype == TSBYTE)
+    {
+       buffer =  malloc ((fptr->Fptr)->maxtilelen * sizeof (char));
+       pixlen = 1;
+       if (nullval)
+           testnullval = *(unsigned char *) nullval;
+    }
+    else
+    {
+        ffpmsg("unsupported datatype for uncompressing image");
+        return(*status = BAD_DATATYPE);
+    }
+
+    /* If nullcheck ==1 and nullval == 0, then this means that the */
+    /* calling routine does not want to check for null pixels in the array */
+    if (nullcheck == 1 && testnullval == 0.)
+        nullcheck = 0;
+
+    if (buffer == NULL)
+    {
+	    ffpmsg("Out of memory (fits_read_compress_img)");
+	    return (*status = MEMORY_ALLOCATION);
+    }
+
+    /* initialize all the arrays */
+    for (ii = 0; ii < MAX_COMPRESS_DIM; ii++)
+    {
+        naxis[ii] = 1;
+        tiledim[ii] = 1;
+        tilesize[ii] = 1;
+        ftile[ii] = 1;
+        ltile[ii] = 1;
+        rowdim[ii] = 1;
+    }
+
+    ndim = (fptr->Fptr)->zndim;
+    ntemp = 1;
+    for (ii = 0; ii < ndim; ii++)
+    {
+        /* support for mirror-reversed image sections */
+        if (infpixel[ii] <= inlpixel[ii])
+        {
+           fpixel[ii] = (long) infpixel[ii];
+           lpixel[ii] = (long) inlpixel[ii];
+           inc[ii]    = ininc[ii];
+        }
+        else
+        {
+           fpixel[ii] = (long) inlpixel[ii];
+           lpixel[ii] = (long) infpixel[ii];
+           inc[ii]    = -ininc[ii];
+        }
+
+        /* calc number of tiles in each dimension, and tile containing */
+        /* the first and last pixel we want to read in each dimension  */
+        naxis[ii] = (fptr->Fptr)->znaxis[ii];
+        if (fpixel[ii] < 1)
+        {
+            free(buffer);
+            return(*status = BAD_PIX_NUM);
+        }
+
+        tilesize[ii] = (fptr->Fptr)->tilesize[ii];
+        tiledim[ii] = (naxis[ii] - 1) / tilesize[ii] + 1;
+        ftile[ii]   = (fpixel[ii] - 1)   / tilesize[ii] + 1;
+        ltile[ii]   = minvalue((lpixel[ii] - 1) / tilesize[ii] + 1, 
+                                tiledim[ii]);
+        rowdim[ii]  = ntemp;  /* total tiles in each dimension */
+        ntemp *= tiledim[ii];
+    }
+
+    if (anynul)
+       *anynul = 0;  /* initialize */
+
+    firstelem = 1;
+
+    /* support up to 6 dimensions for now */
+    /* tfpixel and tlpixel are the first and last image pixels */
+    /* along each dimension of the compression tile */
+    for (i5 = ftile[5]; i5 <= ltile[5]; i5++)
+    {
+     tfpixel[5] = (i5 - 1) * tilesize[5] + 1;
+     tlpixel[5] = minvalue(tfpixel[5] + tilesize[5] - 1, 
+                            naxis[5]);
+     thistilesize[5] = tlpixel[5] - tfpixel[5] + 1;
+     offset[5] = (i5 - 1) * rowdim[5];
+     for (i4 = ftile[4]; i4 <= ltile[4]; i4++)
+     {
+      tfpixel[4] = (i4 - 1) * tilesize[4] + 1;
+      tlpixel[4] = minvalue(tfpixel[4] + tilesize[4] - 1, 
+                            naxis[4]);
+      thistilesize[4] = thistilesize[5] * (tlpixel[4] - tfpixel[4] + 1);
+      offset[4] = (i4 - 1) * rowdim[4] + offset[5];
+      for (i3 = ftile[3]; i3 <= ltile[3]; i3++)
+      {
+        tfpixel[3] = (i3 - 1) * tilesize[3] + 1;
+        tlpixel[3] = minvalue(tfpixel[3] + tilesize[3] - 1, 
+                              naxis[3]);
+        thistilesize[3] = thistilesize[4] * (tlpixel[3] - tfpixel[3] + 1);
+        offset[3] = (i3 - 1) * rowdim[3] + offset[4];
+        for (i2 = ftile[2]; i2 <= ltile[2]; i2++)
+        {
+          tfpixel[2] = (i2 - 1) * tilesize[2] + 1;
+          tlpixel[2] = minvalue(tfpixel[2] + tilesize[2] - 1, 
+                                naxis[2]);
+          thistilesize[2] = thistilesize[3] * (tlpixel[2] - tfpixel[2] + 1);
+          offset[2] = (i2 - 1) * rowdim[2] + offset[3];
+          for (i1 = ftile[1]; i1 <= ltile[1]; i1++)
+          {
+            tfpixel[1] = (i1 - 1) * tilesize[1] + 1;
+            tlpixel[1] = minvalue(tfpixel[1] + tilesize[1] - 1, 
+                                  naxis[1]);
+            thistilesize[1] = thistilesize[2] * (tlpixel[1] - tfpixel[1] + 1);
+            offset[1] = (i1 - 1) * rowdim[1] + offset[2];
+            for (i0 = ftile[0]; i0 <= ltile[0]; i0++)
+            {
+              tfpixel[0] = (i0 - 1) * tilesize[0] + 1;
+              tlpixel[0] = minvalue(tfpixel[0] + tilesize[0] - 1, 
+                                    naxis[0]);
+              thistilesize[0] = thistilesize[1] * (tlpixel[0] - tfpixel[0] + 1);
+              /* calculate row of table containing this tile */
+              irow = i0 + offset[1];
+ 
+              /* read and uncompress this row (tile) of the table */
+              /* also do type conversion and undefined pixel substitution */
+              /* at this point */
+
+              imcomp_decompress_tile(fptr, irow, thistilesize[0],
+                    datatype, nullcheck, nullval, buffer, bnullarray, &tilenul,
+                     status);
+
+               /* write the image to the output file */
+
+              if (tilenul && anynul) {     
+                   /* this assumes that the tiled pixels are in the same order
+		      as in the uncompressed FITS image.  This is not necessarily
+		      the case, but it almost alway is in practice.  
+		      Note that null checking is not performed for integer images,
+		      so this could only be a problem for tile compressed floating
+		      point images that use an unconventional tiling pattern.
+		   */
+                   fits_write_imgnull(outfptr, datatype, firstelem, thistilesize[0],
+		      buffer, nullval, status);
+              } else {
+                  fits_write_subset(outfptr, datatype, tfpixel, tlpixel, 
+		      buffer, status);
+              }
+
+              firstelem += thistilesize[0];
+
+            }
+          }
+        }
+      }
+     }
+    }
+
+    free(buffer);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_read_compressed_pixels(fitsfile *fptr, /* I - FITS file pointer    */
+            int  datatype,  /* I - datatype of the array to be returned     */
+            LONGLONG   fpixel, /* I - 'first pixel to read          */
+            LONGLONG   npixel,  /* I - number of pixels to read      */
+            int  nullcheck,  /* I - 0 for no null checking                   */
+                              /*     1: set undefined pixels = nullval       */
+                              /*     2: set nullarray=1 for undefined pixels */
+            void *nullval,    /* I - value for undefined pixels              */
+            void *array,      /* O - array of values that are returned       */
+            char *nullarray,  /* O - array of flags = 1 if nullcheck = 2     */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            int  *status)     /* IO - error status                           */
+/*
+   Read a consecutive set of pixels from a compressed image.  This routine
+   interpretes the n-dimensional image as a long one-dimensional array. 
+   This is actually a rather inconvenient way to read compressed images in
+   general, and could be rather inefficient if the requested pixels to be
+   read are located in many different image compression tiles.    
+
+   The general strategy used here is to read the requested pixels in blocks
+   that correspond to rectangular image sections.  
+*/
+{
+    int naxis, ii, bytesperpixel, planenul;
+    long naxes[MAX_COMPRESS_DIM], nread;
+    long nplane, inc[MAX_COMPRESS_DIM];
+    LONGLONG tfirst, tlast, last0, last1, dimsize[MAX_COMPRESS_DIM];
+    LONGLONG firstcoord[MAX_COMPRESS_DIM], lastcoord[MAX_COMPRESS_DIM];
+    char *arrayptr, *nullarrayptr;
+
+    if (*status > 0)
+        return(*status);
+
+    arrayptr = (char *) array;
+    nullarrayptr = nullarray;
+
+    /* get size of array pixels, in bytes */
+    bytesperpixel = ffpxsz(datatype);
+
+    for (ii = 0; ii < MAX_COMPRESS_DIM; ii++)
+    {
+        naxes[ii] = 1;
+        firstcoord[ii] = 0;
+        lastcoord[ii] = 0;
+        inc[ii] = 1;
+    }
+
+    /*  determine the dimensions of the image to be read */
+    ffgidm(fptr, &naxis, status);
+    ffgisz(fptr, MAX_COMPRESS_DIM, naxes, status);
+
+    /* calc the cumulative number of pixels in each successive dimension */
+    dimsize[0] = 1;
+    for (ii = 1; ii < MAX_COMPRESS_DIM; ii++)
+         dimsize[ii] = dimsize[ii - 1] * naxes[ii - 1];
+
+    /*  determine the coordinate of the first and last pixel in the image */
+    /*  Use zero based indexes here */
+    tfirst = fpixel - 1;
+    tlast = tfirst + npixel - 1;
+    for (ii = naxis - 1; ii >= 0; ii--)
+    {
+        firstcoord[ii] = tfirst / dimsize[ii];
+        lastcoord[ii] =  tlast / dimsize[ii];
+        tfirst = tfirst - firstcoord[ii] * dimsize[ii];
+        tlast = tlast - lastcoord[ii] * dimsize[ii];
+    }
+
+    /* to simplify things, treat 1-D, 2-D, and 3-D images as separate cases */
+
+    if (naxis == 1)
+    {
+        /* Simple: just read the requested range of pixels */
+
+        firstcoord[0] = firstcoord[0] + 1;
+        lastcoord[0] = lastcoord[0] + 1;
+        fits_read_compressed_img(fptr, datatype, firstcoord, lastcoord, inc,
+            nullcheck, nullval, array, nullarray, anynul, status);
+        return(*status);
+    }
+    else if (naxis == 2)
+    {
+        nplane = 0;  /* read 1st (and only) plane of the image */
+
+        fits_read_compressed_img_plane(fptr, datatype, bytesperpixel,
+          nplane, firstcoord, lastcoord, inc, naxes, nullcheck, nullval,
+          array, nullarray, anynul, &nread, status);
+    }
+    else if (naxis == 3)
+    {
+        /* test for special case: reading an integral number of planes */
+        if (firstcoord[0] == 0 && firstcoord[1] == 0 &&
+            lastcoord[0] == naxes[0] - 1 && lastcoord[1] == naxes[1] - 1)
+        {
+            for (ii = 0; ii < MAX_COMPRESS_DIM; ii++)
+            {
+                /* convert from zero base to 1 base */
+                (firstcoord[ii])++;
+                (lastcoord[ii])++;
+            }
+
+            /* we can read the contiguous block of pixels in one go */
+            fits_read_compressed_img(fptr, datatype, firstcoord, lastcoord, inc,
+                nullcheck, nullval, array, nullarray, anynul, status);
+
+            return(*status);
+        }
+
+        if (anynul)
+            *anynul = 0;  /* initialize */
+
+        /* save last coordinate in temporary variables */
+        last0 = lastcoord[0];
+        last1 = lastcoord[1];
+
+        if (firstcoord[2] < lastcoord[2])
+        {
+            /* we will read up to the last pixel in all but the last plane */
+            lastcoord[0] = naxes[0] - 1;
+            lastcoord[1] = naxes[1] - 1;
+        }
+
+        /* read one plane of the cube at a time, for simplicity */
+        for (nplane = (long) firstcoord[2]; nplane <= lastcoord[2]; nplane++)
+        {
+            if (nplane == lastcoord[2])
+            {
+                lastcoord[0] = last0;
+                lastcoord[1] = last1;
+            }
+
+            fits_read_compressed_img_plane(fptr, datatype, bytesperpixel,
+              nplane, firstcoord, lastcoord, inc, naxes, nullcheck, nullval,
+              arrayptr, nullarrayptr, &planenul, &nread, status);
+
+            if (planenul && anynul)
+               *anynul = 1;  /* there are null pixels */
+
+            /* for all subsequent planes, we start with the first pixel */
+            firstcoord[0] = 0;
+            firstcoord[1] = 0;
+
+            /* increment pointers to next elements to be read */
+            arrayptr = arrayptr + nread * bytesperpixel;
+            if (nullarrayptr && (nullcheck == 2) )
+                nullarrayptr = nullarrayptr + nread;
+        }
+    }
+    else
+    {
+        ffpmsg("only 1D, 2D, or 3D images are currently supported");
+        return(*status = DATA_DECOMPRESSION_ERR);
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_read_compressed_img_plane(fitsfile *fptr, /* I - FITS file   */
+            int  datatype,  /* I - datatype of the array to be returned      */
+            int  bytesperpixel, /* I - number of bytes per pixel in array */
+            long   nplane,  /* I - which plane of the cube to read      */
+            LONGLONG *firstcoord,  /* coordinate of first pixel to read */
+            LONGLONG *lastcoord,   /* coordinate of last pixel to read */
+            long *inc,         /* increment of pixels to read */
+            long *naxes,      /* size of each image dimension */
+            int  nullcheck,  /* I - 0 for no null checking                   */
+                              /*     1: set undefined pixels = nullval       */
+                              /*     2: set nullarray=1 for undefined pixels */
+            void *nullval,    /* I - value for undefined pixels              */
+            void *array,      /* O - array of values that are returned       */
+            char *nullarray,  /* O - array of flags = 1 if nullcheck = 2     */
+            int  *anynul,     /* O - set to 1 if any values are null; else 0 */
+            long *nread,      /* O - total number of pixels read and returned*/
+            int  *status)     /* IO - error status                           */
+
+   /*
+           in general we have to read the first partial row of the image,
+           followed by the middle complete rows, followed by the last
+           partial row of the image.  If the first or last rows are complete,
+           then read them at the same time as all the middle rows.
+    */
+{
+     /* bottom left coord. and top right coord. */
+    LONGLONG blc[MAX_COMPRESS_DIM], trc[MAX_COMPRESS_DIM]; 
+    char *arrayptr, *nullarrayptr;
+    int tnull;
+
+    if (anynul)
+        *anynul = 0;
+
+    *nread = 0;
+
+    arrayptr = (char *) array;
+    nullarrayptr = nullarray;
+
+    blc[2] = nplane + 1;
+    trc[2] = nplane + 1;
+
+    if (firstcoord[0] != 0)
+    { 
+            /* have to read a partial first row */
+            blc[0] = firstcoord[0] + 1;
+            blc[1] = firstcoord[1] + 1;
+            trc[1] = blc[1];  
+            if (lastcoord[1] == firstcoord[1])
+               trc[0] = lastcoord[0] + 1; /* 1st and last pixels in same row */
+            else
+               trc[0] = naxes[0];  /* read entire rest of the row */
+
+            fits_read_compressed_img(fptr, datatype, blc, trc, inc,
+                nullcheck, nullval, arrayptr, nullarrayptr, &tnull, status);
+
+            *nread = *nread + (long) (trc[0] - blc[0] + 1);
+
+            if (tnull && anynul)
+               *anynul = 1;  /* there are null pixels */
+
+            if (lastcoord[1] == firstcoord[1])
+            {
+               return(*status);  /* finished */
+            }
+
+            /* set starting coord to beginning of next line */
+            firstcoord[0] = 0;
+            firstcoord[1] += 1;
+            arrayptr = arrayptr + (trc[0] - blc[0] + 1) * bytesperpixel;
+            if (nullarrayptr && (nullcheck == 2) )
+                nullarrayptr = nullarrayptr + (trc[0] - blc[0] + 1);
+
+    }
+
+    /* read contiguous complete rows of the image, if any */
+    blc[0] = 1;
+    blc[1] = firstcoord[1] + 1;
+    trc[0] = naxes[0];
+
+    if (lastcoord[0] + 1 == naxes[0])
+    {
+            /* can read the last complete row, too */
+            trc[1] = lastcoord[1] + 1;
+    }
+    else
+    {
+            /* last row is incomplete; have to read it separately */
+            trc[1] = lastcoord[1];
+    }
+
+    if (trc[1] >= blc[1])  /* must have at least one whole line to read */
+    {
+        fits_read_compressed_img(fptr, datatype, blc, trc, inc,
+                nullcheck, nullval, arrayptr, nullarrayptr, &tnull, status);
+
+        *nread = *nread + (long) ((trc[1] - blc[1] + 1) * naxes[0]);
+
+        if (tnull && anynul)
+           *anynul = 1;
+
+        if (lastcoord[1] + 1 == trc[1])
+               return(*status);  /* finished */
+
+        /* increment pointers for the last partial row */
+        arrayptr = arrayptr + (trc[1] - blc[1] + 1) * naxes[0] * bytesperpixel;
+        if (nullarrayptr && (nullcheck == 2) )
+                nullarrayptr = nullarrayptr + (trc[1] - blc[1] + 1) * naxes[0];
+     }
+
+    if (trc[1] == lastcoord[1] + 1)
+        return(*status);           /* all done */
+
+    /* set starting and ending coord to last line */
+
+    trc[0] = lastcoord[0] + 1;
+    trc[1] = lastcoord[1] + 1;
+    blc[1] = trc[1];
+
+    fits_read_compressed_img(fptr, datatype, blc, trc, inc,
+                nullcheck, nullval, arrayptr, nullarrayptr, &tnull, status);
+
+    if (tnull && anynul)
+       *anynul = 1;
+
+    *nread = *nread + (long) (trc[0] - blc[0] + 1);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int imcomp_get_compressed_image_par(fitsfile *infptr, int *status)
+ 
+/* 
+    This routine reads keywords from a BINTABLE extension containing a
+    compressed image.
+*/
+{
+    char keyword[FLEN_KEYWORD];
+    char value[FLEN_VALUE];
+    int ii, tstatus, doffset;
+    long expect_nrows, maxtilelen;
+
+    if (*status > 0)
+        return(*status);
+
+    /* Copy relevant header keyword values to structure */
+    if (ffgky (infptr, TSTRING, "ZCMPTYPE", value, NULL, status) > 0)
+    {
+        ffpmsg("required ZCMPTYPE compression keyword not found in");
+        ffpmsg(" imcomp_get_compressed_image_par");
+        return(*status);
+    }
+
+    (infptr->Fptr)->zcmptype[0] = '\0';
+    strncat((infptr->Fptr)->zcmptype, value, 11);
+
+    if (!FSTRCMP(value, "RICE_1") )
+        (infptr->Fptr)->compress_type = RICE_1;
+    else if (!FSTRCMP(value, "HCOMPRESS_1") )
+        (infptr->Fptr)->compress_type = HCOMPRESS_1;
+    else if (!FSTRCMP(value, "GZIP_1") )
+        (infptr->Fptr)->compress_type = GZIP_1;
+    else if (!FSTRCMP(value, "GZIP_2") )
+        (infptr->Fptr)->compress_type = GZIP_2;
+    else if (!FSTRCMP(value, "BZIP2_1") )
+        (infptr->Fptr)->compress_type = BZIP2_1;
+    else if (!FSTRCMP(value, "PLIO_1") )
+        (infptr->Fptr)->compress_type = PLIO_1;
+    else if (!FSTRCMP(value, "NOCOMPRESS") )
+        (infptr->Fptr)->compress_type = NOCOMPRESS;
+    else
+    {
+        ffpmsg("Unknown image compression type:");
+        ffpmsg(value);
+	return (*status = DATA_DECOMPRESSION_ERR);
+    }
+
+    /* get the floating point to integer quantization type, if present. */
+    /* FITS files produced before 2009 will not have this keyword */
+    tstatus = 0;
+    if (ffgky(infptr, TSTRING, "ZQUANTIZ", value, NULL, &tstatus) > 0)
+    {
+        (infptr->Fptr)->quantize_dither = 0;
+    } else {
+        if (!FSTRCMP(value, "NONE") )
+            (infptr->Fptr)->quantize_level = NO_QUANTIZE;
+        else if (!FSTRCMP(value, "SUBTRACTIVE_DITHER_1") )
+            (infptr->Fptr)->quantize_dither = SUBTRACTIVE_DITHER_1;
+        else
+            (infptr->Fptr)->quantize_dither = 0;
+    }
+
+    /* get the floating point quantization dithering offset, if present. */
+    /* FITS files produced before October 2009 will not have this keyword */
+    tstatus = 0;
+    if (ffgky(infptr, TINT, "ZDITHER0", &doffset, NULL, &tstatus) > 0)
+    {
+	/* by default start with 1st element of random sequence */
+        (infptr->Fptr)->dither_offset = 1;  
+    } else {
+        (infptr->Fptr)->dither_offset = doffset;
+    }
+
+    if (ffgky (infptr, TINT,  "ZBITPIX",  &(infptr->Fptr)->zbitpix,  
+               NULL, status) > 0)
+    {
+        ffpmsg("required ZBITPIX compression keyword not found");
+        return(*status);
+    }
+
+    if (ffgky (infptr,TINT, "ZNAXIS", &(infptr->Fptr)->zndim, NULL, status) > 0)
+    {
+        ffpmsg("required ZNAXIS compression keyword not found");
+        return(*status);
+    }
+
+    if ((infptr->Fptr)->zndim < 1)
+    {
+        ffpmsg("Compressed image has no data (ZNAXIS < 1)");
+	return (*status = BAD_NAXIS);
+    }
+
+    if ((infptr->Fptr)->zndim > MAX_COMPRESS_DIM)
+    {
+        ffpmsg("Compressed image has too many dimensions");
+        return(*status = BAD_NAXIS);
+    }
+
+    expect_nrows = 1;
+    maxtilelen = 1;
+    for (ii = 0;  ii < (infptr->Fptr)->zndim;  ii++)
+    {
+        /* get image size */
+        sprintf (keyword, "ZNAXIS%d", ii+1);
+	ffgky (infptr, TLONG,keyword, &(infptr->Fptr)->znaxis[ii],NULL,status);
+
+        if (*status > 0)
+        {
+            ffpmsg("required ZNAXISn compression keyword not found");
+            return(*status);
+        }
+
+        /* get compression tile size */
+	sprintf (keyword, "ZTILE%d", ii+1);
+
+        /* set default tile size in case keywords are not present */
+        if (ii == 0)
+            (infptr->Fptr)->tilesize[0] = (infptr->Fptr)->znaxis[0];
+        else
+            (infptr->Fptr)->tilesize[ii] = 1;
+
+        tstatus = 0;
+	ffgky (infptr, TLONG, keyword, &(infptr->Fptr)->tilesize[ii], NULL, 
+               &tstatus);
+
+        expect_nrows *= (((infptr->Fptr)->znaxis[ii] - 1) / 
+                  (infptr->Fptr)->tilesize[ii]+ 1);
+        maxtilelen *= (infptr->Fptr)->tilesize[ii];
+    }
+
+    /* check number of rows */
+    if (expect_nrows != (infptr->Fptr)->numrows)
+    {
+        ffpmsg(
+        "number of table rows != the number of tiles in compressed image");
+        return (*status = DATA_DECOMPRESSION_ERR);
+    }
+
+    /* read any algorithm specific parameters */
+    if ((infptr->Fptr)->compress_type == RICE_1 )
+    {
+        if (ffgky(infptr, TINT,"ZVAL1", &(infptr->Fptr)->rice_blocksize,
+                  NULL, status) > 0)
+        {
+            ffpmsg("required ZVAL1 compression keyword not found");
+            return(*status);
+        }
+
+        tstatus = 0;
+        if (ffgky(infptr, TINT,"ZVAL2", &(infptr->Fptr)->rice_bytepix,
+                  NULL, &tstatus) > 0)
+        {
+            (infptr->Fptr)->rice_bytepix = 4;  /* default value */
+        }
+
+        if ((infptr->Fptr)->rice_blocksize < 16 &&
+	    (infptr->Fptr)->rice_bytepix > 8) {
+	     /* values are reversed */
+	     tstatus = (infptr->Fptr)->rice_bytepix;
+	     (infptr->Fptr)->rice_bytepix = (infptr->Fptr)->rice_blocksize;
+	     (infptr->Fptr)->rice_blocksize = tstatus;
+        }
+    } else if ((infptr->Fptr)->compress_type == HCOMPRESS_1 ) {
+
+        if (ffgky(infptr, TFLOAT,"ZVAL1", &(infptr->Fptr)->hcomp_scale,
+                  NULL, status) > 0)
+        {
+            ffpmsg("required ZVAL1 compression keyword not found");
+            return(*status);
+        }
+
+        tstatus = 0;
+        ffgky(infptr, TINT,"ZVAL2", &(infptr->Fptr)->hcomp_smooth,
+                  NULL, &tstatus);
+    }    
+
+    /* store number of pixels in each compression tile, */
+    /* and max size of the compressed tile buffer */
+    (infptr->Fptr)->maxtilelen = maxtilelen;
+
+    (infptr->Fptr)->maxelem = 
+           imcomp_calc_max_elem ((infptr->Fptr)->compress_type, maxtilelen, 
+               (infptr->Fptr)->zbitpix, (infptr->Fptr)->rice_blocksize);
+
+    /* Get Column numbers. */
+    if (ffgcno(infptr, CASEINSEN, "COMPRESSED_DATA",
+         &(infptr->Fptr)->cn_compressed, status) > 0)
+    {
+        ffpmsg("couldn't find COMPRESSED_DATA column (fits_get_compressed_img_par)");
+        return(*status = DATA_DECOMPRESSION_ERR);
+    }
+
+    ffpmrk(); /* put mark on message stack; erase any messages after this */
+
+    tstatus = 0;
+    ffgcno(infptr,CASEINSEN, "UNCOMPRESSED_DATA",
+          &(infptr->Fptr)->cn_uncompressed, &tstatus);
+
+    tstatus = 0;
+    ffgcno(infptr,CASEINSEN, "GZIP_COMPRESSED_DATA",
+          &(infptr->Fptr)->cn_gzip_data, &tstatus);
+
+    tstatus = 0;
+    if (ffgcno(infptr, CASEINSEN, "ZSCALE", &(infptr->Fptr)->cn_zscale,
+              &tstatus) > 0)
+    {
+        /* CMPSCALE column doesn't exist; see if there is a keyword */
+        tstatus = 0;
+        if (ffgky(infptr, TDOUBLE, "ZSCALE", &(infptr->Fptr)->zscale, NULL, 
+                 &tstatus) <= 0)
+            (infptr->Fptr)->cn_zscale = -1;  /* flag for a constant ZSCALE */
+    }
+
+    tstatus = 0;
+    if (ffgcno(infptr, CASEINSEN, "ZZERO", &(infptr->Fptr)->cn_zzero,
+               &tstatus) > 0)
+    {
+        /* CMPZERO column doesn't exist; see if there is a keyword */
+        tstatus = 0;
+        if (ffgky(infptr, TDOUBLE, "ZZERO", &(infptr->Fptr)->zzero, NULL, 
+                  &tstatus) <= 0)
+            (infptr->Fptr)->cn_zzero = -1;  /* flag for a constant ZZERO */
+    }
+
+    tstatus = 0;
+    if (ffgcno(infptr, CASEINSEN, "ZBLANK", &(infptr->Fptr)->cn_zblank,
+               &tstatus) > 0)
+    {
+        /* ZBLANK column doesn't exist; see if there is a keyword */
+        tstatus = 0;
+        if (ffgky(infptr, TINT, "ZBLANK", &(infptr->Fptr)->zblank, NULL,
+                  &tstatus) <= 0)  {
+            (infptr->Fptr)->cn_zblank = -1;  /* flag for a constant ZBLANK */
+
+        } else {
+           /* ZBLANK keyword doesn't exist; see if there is a BLANK keyword */
+           tstatus = 0;
+           if (ffgky(infptr, TINT, "BLANK", &(infptr->Fptr)->zblank, NULL,
+                  &tstatus) <= 0)  
+              (infptr->Fptr)->cn_zblank = -1;  /* flag for a constant ZBLANK */
+        }
+    }
+
+    /* read the conventional BSCALE and BZERO scaling keywords, if present */
+    tstatus = 0;
+    if (ffgky (infptr, TDOUBLE, "BSCALE", &(infptr->Fptr)->cn_bscale, 
+        NULL, &tstatus) > 0)
+    {
+        (infptr->Fptr)->cn_bscale = 1.0;
+    }
+
+    tstatus = 0;
+    if (ffgky (infptr, TDOUBLE, "BZERO", &(infptr->Fptr)->cn_bzero, 
+        NULL, &tstatus) > 0)
+    {
+        (infptr->Fptr)->cn_bzero = 0.0;
+        (infptr->Fptr)->cn_actual_bzero = 0.0;
+    } else {
+        (infptr->Fptr)->cn_actual_bzero = (infptr->Fptr)->cn_bzero;
+    }
+
+    ffcmrk();  /* clear any spurious error messages, back to the mark */
+    return (*status);
+}
+/*--------------------------------------------------------------------------*/
+int imcomp_copy_imheader(fitsfile *infptr, fitsfile *outfptr, int *status)
+/*
+    This routine reads the header keywords from the input image and
+    copies them to the output image;  the manditory structural keywords
+    and the checksum keywords are not copied. If the DATE keyword is copied,
+    then it is updated with the current date and time.
+*/
+{
+    int nkeys, ii, keyclass;
+    char card[FLEN_CARD];	/* a header record */
+
+    if (*status > 0)
+        return(*status);
+
+    ffghsp(infptr, &nkeys, NULL, status); /* get number of keywords in image */
+
+    for (ii = 5; ii <= nkeys; ii++)  /* skip the first 4 keywords */
+    {
+        ffgrec(infptr, ii, card, status);
+
+	keyclass = ffgkcl(card);  /* Get the type/class of keyword */
+
+        /* don't copy structural keywords or checksum keywords */
+        if ((keyclass <= TYP_CMPRS_KEY) || (keyclass == TYP_CKSUM_KEY))
+	    continue;
+
+        if (FSTRNCMP(card, "DATE ", 5) == 0) /* write current date */
+        {
+            ffpdat(outfptr, status);
+        }
+        else if (FSTRNCMP(card, "EXTNAME ", 8) == 0) 
+        {
+            /* don't copy default EXTNAME keyword from a compressed image */
+            if (FSTRNCMP(card, "EXTNAME = 'COMPRESSED_IMAGE'", 28))
+            {
+                /* if EXTNAME keyword already exists, overwrite it */
+                /* otherwise append a new EXTNAME keyword */
+                ffucrd(outfptr, "EXTNAME", card, status);
+            }
+        }
+        else
+        {
+            /* just copy the keyword to the output header */
+	    ffprec (outfptr, card, status);
+        }
+
+        if (*status > 0)
+           return (*status);
+    }
+    return (*status);
+}
+/*--------------------------------------------------------------------------*/
+int imcomp_copy_img2comp(fitsfile *infptr, fitsfile *outfptr, int *status)
+/*
+    This routine copies the header keywords from the uncompressed input image 
+    and to the compressed image (in a binary table) 
+*/
+{
+    char card[FLEN_CARD], card2[FLEN_CARD];	/* a header record */
+    int nkeys, nmore, ii, jj, tstatus, bitpix;
+
+    /* tile compressed image keyword translation table  */
+    /*                        INPUT      OUTPUT  */
+    /*                       01234567   01234567 */
+    char *patterns[][2] = {{"SIMPLE",  "ZSIMPLE" },  
+			   {"XTENSION", "ZTENSION" },
+			   {"BITPIX",  "ZBITPIX" },
+			   {"NAXIS",   "ZNAXIS"  },
+			   {"NAXISm",  "ZNAXISm" },
+			   {"EXTEND",  "ZEXTEND" },
+			   {"BLOCKED", "ZBLOCKED"},
+			   {"PCOUNT",  "ZPCOUNT" },  
+			   {"GCOUNT",  "ZGCOUNT" },
+
+			   {"CHECKSUM","ZHECKSUM"},  /* save original checksums */
+			   {"DATASUM", "ZDATASUM"},
+			   
+			   {"*",       "+"       }}; /* copy all other keywords */
+    int npat;
+
+    if (*status > 0)
+        return(*status);
+
+    /* write a default EXTNAME keyword if it doesn't exist in input file*/
+    fits_read_card(infptr, "EXTNAME", card, status);
+    
+    if (*status) {
+       *status = 0;
+       strcpy(card, "EXTNAME = 'COMPRESSED_IMAGE'");
+       fits_write_record(outfptr, card, status);
+    }
+
+    /* copy all the keywords from the input file to the output */
+    npat = sizeof(patterns)/sizeof(patterns[0][0])/2;
+    fits_translate_keywords(infptr, outfptr, 1, patterns, npat,
+			    0, 0, 0, status);
+
+
+    if ( (outfptr->Fptr)->request_lossy_int_compress != 0) { 
+
+	/* request was made to compress integer images as if they had float pixels. */
+	/* If input image has positive bitpix value, then reset the output ZBITPIX */
+	/* value to -32. */
+
+	fits_read_key(infptr, TINT, "BITPIX", &bitpix, NULL, status);
+
+	if (*status <= 0 && bitpix > 0) {
+	    fits_modify_key_lng(outfptr, "ZBITPIX", -32, NULL, status);
+
+	    /* also delete the BSCALE, BZERO, and BLANK keywords */
+	    tstatus = 0;
+	    fits_delete_key(outfptr, "BSCALE", &tstatus);
+	    tstatus = 0;
+	    fits_delete_key(outfptr, "BZERO", &tstatus);
+	    tstatus = 0;
+	    fits_delete_key(outfptr, "BLANK", &tstatus);
+	}
+    }
+
+   /*
+     For compatibility with software that uses an older version of CFITSIO,
+     we must make certain that the new ZQUANTIZ keyword, if it exists, must
+     occur after the other peudo-required keywords (e.g., ZSIMPLE, ZBITPIX,
+     etc.).  Do this by trying to delete the keyword.  If that succeeds (and
+     thus the keyword did exist) then rewrite the keyword at the end of header.
+     In principle this should not be necessary once all software has upgraded
+     to a newer version of CFITSIO (version number greater than 3.181, newer
+     than August 2009).
+     
+     Do the same for the new ZDITHER0 keyword.
+   */
+
+   tstatus = 0;
+   if (fits_read_card(outfptr, "ZQUANTIZ", card, &tstatus) == 0)
+   {
+        fits_delete_key(outfptr, "ZQUANTIZ", status);
+
+        /* rewrite the deleted keyword at the end of the header */
+        fits_write_record(outfptr, card, status);
+
+	fits_write_history(outfptr, 
+	    "Image was compressed by CFITSIO using scaled integer quantization:", status);
+	sprintf(card2, "  q = %f / quantized level scaling parameter", 
+	    (outfptr->Fptr)->quantize_level);
+	fits_write_history(outfptr, card2, status); 
+	fits_write_history(outfptr, card+10, status); 
+   }
+
+   tstatus = 0;
+   if (fits_read_card(outfptr, "ZDITHER0", card, &tstatus) == 0)
+   {
+        fits_delete_key(outfptr, "ZDITHER0", status);
+
+        /* rewrite the deleted keyword at the end of the header */
+        fits_write_record(outfptr, card, status);
+   }
+
+
+    ffghsp(infptr, &nkeys, &nmore, status); /* get number of keywords in image */
+
+    nmore = nmore / 36;  /* how many completely empty header blocks are there? */
+     
+     /* preserve the same number of spare header blocks in the output header */
+     
+    for (jj = 0; jj < nmore; jj++)
+       for (ii = 0; ii < 36; ii++)
+          fits_write_record(outfptr, "    ", status);
+
+    return (*status);
+}
+/*--------------------------------------------------------------------------*/
+int imcomp_copy_comp2img(fitsfile *infptr, fitsfile *outfptr, 
+                          int norec, int *status)
+/*
+    This routine copies the header keywords from the compressed input image 
+    and to the uncompressed image (in a binary table) 
+*/
+{
+    char card[FLEN_CARD];	/* a header record */
+    char *patterns[40][2];
+    char negative[] = "-";
+    int ii,jj, npat, nreq, nsp, tstatus = 0;
+    int nkeys, nmore;
+    
+    /* tile compressed image keyword translation table  */
+    /*                        INPUT      OUTPUT  */
+    /*                       01234567   01234567 */
+
+    /*  only translate these if required keywords not already written */
+    char *reqkeys[][2] = {  
+			   {"ZSIMPLE",   "SIMPLE" },  
+			   {"ZTENSION", "XTENSION"},
+			   {"ZBITPIX",   "BITPIX" },
+			   {"ZNAXIS",    "NAXIS"  },
+			   {"ZNAXISm",   "NAXISm" },
+			   {"ZEXTEND",   "EXTEND" },
+			   {"ZBLOCKED",  "BLOCKED"},
+			   {"ZPCOUNT",   "PCOUNT" },  
+			   {"ZGCOUNT",   "GCOUNT" },
+			   {"ZHECKSUM",  "CHECKSUM"},  /* restore original checksums */
+			   {"ZDATASUM",  "DATASUM"}}; 
+
+    /* other special keywords */
+    char *spkeys[][2] = {
+			   {"XTENSION", "-"      },
+			   {"BITPIX",  "-"       },
+			   {"NAXIS",   "-"       },
+			   {"NAXISm",  "-"       },
+			   {"PCOUNT",  "-"       },
+			   {"GCOUNT",  "-"       },
+			   {"TFIELDS", "-"       },
+			   {"TTYPEm",  "-"       },
+			   {"TFORMm",  "-"       },
+			   {"ZIMAGE",  "-"       },
+			   {"ZQUANTIZ", "-"      },
+			   {"ZDITHER0", "-"      },
+			   {"ZTILEm",  "-"       },
+			   {"ZCMPTYPE", "-"      },
+			   {"ZBLANK",  "-"       },
+			   {"ZNAMEm",  "-"       },
+			   {"ZVALm",   "-"       },
+
+			   {"CHECKSUM","-"       },  /* delete checksums */
+			   {"DATASUM", "-"       },
+			   {"EXTNAME", "+"       },  /* we may change this, below */
+			   {"*",       "+"      }};  
+
+
+    if (*status > 0)
+        return(*status);
+	
+    nreq = sizeof(reqkeys)/sizeof(reqkeys[0][0])/2;
+    nsp = sizeof(spkeys)/sizeof(spkeys[0][0])/2;
+
+    /* construct translation patterns */
+
+    for (ii = 0; ii < nreq; ii++) {
+        patterns[ii][0] = reqkeys[ii][0];
+	
+        if (norec) 
+            patterns[ii][1] = negative;
+        else
+            patterns[ii][1] = reqkeys[ii][1];
+    }
+    
+    for (ii = 0; ii < nsp; ii++) {
+        patterns[ii+nreq][0] = spkeys[ii][0];
+        patterns[ii+nreq][1] = spkeys[ii][1];
+    }
+
+    npat = nreq + nsp;
+    
+    /* see if the EXTNAME keyword should be copied or not */
+    fits_read_card(infptr, "EXTNAME", card, &tstatus);
+
+    if (tstatus == 0) {
+      if (!strncmp(card, "EXTNAME = 'COMPRESSED_IMAGE'", 28)) 
+        patterns[npat-2][1] = negative;
+    }
+    
+    /* translate and copy the keywords from the input file to the output */
+    fits_translate_keywords(infptr, outfptr, 1, patterns, npat,
+			    0, 0, 0, status);
+
+    ffghsp(infptr, &nkeys, &nmore, status); /* get number of keywords in image */
+
+    nmore = nmore / 36;  /* how many completely empty header blocks are there? */
+     
+    /* preserve the same number of spare header blocks in the output header */
+     
+    for (jj = 0; jj < nmore; jj++)
+       for (ii = 0; ii < 36; ii++)
+          fits_write_record(outfptr, "    ", status);
+
+
+    return (*status);
+}
+/*--------------------------------------------------------------------------*/
+int imcomp_copy_prime2img(fitsfile *infptr, fitsfile *outfptr, int *status)
+/*
+    This routine copies any unexpected keywords from the primary array
+    of the compressed input image into the header of the uncompressed image
+    (which is the primary array of the output file). 
+*/
+{
+    int  nsp;
+
+    /* keywords that will not be copied */
+    char *spkeys[][2] = {
+			   {"SIMPLE", "-"      },
+			   {"BITPIX",  "-"       },
+			   {"NAXIS",   "-"       },
+			   {"NAXISm",  "-"       },
+			   {"PCOUNT",  "-"       },
+			   {"EXTEND",  "-"       },
+			   {"GCOUNT",  "-"       },
+			   {"CHECKSUM","-"       }, 
+			   {"DATASUM", "-"       },
+			   {"EXTNAME", "-"       },
+			   {"HISTORY", "-"       },
+			   {"COMMENT", "-"       },
+			   {"*",       "+"      }};  
+
+    if (*status > 0)
+        return(*status);
+	
+    nsp = sizeof(spkeys)/sizeof(spkeys[0][0])/2;
+
+    /* translate and copy the keywords from the input file to the output */
+    fits_translate_keywords(infptr, outfptr, 1, spkeys, nsp,
+			    0, 0, 0, status);
+
+    return (*status);
+}
+/*--------------------------------------------------------------------------*/
+int imcomp_decompress_tile (fitsfile *infptr,
+          int nrow,            /* I - row of table to read and uncompress */
+          int tilelen,         /* I - number of pixels in the tile        */
+          int datatype,        /* I - datatype to be returned in 'buffer' */
+          int nullcheck,       /* I - 0 for no null checking */
+          void *nulval,        /* I - value to be used for undefined pixels */
+          void *buffer,        /* O - buffer for returned decompressed values */
+          char *bnullarray,    /* O - buffer for returned null flags */
+          int *anynul,         /* O - any null values returned?  */
+          int *status)
+
+/* This routine decompresses one tile of the image */
+{
+    int *idata = 0;
+    int tiledatatype, pixlen;          /* uncompressed integer data */
+    size_t idatalen, tilebytesize;
+    int ii, tnull;        /* value in the data which represents nulls */
+    unsigned char *cbuf; /* compressed data */
+    unsigned char charnull = 0;
+    short snull = 0;
+    int blocksize;
+    float fnulval=0;
+    float *tempfloat = 0;
+    double dnulval=0;
+    double bscale, bzero, actual_bzero, dummy = 0;    /* scaling parameters */
+    long nelem = 0, offset = 0, tilesize;      /* number of bytes */
+    int smooth, nx, ny, scale;  /* hcompress parameters */
+
+    if (*status > 0)
+       return(*status);
+
+    /* **************************************************************** */
+    /* check if this tile was cached; if so, just copy it out */
+    if (nrow == (infptr->Fptr)->tilerow && datatype == (infptr->Fptr)->tiletype ) {
+
+         memcpy(buffer, (infptr->Fptr)->tiledata, (infptr->Fptr)->tiledatasize);
+	 
+	 if (nullcheck == 2)
+             memcpy(bnullarray, (infptr->Fptr)->tilenullarray, tilelen);
+
+         *anynul = (infptr->Fptr)->tileanynull;
+         return(*status);
+    }
+
+    /* **************************************************************** */
+    /* get length of the compressed byte stream */
+    ffgdes (infptr, (infptr->Fptr)->cn_compressed, nrow, &nelem, &offset, 
+            status);
+
+    /* EOF error here indicates that this tile has not yet been written */
+    if (*status == END_OF_FILE)
+           return(*status = NO_COMPRESSED_TILE);
+      
+    /* **************************************************************** */
+    if (nelem == 0)  /* special case: tile was not compressed normally */
+    {
+        if ((infptr->Fptr)->cn_uncompressed >= 1 ) {
+
+	    /* This option of writing the uncompressed floating point data */
+	    /* to the tile compressed file was used until about May 2011. */
+	    /* This was replaced by the more efficient option of gzipping the */
+	    /* floating point data before writing it to the tile-compressed file */
+	    
+            /* no compressed data, so simply read the uncompressed data */
+            /* directly from the UNCOMPRESSED_DATA column */   
+            ffgdes (infptr, (infptr->Fptr)->cn_uncompressed, nrow, &nelem,
+               &offset, status);
+
+            if (nelem == 0 && offset == 0)  /* this should never happen */
+	        return (*status = NO_COMPRESSED_TILE);
+
+            if (nullcheck <= 1) { /* set any null values in the array = nulval */
+                fits_read_col(infptr, datatype, (infptr->Fptr)->cn_uncompressed,
+                  nrow, 1, nelem, nulval, buffer, anynul, status);
+            } else  { /* set the bnullarray = 1 for any null values in the array */
+                fits_read_colnull(infptr, datatype, (infptr->Fptr)->cn_uncompressed,
+                  nrow, 1, nelem, buffer, bnullarray, anynul, status);
+            }
+        } else if ((infptr->Fptr)->cn_gzip_data >= 1) {
+
+            /* This is the newer option, that was introduced in May 2011 */
+            /* floating point data was not quantized,  so read the losslessly */
+	    /* compressed data from the GZIP_COMPRESSED_DATA column */   
+
+            ffgdes (infptr, (infptr->Fptr)->cn_gzip_data, nrow, &nelem,
+               &offset, status);
+
+            if (nelem == 0 && offset == 0) /* this should never happen */
+	        return (*status = NO_COMPRESSED_TILE);
+
+	    /* allocate memory for the compressed tile of data */
+            cbuf = (unsigned char *) malloc (nelem);  
+            if (cbuf == NULL) {
+	        ffpmsg("error allocating memory for gzipped tile (imcomp_decompress_tile)");
+	        return (*status = MEMORY_ALLOCATION);
+            }
+
+            /* read array of compressed bytes */
+            if (fits_read_col(infptr, TBYTE, (infptr->Fptr)->cn_gzip_data, nrow,
+                 1, nelem, &charnull, cbuf, NULL, status) > 0) {
+                ffpmsg("error reading compressed byte stream from binary table");
+	        free (cbuf);
+                return (*status);
+            }
+
+            /* size of the returned (uncompressed) data buffer, in bytes */
+            if ((infptr->Fptr)->zbitpix == FLOAT_IMG) {
+	         idatalen = tilelen * sizeof(float);
+            } else if ((infptr->Fptr)->zbitpix == DOUBLE_IMG) {
+	         idatalen = tilelen * sizeof(double);
+            } else {
+                /* this should never happen! */
+                ffpmsg("incompatible data type in gzipped floating-point tile-compressed image");
+                free (cbuf);
+                return (*status = DATA_DECOMPRESSION_ERR);
+            }
+
+            if (datatype == TDOUBLE && (infptr->Fptr)->zbitpix == FLOAT_IMG) {  
+                /*  have to allocat a temporary buffer for the uncompressed data in the */
+                /*  case where a gzipped "float" tile is returned as a "double" array   */
+                tempfloat = (float*) malloc (idatalen); 
+
+                if (tempfloat == NULL) {
+	            ffpmsg("Memory allocation failure for tempfloat. (imcomp_decompress_tile)");
+                    free (cbuf);
+	            return (*status = MEMORY_ALLOCATION);
+                }
+
+                /* uncompress the data into temp buffer */
+                if (uncompress2mem_from_mem ((char *)cbuf, nelem,
+                     (char **) &tempfloat, &idatalen, NULL, &tilebytesize, status)) {
+                    ffpmsg("failed to gunzip the image tile");
+                    free (tempfloat);
+                    free (cbuf);
+                    return (*status);
+                }
+            } else {
+
+                /* uncompress the data directly into the output buffer in all other cases */
+                if (uncompress2mem_from_mem ((char *)cbuf, nelem,
+                  (char **) &buffer, &idatalen, NULL, &tilebytesize, status)) {
+                    ffpmsg("failed to gunzip the image tile");
+                    free (cbuf);
+                    return (*status);
+                }
+            }
+
+            free(cbuf);
+
+            /* do byte swapping and null value substitution for the tile of pixels */
+            if (tilebytesize == 4 * tilelen) {  /* float pixels */
+
+#if BYTESWAPPED
+                if (tempfloat)
+                    ffswap4((int *) tempfloat, tilelen);
+                else
+                    ffswap4((int *) buffer, tilelen);
+#endif
+               if (datatype == TFLOAT) {
+                  if (nulval) {
+		    fnulval = *(float *) nulval;
+  		  }
+
+                  fffr4r4((float *) buffer, (long) tilelen, 1., 0., nullcheck,   
+                        fnulval, bnullarray, anynul,
+                        (float *) buffer, status);
+                } else if (datatype == TDOUBLE) {
+                  if (nulval) {
+		    dnulval = *(double *) nulval;
+		  }
+
+                  /* note that the R*4 data are in the tempfloat array in this case */
+                  fffr4r8((float *) tempfloat, (long) tilelen, 1., 0., nullcheck,   
+                   dnulval, bnullarray, anynul,
+                    (double *) buffer, status);            
+                  free(tempfloat);
+
+                } else {
+                  ffpmsg("implicit data type conversion is not supported for gzipped image tiles");
+                  return (*status = DATA_DECOMPRESSION_ERR);
+                }
+            } else if (tilebytesize == 8 * tilelen) { /* double pixels */
+
+#if BYTESWAPPED
+                ffswap8((double *) buffer, tilelen);
+#endif
+                if (datatype == TFLOAT) {
+                  if (nulval) {
+		    fnulval = *(float *) nulval;
+  		  }
+
+                  fffr8r4((double *) buffer, (long) tilelen, 1., 0., nullcheck,   
+                        fnulval, bnullarray, anynul,
+                        (float *) buffer, status);
+                } else if (datatype == TDOUBLE) {
+                  if (nulval) {
+		    dnulval = *(double *) nulval;
+		  }
+
+                  fffr8r8((double *) buffer, (long) tilelen, 1., 0., nullcheck,   
+                   dnulval, bnullarray, anynul,
+                    (double *) buffer, status);            
+                } else {
+                  ffpmsg("implicit data type conversion is not supported in tile-compressed images");
+                  return (*status = DATA_DECOMPRESSION_ERR);
+                }
+	    } else {
+                ffpmsg("error: uncompressed tile has wrong size");
+                return (*status = DATA_DECOMPRESSION_ERR);
+            }
+
+          /* end of special case of losslessly gzipping a floating-point image tile */
+        } else {  /* this should never happen */
+	   *status = NO_COMPRESSED_TILE;
+        }
+
+        return(*status);
+    }
+   
+    /* **************************************************************** */
+    /* deal with the normal case of a compressed tile of pixels */
+    if (nullcheck == 2)  {
+        for (ii = 0; ii < tilelen; ii++)  /* initialize the null flage array */
+            bnullarray[ii] = 0;
+    }
+
+    if (anynul)
+       *anynul = 0;
+
+    /* get linear scaling and offset values, if they exist */
+    actual_bzero = (infptr->Fptr)->cn_actual_bzero;
+    if ((infptr->Fptr)->cn_zscale == 0) {
+         /* set default scaling, if scaling is not defined */
+         bscale = 1.;
+         bzero = 0.;
+    } else if ((infptr->Fptr)->cn_zscale == -1) {
+        bscale = (infptr->Fptr)->zscale;
+        bzero  = (infptr->Fptr)->zzero;
+    } else {
+        /* read the linear scale and offset values for this row */
+	ffgcvd (infptr, (infptr->Fptr)->cn_zscale, nrow, 1, 1, 0.,
+				&bscale, NULL, status);
+	ffgcvd (infptr, (infptr->Fptr)->cn_zzero, nrow, 1, 1, 0.,
+				&bzero, NULL, status);
+        if (*status > 0)
+        {
+          ffpmsg("error reading scaling factor and offset for compressed tile");
+          return (*status);
+        }
+
+        /* test if floating-point FITS image also has non-default BSCALE and  */
+	/* BZERO keywords.  If so, we have to combine the 2 linear scaling factors. */
+	
+	if ( ((infptr->Fptr)->zbitpix == FLOAT_IMG || 
+	      (infptr->Fptr)->zbitpix == DOUBLE_IMG )
+	    &&  
+	      ((infptr->Fptr)->cn_bscale != 1.0 ||
+	       (infptr->Fptr)->cn_bzero  != 0.0 )    ) 
+	    {
+	       bscale = bscale * (infptr->Fptr)->cn_bscale;
+	       bzero  = bzero  * (infptr->Fptr)->cn_bscale + (infptr->Fptr)->cn_bzero;
+	    }
+    }
+
+    if (bscale == 1.0 && bzero == 0.0 ) {
+      /* if no other scaling has been specified, try using the values
+         given by the BSCALE and BZERO keywords, if any */
+
+        bscale = (infptr->Fptr)->cn_bscale;
+        bzero  = (infptr->Fptr)->cn_bzero;
+    }
+
+    /* ************************************************************* */
+    /* get the value used to represent nulls in the int array */
+    if ((infptr->Fptr)->cn_zblank == 0) {
+        nullcheck = 0;  /* no null value; don't check for nulls */
+    } else if ((infptr->Fptr)->cn_zblank == -1) {
+        tnull = (infptr->Fptr)->zblank;  /* use the the ZBLANK keyword */
+    } else {
+        /* read the null value for this row */
+	ffgcvk (infptr, (infptr->Fptr)->cn_zblank, nrow, 1, 1, 0,
+				&tnull, NULL, status);
+        if (*status > 0) {
+            ffpmsg("error reading null value for compressed tile");
+            return (*status);
+        }
+    }
+
+    /* ************************************************************* */
+    /* allocate memory for the uncompressed array of tile integers */
+    /* The size depends on the datatype and the compression type. */
+    
+    if ((infptr->Fptr)->compress_type == HCOMPRESS_1 &&
+          ((infptr->Fptr)->zbitpix != BYTE_IMG &&
+	   (infptr->Fptr)->zbitpix != SHORT_IMG) ) {
+
+           idatalen = tilelen * sizeof(LONGLONG);  /* 8 bytes per pixel */
+
+    } else if ( (infptr->Fptr)->compress_type == RICE_1 &&
+               (infptr->Fptr)->zbitpix == BYTE_IMG && 
+	       (infptr->Fptr)->rice_bytepix == 1) {
+
+           idatalen = tilelen * sizeof(char); /* 1 byte per pixel */
+    } else if ( ( (infptr->Fptr)->compress_type == GZIP_1  ||
+                  (infptr->Fptr)->compress_type == GZIP_2  ||
+                  (infptr->Fptr)->compress_type == BZIP2_1 ) &&
+               (infptr->Fptr)->zbitpix == BYTE_IMG ) {
+
+           idatalen = tilelen * sizeof(char); /* 1 byte per pixel */
+    } else if ( (infptr->Fptr)->compress_type == RICE_1 &&
+               (infptr->Fptr)->zbitpix == SHORT_IMG && 
+	       (infptr->Fptr)->rice_bytepix == 2) {
+
+           idatalen = tilelen * sizeof(short); /* 2 bytes per pixel */
+    } else if ( ( (infptr->Fptr)->compress_type == GZIP_1  ||
+                  (infptr->Fptr)->compress_type == GZIP_2  ||
+                  (infptr->Fptr)->compress_type == BZIP2_1 )  &&
+               (infptr->Fptr)->zbitpix == SHORT_IMG ) {
+
+           idatalen = tilelen * sizeof(short); /* 2 bytes per pixel */
+    } else if ( ( (infptr->Fptr)->compress_type == GZIP_1  ||
+                  (infptr->Fptr)->compress_type == GZIP_2  ||
+                  (infptr->Fptr)->compress_type == BZIP2_1 ) &&
+               (infptr->Fptr)->zbitpix == DOUBLE_IMG ) {
+
+           idatalen = tilelen * sizeof(double); /* 8 bytes per pixel  */
+    } else {
+           idatalen = tilelen * sizeof(int);  /* all other cases have int pixels */
+    }
+
+    idata = (int*) malloc (idatalen); 
+    if (idata == NULL) {
+	    ffpmsg("Memory allocation failure for idata. (imcomp_decompress_tile)");
+	    return (*status = MEMORY_ALLOCATION);
+    }
+
+    /* ************************************************************* */
+    /* allocate memory for the compressed bytes */
+
+    if ((infptr->Fptr)->compress_type == PLIO_1) {
+        cbuf = (unsigned char *) malloc (nelem * sizeof (short));
+    } else {
+        cbuf = (unsigned char *) malloc (nelem);
+    }
+    if (cbuf == NULL) {
+	ffpmsg("Out of memory for cbuf. (imcomp_decompress_tile)");
+        free(idata);
+	return (*status = MEMORY_ALLOCATION);
+    }
+    
+    /* ************************************************************* */
+    /* read the compressed bytes from the FITS file */
+
+    if ((infptr->Fptr)->compress_type == PLIO_1) {
+        fits_read_col(infptr, TSHORT, (infptr->Fptr)->cn_compressed, nrow,
+             1, nelem, &snull, (short *) cbuf, NULL, status);
+    } else {
+       fits_read_col(infptr, TBYTE, (infptr->Fptr)->cn_compressed, nrow,
+             1, nelem, &charnull, cbuf, NULL, status);
+    }
+
+    if (*status > 0) {
+        ffpmsg("error reading compressed byte stream from binary table");
+	free (cbuf);
+        free(idata);
+        return (*status);
+    }
+
+    /* ************************************************************* */
+    /*  call the algorithm-specific code to uncompress the tile */
+
+    if ((infptr->Fptr)->compress_type == RICE_1) {
+
+        blocksize = (infptr->Fptr)->rice_blocksize;
+
+        if ((infptr->Fptr)->rice_bytepix == 1 ) {
+            *status = fits_rdecomp_byte (cbuf, nelem, (unsigned char *)idata,
+                        tilelen, blocksize);
+            tiledatatype = TBYTE;
+        } else if ((infptr->Fptr)->rice_bytepix == 2 ) {
+            *status = fits_rdecomp_short (cbuf, nelem, (unsigned short *)idata,
+                        tilelen, blocksize);
+            tiledatatype = TSHORT;
+        } else {
+            *status = fits_rdecomp (cbuf, nelem, (unsigned int *)idata,
+                         tilelen, blocksize);
+            tiledatatype = TINT;
+        }
+
+    /* ************************************************************* */
+    } else if ((infptr->Fptr)->compress_type == HCOMPRESS_1)  {
+
+        smooth = (infptr->Fptr)->hcomp_smooth;
+
+        if ( ((infptr->Fptr)->zbitpix == BYTE_IMG || (infptr->Fptr)->zbitpix == SHORT_IMG)) {
+            *status = fits_hdecompress(cbuf, smooth, idata, &nx, &ny,
+	        &scale, status);
+        } else {  /* zbitpix = LONG_IMG (32) */
+            /* idata must have been allocated twice as large for this to work */
+            *status = fits_hdecompress64(cbuf, smooth, (LONGLONG *) idata, &nx, &ny,
+	        &scale, status);
+        }       
+
+        tiledatatype = TINT;
+
+    /* ************************************************************* */
+    } else if ((infptr->Fptr)->compress_type == PLIO_1) {
+
+        pl_l2pi ((short *) cbuf, 1, idata, tilelen);  /* uncompress the data */
+        tiledatatype = TINT;
+
+    /* ************************************************************* */
+    } else if ( ((infptr->Fptr)->compress_type == GZIP_1) ||
+                ((infptr->Fptr)->compress_type == GZIP_2) ) {
+
+        uncompress2mem_from_mem ((char *)cbuf, nelem,
+             (char **) &idata, &idatalen, realloc, &tilebytesize, status);
+
+        /* determine the data type of the uncompressed array, and */
+	/*  do byte unshuffling and unswapping if needed */
+	if (tilebytesize == (size_t) (tilelen * 2)) {
+	    /* this is a short I*2 array */
+            tiledatatype = TSHORT;
+
+            if ( (infptr->Fptr)->compress_type == GZIP_2 )
+		    fits_unshuffle_2bytes((char *) idata, tilelen, status);
+
+#if BYTESWAPPED
+            ffswap2((short *) idata, tilelen);
+#endif
+
+	} else if (tilebytesize == (size_t) (tilelen * 4)) {
+	    /* this is a int I*4 array (or maybe R*4) */
+            tiledatatype = TINT;
+
+            if ( (infptr->Fptr)->compress_type == GZIP_2 )
+		    fits_unshuffle_4bytes((char *) idata, tilelen, status);
+
+#if BYTESWAPPED
+            ffswap4(idata, tilelen);
+#endif
+
+	} else if (tilebytesize == (size_t) (tilelen * 8)) {
+	    /* this is a R*8 double array */
+            tiledatatype = TDOUBLE;
+
+            if ( (infptr->Fptr)->compress_type == GZIP_2 )
+		    fits_unshuffle_8bytes((char *) idata, tilelen, status);
+#if BYTESWAPPED
+            ffswap8((double *) idata, tilelen);
+#endif
+
+        } else if (tilebytesize == (size_t) tilelen) {
+	    
+	    /* this is an unsigned char I*1 array */
+            tiledatatype = TBYTE;
+
+        } else {
+            ffpmsg("error: uncompressed tile has wrong size");
+            free(idata);
+            return (*status = DATA_DECOMPRESSION_ERR);
+        }
+
+    /* ************************************************************* */
+    } else if ((infptr->Fptr)->compress_type == BZIP2_1) {
+
+/*  BZIP2 is not supported in the public release; this is only for test purposes 
+
+        if (BZ2_bzBuffToBuffDecompress ((char *) idata, &idatalen, 
+		(char *)cbuf, (unsigned int) nelem, 0, 0) )
+*/
+        {
+            ffpmsg("bzip2 decompression error");
+            free(idata);
+            free (cbuf);
+            return (*status = DATA_DECOMPRESSION_ERR);
+        }
+
+        if ((infptr->Fptr)->zbitpix == BYTE_IMG) {
+	     tiledatatype = TBYTE;
+        } else if ((infptr->Fptr)->zbitpix == SHORT_IMG) {
+  	     tiledatatype = TSHORT;
+#if BYTESWAPPED
+            ffswap2((short *) idata, tilelen);
+#endif
+	} else {
+  	     tiledatatype = TINT;
+#if BYTESWAPPED
+            ffswap4(idata, tilelen);
+#endif
+	}
+
+    /* ************************************************************* */
+    } else {
+        ffpmsg("unknown compression algorithm");
+        free(idata);
+        return (*status = DATA_DECOMPRESSION_ERR);
+    }
+
+    free(cbuf);
+    if (*status)  {  /* error uncompressing the tile */
+            free(idata);
+            return (*status);
+    }
+
+    /* ************************************************************* */
+    /* copy the uncompressed tile data to the output buffer, doing */
+    /* null checking, datatype conversion and linear scaling, if necessary */
+
+    if (nulval == 0)
+         nulval = &dummy;  /* set address to dummy value */
+
+    if (datatype == TSHORT)
+    {
+        pixlen = sizeof(short);
+
+	if ((infptr->Fptr)->quantize_level == NO_QUANTIZE) {
+	 /* the floating point pixels were losselessly compressed with GZIP */
+	 /* Just have to copy the values to the output array */
+	 
+          if (tiledatatype == TINT) {
+              fffr4i2((float *) idata, tilelen, bscale, bzero, nullcheck,   
+                *(short *) nulval, bnullarray, anynul,
+                (short *) buffer, status);
+          } else {
+              fffr8i2((double *) idata, tilelen, bscale, bzero, nullcheck,   
+                *(short *) nulval, bnullarray, anynul,
+                (short *) buffer, status);
+          }
+        } else if (tiledatatype == TINT)
+          if ((infptr->Fptr)->compress_type == PLIO_1 &&
+	    bzero == 0. && actual_bzero == 32768.) {
+	    /* special case where unsigned 16-bit integers have been */
+	    /* offset by +32768 when using PLIO */
+            fffi4i2(idata, tilelen, bscale, -32768., nullcheck, tnull,
+             *(short *) nulval, bnullarray, anynul,
+            (short *) buffer, status);
+          } else {
+            fffi4i2(idata, tilelen, bscale, bzero, nullcheck, tnull,
+             *(short *) nulval, bnullarray, anynul,
+            (short *) buffer, status);
+          }
+        else if (tiledatatype == TSHORT)
+          fffi2i2((short *)idata, tilelen, bscale, bzero, nullcheck, (short) tnull,
+           *(short *) nulval, bnullarray, anynul,
+          (short *) buffer, status);
+        else if (tiledatatype == TBYTE)
+          fffi1i2((unsigned char *)idata, tilelen, bscale, bzero, nullcheck, (unsigned char) tnull,
+           *(short *) nulval, bnullarray, anynul,
+          (short *) buffer, status);
+    }
+    else if (datatype == TINT)
+    {
+        pixlen = sizeof(int);
+
+	if ((infptr->Fptr)->quantize_level == NO_QUANTIZE) {
+	 /* the floating point pixels were losselessly compressed with GZIP */
+	 /* Just have to copy the values to the output array */
+	 
+          if (tiledatatype == TINT) {
+              fffr4int((float *) idata, tilelen, bscale, bzero, nullcheck,   
+                *(int *) nulval, bnullarray, anynul,
+                (int *) buffer, status);
+          } else {
+              fffr8int((double *) idata, tilelen, bscale, bzero, nullcheck,   
+                *(int *) nulval, bnullarray, anynul,
+                (int *) buffer, status);
+          }
+        } else if (tiledatatype == TINT)
+          fffi4int(idata, (long) tilelen, bscale, bzero, nullcheck, tnull,
+           *(int *) nulval, bnullarray, anynul,
+           (int *) buffer, status);
+        else if (tiledatatype == TSHORT)
+          fffi2int((short *)idata, tilelen, bscale, bzero, nullcheck, (short) tnull,
+           *(int *) nulval, bnullarray, anynul,
+           (int *) buffer, status);
+        else if (tiledatatype == TBYTE)
+          fffi1int((unsigned char *)idata, tilelen, bscale, bzero, nullcheck, (unsigned char) tnull,
+           *(int *) nulval, bnullarray, anynul,
+           (int *) buffer, status);
+    }
+    else if (datatype == TLONG)
+    {
+        pixlen = sizeof(long);
+
+	if ((infptr->Fptr)->quantize_level == NO_QUANTIZE) {
+	 /* the floating point pixels were losselessly compressed with GZIP */
+	 /* Just have to copy the values to the output array */
+	 
+          if (tiledatatype == TINT) {
+              fffr4i4((float *) idata, tilelen, bscale, bzero, nullcheck,   
+                *(long *) nulval, bnullarray, anynul,
+                (long *) buffer, status);
+          } else {
+              fffr8i4((double *) idata, tilelen, bscale, bzero, nullcheck,   
+                *(long *) nulval, bnullarray, anynul,
+                (long *) buffer, status);
+          }
+        } else if (tiledatatype == TINT)
+          fffi4i4(idata, tilelen, bscale, bzero, nullcheck, tnull,
+           *(long *) nulval, bnullarray, anynul,
+            (long *) buffer, status);
+        else if (tiledatatype == TSHORT)
+          fffi2i4((short *)idata, tilelen, bscale, bzero, nullcheck, (short) tnull,
+           *(long *) nulval, bnullarray, anynul,
+            (long *) buffer, status);
+        else if (tiledatatype == TBYTE)
+          fffi1i4((unsigned char *)idata, tilelen, bscale, bzero, nullcheck, (unsigned char) tnull,
+           *(long *) nulval, bnullarray, anynul,
+            (long *) buffer, status);
+    }
+    else if (datatype == TFLOAT)
+    {
+        pixlen = sizeof(float);
+        if (nulval) {
+	      fnulval = *(float *) nulval;
+	}
+ 
+	if ((infptr->Fptr)->quantize_level == NO_QUANTIZE) {
+	 /* the floating point pixels were losselessly compressed with GZIP */
+	 /* Just have to copy the values to the output array */
+	 
+          if (tiledatatype == TINT) {
+              fffr4r4((float *) idata, tilelen, bscale, bzero, nullcheck,   
+                fnulval, bnullarray, anynul,
+                (float *) buffer, status);
+          } else {
+              fffr8r4((double *) idata, tilelen, bscale, bzero, nullcheck,   
+                fnulval, bnullarray, anynul,
+                (float *) buffer, status);
+          }
+	
+        } else if ((infptr->Fptr)->quantize_dither == SUBTRACTIVE_DITHER_1) {
+
+         /* use the new dithering algorithm (introduced in July 2009) */
+
+         if (tiledatatype == TINT)
+          unquantize_i4r4(nrow + (infptr->Fptr)->dither_offset - 1, idata, 
+	   tilelen, bscale, bzero, nullcheck, tnull,
+           fnulval, bnullarray, anynul,
+            (float *) buffer, status);
+         else if (tiledatatype == TSHORT)
+          unquantize_i2r4(nrow + (infptr->Fptr)->dither_offset - 1, (short *)idata, 
+	   tilelen, bscale, bzero, nullcheck, (short) tnull,
+           fnulval, bnullarray, anynul,
+            (float *) buffer, status);
+         else if (tiledatatype == TBYTE)
+          unquantize_i1r4(nrow + (infptr->Fptr)->dither_offset - 1, (unsigned char *)idata, 
+	   tilelen, bscale, bzero, nullcheck, (unsigned char) tnull,
+           fnulval, bnullarray, anynul,
+            (float *) buffer, status);
+
+        } else {  /* use the old "round to nearest level" quantization algorithm */
+
+         if (tiledatatype == TINT)
+          fffi4r4(idata, tilelen, bscale, bzero, nullcheck, tnull,  
+           fnulval, bnullarray, anynul,
+            (float *) buffer, status);
+         else if (tiledatatype == TSHORT)
+          fffi2r4((short *)idata, tilelen, bscale, bzero, nullcheck, (short) tnull,  
+           fnulval, bnullarray, anynul,
+            (float *) buffer, status);
+         else if (tiledatatype == TBYTE)
+          fffi1r4((unsigned char *)idata, tilelen, bscale, bzero, nullcheck, (unsigned char) tnull,
+           fnulval, bnullarray, anynul,
+            (float *) buffer, status);
+	}
+    }
+    else if (datatype == TDOUBLE)
+    {
+        pixlen = sizeof(double);
+        if (nulval) {
+	     dnulval = *(double *) nulval;
+	}
+
+	if ((infptr->Fptr)->quantize_level == NO_QUANTIZE) {
+	 /* the floating point pixels were losselessly compressed with GZIP */
+	 /* Just have to copy the values to the output array */
+
+          if (tiledatatype == TINT) {
+              fffr4r8((float *) idata, tilelen, bscale, bzero, nullcheck,   
+                dnulval, bnullarray, anynul,
+                (double *) buffer, status);
+          } else {
+              fffr8r8((double *) idata, tilelen, bscale, bzero, nullcheck,   
+                dnulval, bnullarray, anynul,
+                (double *) buffer, status);
+          }
+	
+	} else if ((infptr->Fptr)->quantize_dither == SUBTRACTIVE_DITHER_1) {
+
+         /* use the new dithering algorithm (introduced in July 2009) */
+         if (tiledatatype == TINT)
+          unquantize_i4r8(nrow + (infptr->Fptr)->dither_offset - 1, idata,
+	   tilelen, bscale, bzero, nullcheck, tnull,
+           dnulval, bnullarray, anynul,
+            (double *) buffer, status);
+         else if (tiledatatype == TSHORT)
+          unquantize_i2r8(nrow + (infptr->Fptr)->dither_offset - 1, (short *)idata,
+	   tilelen, bscale, bzero, nullcheck, (short) tnull,
+           dnulval, bnullarray, anynul,
+            (double *) buffer, status);
+         else if (tiledatatype == TBYTE)
+          unquantize_i1r8(nrow + (infptr->Fptr)->dither_offset - 1, (unsigned char *)idata,
+	   tilelen, bscale, bzero, nullcheck, (unsigned char) tnull,
+           dnulval, bnullarray, anynul,
+            (double *) buffer, status);
+
+        } else {  /* use the old "round to nearest level" quantization algorithm */
+
+         if (tiledatatype == TINT)
+          fffi4r8(idata, tilelen, bscale, bzero, nullcheck, tnull,
+           dnulval, bnullarray, anynul,
+            (double *) buffer, status);
+         else if (tiledatatype == TSHORT)
+          fffi2r8((short *)idata, tilelen, bscale, bzero, nullcheck, (short) tnull,
+           dnulval, bnullarray, anynul,
+            (double *) buffer, status);
+         else if (tiledatatype == TBYTE)
+          fffi1r8((unsigned char *)idata, tilelen, bscale, bzero, nullcheck, (unsigned char) tnull,
+           dnulval, bnullarray, anynul,
+            (double *) buffer, status);
+	}
+    }
+    else if (datatype == TBYTE)
+    {
+        pixlen = sizeof(char);
+        if (tiledatatype == TINT)
+          fffi4i1(idata, tilelen, bscale, bzero, nullcheck, tnull,
+           *(unsigned char *) nulval, bnullarray, anynul,
+            (unsigned char *) buffer, status);
+        else if (tiledatatype == TSHORT)
+          fffi2i1((short *)idata, tilelen, bscale, bzero, nullcheck, (short) tnull,
+           *(unsigned char *) nulval, bnullarray, anynul,
+            (unsigned char *) buffer, status);
+        else if (tiledatatype == TBYTE)
+          fffi1i1((unsigned char *)idata, tilelen, bscale, bzero, nullcheck, (unsigned char) tnull,
+           *(unsigned char *) nulval, bnullarray, anynul,
+            (unsigned char *) buffer, status);
+    }
+    else if (datatype == TSBYTE)
+    {
+        pixlen = sizeof(char);
+        if (tiledatatype == TINT)
+          fffi4s1(idata, tilelen, bscale, bzero, nullcheck, tnull,
+           *(signed char *) nulval, bnullarray, anynul,
+            (signed char *) buffer, status);
+        else if (tiledatatype == TSHORT)
+          fffi2s1((short *)idata, tilelen, bscale, bzero, nullcheck, (short) tnull,
+           *(signed char *) nulval, bnullarray, anynul,
+            (signed char *) buffer, status);
+        else if (tiledatatype == TBYTE)
+          fffi1s1((unsigned char *)idata, tilelen, bscale, bzero, nullcheck, (unsigned char) tnull,
+           *(signed char *) nulval, bnullarray, anynul,
+            (signed char *) buffer, status);
+    }
+    else if (datatype == TUSHORT)
+    {
+        pixlen = sizeof(short);
+
+	if ((infptr->Fptr)->quantize_level == NO_QUANTIZE) {
+	 /* the floating point pixels were losselessly compressed with GZIP */
+	 /* Just have to copy the values to the output array */
+	 
+          if (tiledatatype == TINT) {
+              fffr4u2((float *) idata, tilelen, bscale, bzero, nullcheck,   
+                *(unsigned short *) nulval, bnullarray, anynul,
+                (unsigned short *) buffer, status);
+          } else {
+              fffr8u2((double *) idata, tilelen, bscale, bzero, nullcheck,   
+                *(unsigned short *) nulval, bnullarray, anynul,
+                (unsigned short *) buffer, status);
+          }
+        } else if (tiledatatype == TINT)
+          fffi4u2(idata, tilelen, bscale, bzero, nullcheck, tnull,
+           *(unsigned short *) nulval, bnullarray, anynul,
+            (unsigned short *) buffer, status);
+        else if (tiledatatype == TSHORT)
+          fffi2u2((short *)idata, tilelen, bscale, bzero, nullcheck, (short) tnull,
+           *(unsigned short *) nulval, bnullarray, anynul,
+            (unsigned short *) buffer, status);
+        else if (tiledatatype == TBYTE)
+          fffi1u2((unsigned char *)idata, tilelen, bscale, bzero, nullcheck, (unsigned char) tnull,
+           *(unsigned short *) nulval, bnullarray, anynul,
+            (unsigned short *) buffer, status);
+    }
+    else if (datatype == TUINT)
+    {
+        pixlen = sizeof(int);
+
+	if ((infptr->Fptr)->quantize_level == NO_QUANTIZE) {
+	 /* the floating point pixels were losselessly compressed with GZIP */
+	 /* Just have to copy the values to the output array */
+	 
+          if (tiledatatype == TINT) {
+              fffr4uint((float *) idata, tilelen, bscale, bzero, nullcheck,   
+                *(unsigned int *) nulval, bnullarray, anynul,
+                (unsigned int *) buffer, status);
+          } else {
+              fffr8uint((double *) idata, tilelen, bscale, bzero, nullcheck,   
+                *(unsigned int *) nulval, bnullarray, anynul,
+                (unsigned int *) buffer, status);
+          }
+        } else         if (tiledatatype == TINT)
+          fffi4uint(idata, tilelen, bscale, bzero, nullcheck, tnull,
+           *(unsigned int *) nulval, bnullarray, anynul,
+            (unsigned int *) buffer, status);
+        else if (tiledatatype == TSHORT)
+          fffi2uint((short *)idata, tilelen, bscale, bzero, nullcheck, (short) tnull,
+           *(unsigned int *) nulval, bnullarray, anynul,
+            (unsigned int *) buffer, status);
+        else if (tiledatatype == TBYTE)
+          fffi1uint((unsigned char *)idata, tilelen, bscale, bzero, nullcheck, (unsigned char) tnull,
+           *(unsigned int *) nulval, bnullarray, anynul,
+            (unsigned int *) buffer, status);
+    }
+    else if (datatype == TULONG)
+    {
+        pixlen = sizeof(long);
+
+	if ((infptr->Fptr)->quantize_level == NO_QUANTIZE) {
+	 /* the floating point pixels were losselessly compressed with GZIP */
+	 /* Just have to copy the values to the output array */
+	 
+          if (tiledatatype == TINT) {
+              fffr4u4((float *) idata, tilelen, bscale, bzero, nullcheck,   
+                *(unsigned long *) nulval, bnullarray, anynul,
+                (unsigned long *) buffer, status);
+          } else {
+              fffr8u4((double *) idata, tilelen, bscale, bzero, nullcheck,   
+                *(unsigned long *) nulval, bnullarray, anynul,
+                (unsigned long *) buffer, status);
+          }
+        } else if (tiledatatype == TINT)
+          fffi4u4(idata, tilelen, bscale, bzero, nullcheck, tnull,
+           *(unsigned long *) nulval, bnullarray, anynul, 
+            (unsigned long *) buffer, status);
+        else if (tiledatatype == TSHORT)
+          fffi2u4((short *)idata, tilelen, bscale, bzero, nullcheck, (short) tnull,
+           *(unsigned long *) nulval, bnullarray, anynul, 
+            (unsigned long *) buffer, status);
+        else if (tiledatatype == TBYTE)
+          fffi1u4((unsigned char *)idata, tilelen, bscale, bzero, nullcheck, (unsigned char) tnull,
+           *(unsigned long *) nulval, bnullarray, anynul, 
+            (unsigned long *) buffer, status);
+    }
+    else
+         *status = BAD_DATATYPE;
+
+    free(idata);  /* don't need the uncompressed tile any more */
+
+    /* **************************************************************** */
+    /* cache the tile, in case the application wants it again  */
+
+    /*   Don't cache the tile if tile is a single row of the image; 
+         it is less likely that the cache will be used in this cases,
+	 so it is not worth the time and the memory overheads.
+    */
+    if ((infptr->Fptr)->znaxis[0]   != (infptr->Fptr)->tilesize[0] ||
+        (infptr->Fptr)->tilesize[1] != 1 )
+    {
+      tilesize = pixlen * tilelen;
+
+      /* check that tile size/type has not changed */
+      if (tilesize != (infptr->Fptr)->tiledatasize ||
+        datatype != (infptr->Fptr)->tiletype )  {
+
+        if ((infptr->Fptr)->tiledata) {
+            free((infptr->Fptr)->tiledata);	    
+        }
+	
+        (infptr->Fptr)->tiledata = 0;
+
+        if ((infptr->Fptr)->tilenullarray) {
+            free((infptr->Fptr)->tilenullarray);
+        }
+	
+        (infptr->Fptr)->tilenullarray = 0;
+        (infptr->Fptr)->tilerow = 0;
+        (infptr->Fptr)->tiledatasize = 0;
+        (infptr->Fptr)->tiletype = 0;
+
+        /* allocate new array(s) */
+	(infptr->Fptr)->tiledata = malloc(tilesize);
+	if ((infptr->Fptr)->tiledata == 0)
+	   return (*status);
+
+        if (nullcheck == 2) {  /* also need array of null pixel flags */
+	    (infptr->Fptr)->tilenullarray = malloc(tilelen);
+	    if ((infptr->Fptr)->tilenullarray == 0)
+	        return (*status);
+        }
+
+        (infptr->Fptr)->tiledatasize = tilesize;
+        (infptr->Fptr)->tiletype = datatype;
+      }
+
+      /* copy the tile array(s) into cache buffer */
+      memcpy((infptr->Fptr)->tiledata, buffer, tilesize);
+
+      if (nullcheck == 2) {
+	    if ((infptr->Fptr)->tilenullarray == 0)  {
+       	      (infptr->Fptr)->tilenullarray = malloc(tilelen);
+            }
+            memcpy((infptr->Fptr)->tilenullarray, bnullarray, tilelen);
+      }
+
+      (infptr->Fptr)->tilerow = nrow;
+      (infptr->Fptr)->tileanynull = *anynul;
+    }
+
+    return (*status);
+}
+/*--------------------------------------------------------------------------*/
+int imcomp_copy_overlap (
+    char *tile,         /* I - multi dimensional array of tile pixels */
+    int pixlen,         /* I - number of bytes in each tile or image pixel */
+    int ndim,           /* I - number of dimension in the tile and image */
+    long *tfpixel,      /* I - first pixel number in each dim. of the tile */
+    long *tlpixel,      /* I - last pixel number in each dim. of the tile */
+    char *bnullarray,   /* I - array of null flags; used if nullcheck = 2 */
+    char *image,        /* O - multi dimensional output image */
+    long *fpixel,       /* I - first pixel number in each dim. of the image */
+    long *lpixel,       /* I - last pixel number in each dim. of the image */
+    long *ininc,        /* I - increment to be applied in each image dimen. */
+    int nullcheck,      /* I - 0, 1: do nothing; 2: set nullarray for nulls */
+    char *nullarray, 
+    int *status)
+
+/* 
+  copy the intersecting pixels from a decompressed tile to the output image. 
+  Both the tile and the image must have the same number of dimensions. 
+*/
+{
+    long imgdim[MAX_COMPRESS_DIM]; /* product of preceding dimensions in the */
+                                   /* output image, allowing for inc factor */
+    long tiledim[MAX_COMPRESS_DIM]; /* product of preceding dimensions in the */
+                                 /* tile, array;  inc factor is not relevant */
+    long imgfpix[MAX_COMPRESS_DIM]; /* 1st img pix overlapping tile: 0 base, */
+                                    /*  allowing for inc factor */
+    long imglpix[MAX_COMPRESS_DIM]; /* last img pix overlapping tile 0 base, */
+                                    /*  allowing for inc factor */
+    long tilefpix[MAX_COMPRESS_DIM]; /* 1st tile pix overlapping img 0 base, */
+                                    /*  allowing for inc factor */
+    long inc[MAX_COMPRESS_DIM]; /* local copy of input ininc */
+    long i1, i2, i3, i4;   /* offset along each axis of the image */
+    long it1, it2, it3, it4;
+    long im1, im2, im3, im4;  /* offset to image pixel, allowing for inc */
+    long ipos, tf, tl;
+    long t2, t3, t4;   /* offset along each axis of the tile */
+    long tilepix, imgpix, tilepixbyte, imgpixbyte;
+    int ii, overlap_bytes, overlap_flags;
+
+    if (*status > 0)
+        return(*status);
+
+    for (ii = 0; ii < MAX_COMPRESS_DIM; ii++)
+    {
+        /* set default values for higher dimensions */
+        inc[ii] = 1;
+        imgdim[ii] = 1;
+        tiledim[ii] = 1;
+        imgfpix[ii] = 0;
+        imglpix[ii] = 0;
+        tilefpix[ii] = 0;
+    }
+
+    /* ------------------------------------------------------------ */
+    /* calc amount of overlap in each dimension; if there is zero   */
+    /* overlap in any dimension then just return  */
+    /* ------------------------------------------------------------ */
+    
+    for (ii = 0; ii < ndim; ii++)
+    {
+        if (tlpixel[ii] < fpixel[ii] || tfpixel[ii] > lpixel[ii])
+            return(*status);  /* there are no overlapping pixels */
+
+        inc[ii] = ininc[ii];
+
+        /* calc dimensions of the output image section */
+        imgdim[ii] = (lpixel[ii] - fpixel[ii]) / labs(inc[ii]) + 1;
+        if (imgdim[ii] < 1)
+            return(*status = NEG_AXIS);
+
+        /* calc dimensions of the tile */
+        tiledim[ii] = tlpixel[ii] - tfpixel[ii] + 1;
+        if (tiledim[ii] < 1)
+            return(*status = NEG_AXIS);
+
+        if (ii > 0)
+           tiledim[ii] *= tiledim[ii - 1];  /* product of dimensions */
+
+        /* first and last pixels in image that overlap with the tile, 0 base */
+        tf = tfpixel[ii] - 1;
+        tl = tlpixel[ii] - 1;
+
+        /* skip this plane if it falls in the cracks of the subsampled image */
+        while ((tf-(fpixel[ii] - 1)) % labs(inc[ii]))
+        {
+           tf++;
+           if (tf > tl)
+             return(*status);  /* no overlapping pixels */
+        }
+
+        while ((tl-(fpixel[ii] - 1)) % labs(inc[ii]))
+        {
+           tl--;
+           if (tf > tl)
+             return(*status);  /* no overlapping pixels */
+        }
+        imgfpix[ii] = maxvalue((tf - fpixel[ii] +1) / labs(inc[ii]) , 0);
+        imglpix[ii] = minvalue((tl - fpixel[ii] +1) / labs(inc[ii]) ,
+                               imgdim[ii] - 1);
+
+        /* first pixel in the tile that overlaps with the image (0 base) */
+        tilefpix[ii] = maxvalue(fpixel[ii] - tfpixel[ii], 0);
+
+        while ((tfpixel[ii] + tilefpix[ii] - fpixel[ii]) % labs(inc[ii]))
+        {
+           (tilefpix[ii])++;
+           if (tilefpix[ii] >= tiledim[ii])
+              return(*status);  /* no overlapping pixels */
+        }
+/*
+printf("ii tfpixel, tlpixel %d %d %d \n",ii, tfpixel[ii], tlpixel[ii]);
+printf("ii, tf, tl, imgfpix,imglpix, tilefpix %d %d %d %d %d %d\n",ii,
+ tf,tl,imgfpix[ii], imglpix[ii],tilefpix[ii]);
+*/
+        if (ii > 0)
+           imgdim[ii] *= imgdim[ii - 1];  /* product of dimensions */
+    }
+
+    /* ---------------------------------------------------------------- */
+    /* calc number of pixels in each row (first dimension) that overlap */
+    /* multiply by pixlen to get number of bytes to copy in each loop   */
+    /* ---------------------------------------------------------------- */
+
+    if (inc[0] != 1)
+       overlap_flags = 1;  /* can only copy 1 pixel at a time */
+    else
+       overlap_flags = imglpix[0] - imgfpix[0] + 1;  /* can copy whole row */
+
+    overlap_bytes = overlap_flags * pixlen;
+
+    /* support up to 5 dimensions for now */
+    for (i4 = 0, it4=0; i4 <= imglpix[4] - imgfpix[4]; i4++, it4++)
+    {
+     /* increment plane if it falls in the cracks of the subsampled image */
+     while (ndim > 4 &&  (tfpixel[4] + tilefpix[4] - fpixel[4] + it4)
+                          % labs(inc[4]) != 0)
+        it4++;
+
+       /* offset to start of hypercube */
+       if (inc[4] > 0)
+          im4 = (i4 + imgfpix[4]) * imgdim[3];
+       else
+          im4 = imgdim[4] - (i4 + 1 + imgfpix[4]) * imgdim[3];
+
+      t4 = (tilefpix[4] + it4) * tiledim[3];
+      for (i3 = 0, it3=0; i3 <= imglpix[3] - imgfpix[3]; i3++, it3++)
+      {
+       /* increment plane if it falls in the cracks of the subsampled image */
+       while (ndim > 3 &&  (tfpixel[3] + tilefpix[3] - fpixel[3] + it3)
+                            % labs(inc[3]) != 0)
+          it3++;
+
+       /* offset to start of cube */
+       if (inc[3] > 0)
+          im3 = (i3 + imgfpix[3]) * imgdim[2] + im4;
+       else
+          im3 = imgdim[3] - (i3 + 1 + imgfpix[3]) * imgdim[2] + im4;
+
+       t3 = (tilefpix[3] + it3) * tiledim[2] + t4;
+
+       /* loop through planes of the image */
+       for (i2 = 0, it2=0; i2 <= imglpix[2] - imgfpix[2]; i2++, it2++)
+       {
+          /* incre plane if it falls in the cracks of the subsampled image */
+          while (ndim > 2 &&  (tfpixel[2] + tilefpix[2] - fpixel[2] + it2)
+                               % labs(inc[2]) != 0)
+             it2++;
+
+          /* offset to start of plane */
+          if (inc[2] > 0)
+             im2 = (i2 + imgfpix[2]) * imgdim[1] + im3;
+          else
+             im2 = imgdim[2] - (i2 + 1 + imgfpix[2]) * imgdim[1] + im3;
+
+          t2 = (tilefpix[2] + it2) * tiledim[1] + t3;
+
+          /* loop through rows of the image */
+          for (i1 = 0, it1=0; i1 <= imglpix[1] - imgfpix[1]; i1++, it1++)
+          {
+             /* incre row if it falls in the cracks of the subsampled image */
+             while (ndim > 1 &&  (tfpixel[1] + tilefpix[1] - fpixel[1] + it1)
+                                  % labs(inc[1]) != 0)
+                it1++;
+
+             /* calc position of first pixel in tile to be copied */
+             tilepix = tilefpix[0] + (tilefpix[1] + it1) * tiledim[0] + t2;
+
+             /* offset to start of row */
+             if (inc[1] > 0)
+                im1 = (i1 + imgfpix[1]) * imgdim[0] + im2;
+             else
+                im1 = imgdim[1] - (i1 + 1 + imgfpix[1]) * imgdim[0] + im2;
+/*
+printf("inc = %d %d %d %d\n",inc[0],inc[1],inc[2],inc[3]);
+printf("im1,im2,im3,im4 = %d %d %d %d\n",im1,im2,im3,im4);
+*/
+             /* offset to byte within the row */
+             if (inc[0] > 0)
+                imgpix = imgfpix[0] + im1;
+             else
+                imgpix = imgdim[0] - 1 - imgfpix[0] + im1;
+/*
+printf("tilefpix0,1, imgfpix1, it1, inc1, t2= %d %d %d %d %d %d\n",
+       tilefpix[0],tilefpix[1],imgfpix[1],it1,inc[1], t2);
+printf("i1, it1, tilepix, imgpix %d %d %d %d \n", i1, it1, tilepix, imgpix);
+*/
+             /* loop over pixels along one row of the image */
+             for (ipos = imgfpix[0]; ipos <= imglpix[0]; ipos += overlap_flags)
+             {
+               if (nullcheck == 2)
+               {
+                   /* copy overlapping null flags from tile to image */
+                   memcpy(nullarray + imgpix, bnullarray + tilepix,
+                          overlap_flags);
+               }
+
+               /* convert from image pixel to byte offset */
+               tilepixbyte = tilepix * pixlen;
+               imgpixbyte  = imgpix  * pixlen;
+/*
+printf("  tilepix, tilepixbyte, imgpix, imgpixbyte= %d %d %d %d\n",
+          tilepix, tilepixbyte, imgpix, imgpixbyte);
+*/
+               /* copy overlapping row of pixels from tile to image */
+               memcpy(image + imgpixbyte, tile + tilepixbyte, overlap_bytes);
+
+               tilepix += (overlap_flags * labs(inc[0]));
+               if (inc[0] > 0)
+                 imgpix += overlap_flags;
+               else
+                 imgpix -= overlap_flags;
+            }
+          }
+        }
+      }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int imcomp_merge_overlap (
+    char *tile,         /* O - multi dimensional array of tile pixels */
+    int pixlen,         /* I - number of bytes in each tile or image pixel */
+    int ndim,           /* I - number of dimension in the tile and image */
+    long *tfpixel,      /* I - first pixel number in each dim. of the tile */
+    long *tlpixel,      /* I - last pixel number in each dim. of the tile */
+    char *bnullarray,   /* I - array of null flags; used if nullcheck = 2 */
+    char *image,        /* I - multi dimensional output image */
+    long *fpixel,       /* I - first pixel number in each dim. of the image */
+    long *lpixel,       /* I - last pixel number in each dim. of the image */
+    int nullcheck,      /* I - 0, 1: do nothing; 2: set nullarray for nulls */
+    int *status)
+
+/* 
+  Similar to imcomp_copy_overlap, except it copies the overlapping pixels from
+  the 'image' to the 'tile'.
+*/
+{
+    long imgdim[MAX_COMPRESS_DIM]; /* product of preceding dimensions in the */
+                                   /* output image, allowing for inc factor */
+    long tiledim[MAX_COMPRESS_DIM]; /* product of preceding dimensions in the */
+                                 /* tile, array;  inc factor is not relevant */
+    long imgfpix[MAX_COMPRESS_DIM]; /* 1st img pix overlapping tile: 0 base, */
+                                    /*  allowing for inc factor */
+    long imglpix[MAX_COMPRESS_DIM]; /* last img pix overlapping tile 0 base, */
+                                    /*  allowing for inc factor */
+    long tilefpix[MAX_COMPRESS_DIM]; /* 1st tile pix overlapping img 0 base, */
+                                    /*  allowing for inc factor */
+    long inc[MAX_COMPRESS_DIM]; /* local copy of input ininc */
+    long i1, i2, i3, i4;   /* offset along each axis of the image */
+    long it1, it2, it3, it4;
+    long im1, im2, im3, im4;  /* offset to image pixel, allowing for inc */
+    long ipos, tf, tl;
+    long t2, t3, t4;   /* offset along each axis of the tile */
+    long tilepix, imgpix, tilepixbyte, imgpixbyte;
+    int ii, overlap_bytes, overlap_flags;
+
+    if (*status > 0)
+        return(*status);
+
+    for (ii = 0; ii < MAX_COMPRESS_DIM; ii++)
+    {
+        /* set default values for higher dimensions */
+        inc[ii] = 1;
+        imgdim[ii] = 1;
+        tiledim[ii] = 1;
+        imgfpix[ii] = 0;
+        imglpix[ii] = 0;
+        tilefpix[ii] = 0;
+    }
+
+    /* ------------------------------------------------------------ */
+    /* calc amount of overlap in each dimension; if there is zero   */
+    /* overlap in any dimension then just return  */
+    /* ------------------------------------------------------------ */
+    
+    for (ii = 0; ii < ndim; ii++)
+    {
+        if (tlpixel[ii] < fpixel[ii] || tfpixel[ii] > lpixel[ii])
+            return(*status);  /* there are no overlapping pixels */
+
+        /* calc dimensions of the output image section */
+        imgdim[ii] = (lpixel[ii] - fpixel[ii]) / labs(inc[ii]) + 1;
+        if (imgdim[ii] < 1)
+            return(*status = NEG_AXIS);
+
+        /* calc dimensions of the tile */
+        tiledim[ii] = tlpixel[ii] - tfpixel[ii] + 1;
+        if (tiledim[ii] < 1)
+            return(*status = NEG_AXIS);
+
+        if (ii > 0)
+           tiledim[ii] *= tiledim[ii - 1];  /* product of dimensions */
+
+        /* first and last pixels in image that overlap with the tile, 0 base */
+        tf = tfpixel[ii] - 1;
+        tl = tlpixel[ii] - 1;
+
+        /* skip this plane if it falls in the cracks of the subsampled image */
+        while ((tf-(fpixel[ii] - 1)) % labs(inc[ii]))
+        {
+           tf++;
+           if (tf > tl)
+             return(*status);  /* no overlapping pixels */
+        }
+
+        while ((tl-(fpixel[ii] - 1)) % labs(inc[ii]))
+        {
+           tl--;
+           if (tf > tl)
+             return(*status);  /* no overlapping pixels */
+        }
+        imgfpix[ii] = maxvalue((tf - fpixel[ii] +1) / labs(inc[ii]) , 0);
+        imglpix[ii] = minvalue((tl - fpixel[ii] +1) / labs(inc[ii]) ,
+                               imgdim[ii] - 1);
+
+        /* first pixel in the tile that overlaps with the image (0 base) */
+        tilefpix[ii] = maxvalue(fpixel[ii] - tfpixel[ii], 0);
+
+        while ((tfpixel[ii] + tilefpix[ii] - fpixel[ii]) % labs(inc[ii]))
+        {
+           (tilefpix[ii])++;
+           if (tilefpix[ii] >= tiledim[ii])
+              return(*status);  /* no overlapping pixels */
+        }
+/*
+printf("ii tfpixel, tlpixel %d %d %d \n",ii, tfpixel[ii], tlpixel[ii]);
+printf("ii, tf, tl, imgfpix,imglpix, tilefpix %d %d %d %d %d %d\n",ii,
+ tf,tl,imgfpix[ii], imglpix[ii],tilefpix[ii]);
+*/
+        if (ii > 0)
+           imgdim[ii] *= imgdim[ii - 1];  /* product of dimensions */
+    }
+
+    /* ---------------------------------------------------------------- */
+    /* calc number of pixels in each row (first dimension) that overlap */
+    /* multiply by pixlen to get number of bytes to copy in each loop   */
+    /* ---------------------------------------------------------------- */
+
+    if (inc[0] != 1)
+       overlap_flags = 1;  /* can only copy 1 pixel at a time */
+    else
+       overlap_flags = imglpix[0] - imgfpix[0] + 1;  /* can copy whole row */
+
+    overlap_bytes = overlap_flags * pixlen;
+
+    /* support up to 5 dimensions for now */
+    for (i4 = 0, it4=0; i4 <= imglpix[4] - imgfpix[4]; i4++, it4++)
+    {
+     /* increment plane if it falls in the cracks of the subsampled image */
+     while (ndim > 4 &&  (tfpixel[4] + tilefpix[4] - fpixel[4] + it4)
+                          % labs(inc[4]) != 0)
+        it4++;
+
+       /* offset to start of hypercube */
+       if (inc[4] > 0)
+          im4 = (i4 + imgfpix[4]) * imgdim[3];
+       else
+          im4 = imgdim[4] - (i4 + 1 + imgfpix[4]) * imgdim[3];
+
+      t4 = (tilefpix[4] + it4) * tiledim[3];
+      for (i3 = 0, it3=0; i3 <= imglpix[3] - imgfpix[3]; i3++, it3++)
+      {
+       /* increment plane if it falls in the cracks of the subsampled image */
+       while (ndim > 3 &&  (tfpixel[3] + tilefpix[3] - fpixel[3] + it3)
+                            % labs(inc[3]) != 0)
+          it3++;
+
+       /* offset to start of cube */
+       if (inc[3] > 0)
+          im3 = (i3 + imgfpix[3]) * imgdim[2] + im4;
+       else
+          im3 = imgdim[3] - (i3 + 1 + imgfpix[3]) * imgdim[2] + im4;
+
+       t3 = (tilefpix[3] + it3) * tiledim[2] + t4;
+
+       /* loop through planes of the image */
+       for (i2 = 0, it2=0; i2 <= imglpix[2] - imgfpix[2]; i2++, it2++)
+       {
+          /* incre plane if it falls in the cracks of the subsampled image */
+          while (ndim > 2 &&  (tfpixel[2] + tilefpix[2] - fpixel[2] + it2)
+                               % labs(inc[2]) != 0)
+             it2++;
+
+          /* offset to start of plane */
+          if (inc[2] > 0)
+             im2 = (i2 + imgfpix[2]) * imgdim[1] + im3;
+          else
+             im2 = imgdim[2] - (i2 + 1 + imgfpix[2]) * imgdim[1] + im3;
+
+          t2 = (tilefpix[2] + it2) * tiledim[1] + t3;
+
+          /* loop through rows of the image */
+          for (i1 = 0, it1=0; i1 <= imglpix[1] - imgfpix[1]; i1++, it1++)
+          {
+             /* incre row if it falls in the cracks of the subsampled image */
+             while (ndim > 1 &&  (tfpixel[1] + tilefpix[1] - fpixel[1] + it1)
+                                  % labs(inc[1]) != 0)
+                it1++;
+
+             /* calc position of first pixel in tile to be copied */
+             tilepix = tilefpix[0] + (tilefpix[1] + it1) * tiledim[0] + t2;
+
+             /* offset to start of row */
+             if (inc[1] > 0)
+                im1 = (i1 + imgfpix[1]) * imgdim[0] + im2;
+             else
+                im1 = imgdim[1] - (i1 + 1 + imgfpix[1]) * imgdim[0] + im2;
+/*
+printf("inc = %d %d %d %d\n",inc[0],inc[1],inc[2],inc[3]);
+printf("im1,im2,im3,im4 = %d %d %d %d\n",im1,im2,im3,im4);
+*/
+             /* offset to byte within the row */
+             if (inc[0] > 0)
+                imgpix = imgfpix[0] + im1;
+             else
+                imgpix = imgdim[0] - 1 - imgfpix[0] + im1;
+/*
+printf("tilefpix0,1, imgfpix1, it1, inc1, t2= %d %d %d %d %d %d\n",
+       tilefpix[0],tilefpix[1],imgfpix[1],it1,inc[1], t2);
+printf("i1, it1, tilepix, imgpix %d %d %d %d \n", i1, it1, tilepix, imgpix);
+*/
+             /* loop over pixels along one row of the image */
+             for (ipos = imgfpix[0]; ipos <= imglpix[0]; ipos += overlap_flags)
+             {
+               /* convert from image pixel to byte offset */
+               tilepixbyte = tilepix * pixlen;
+               imgpixbyte  = imgpix  * pixlen;
+/*
+printf("  tilepix, tilepixbyte, imgpix, imgpixbyte= %d %d %d %d\n",
+          tilepix, tilepixbyte, imgpix, imgpixbyte);
+*/
+               /* copy overlapping row of pixels from image to tile */
+               memcpy(tile + tilepixbyte, image + imgpixbyte,  overlap_bytes);
+
+               tilepix += (overlap_flags * labs(inc[0]));
+               if (inc[0] > 0)
+                 imgpix += overlap_flags;
+               else
+                 imgpix -= overlap_flags;
+            }
+          }
+        }
+      }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+static int unquantize_i1r4(long row, /* tile number = row number in table  */
+            unsigned char *input, /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            unsigned char tnull,  /* I - value of FITS TNULLn keyword if any */
+            float nullval,        /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            float *output,        /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+    Unquantize byte values into the scaled floating point values
+*/
+{
+    long ii;
+    int nextrand, iseed;
+
+    if (!fits_rand_value) 
+       if (fits_init_randoms()) return(MEMORY_ALLOCATION);
+
+    /* initialize the index to the next random number in the list */
+    iseed = (int) ((row - 1) % N_RANDOM);
+    nextrand = (int) (fits_rand_value[iseed] * 500);
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+           for (ii = 0; ii < ntodo; ii++)
+            {
+                output[ii] = (float) (((double) input[ii] - fits_rand_value[nextrand] + 0.5) * scale + zero);
+                nextrand++;
+		if (nextrand == N_RANDOM) {
+                    iseed++;
+		    if (iseed == N_RANDOM) iseed = 0;
+	            nextrand = (int) (fits_rand_value[iseed] * 500);
+                }
+            }
+    }
+    else        /* must check for null values */
+    {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    output[ii] = (float) (((double) input[ii] - fits_rand_value[nextrand] + 0.5) * scale + zero);
+                }
+
+                nextrand++;
+		if (nextrand == N_RANDOM) {
+                    iseed++;
+		    if (iseed == N_RANDOM) iseed = 0;
+	            nextrand = (int) (fits_rand_value[iseed] * 500);
+                }
+ 
+            }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+static int unquantize_i2r4(long row, /* seed for random values  */
+            short *input,         /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            short tnull,          /* I - value of FITS TNULLn keyword if any */
+            float nullval,        /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            float *output,        /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+    Unquantize short integer values into the scaled floating point values
+*/
+{
+    long ii;
+    int nextrand, iseed;
+
+    if (!fits_rand_value) 
+       if (fits_init_randoms()) return(MEMORY_ALLOCATION);
+
+    /* initialize the index to the next random number in the list */
+    iseed = (int) ((row - 1) % N_RANDOM);
+    nextrand = (int) (fits_rand_value[iseed] * 500);
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+           for (ii = 0; ii < ntodo; ii++)
+            {
+                output[ii] = (float) (((double) input[ii] - fits_rand_value[nextrand] + 0.5) * scale + zero);
+                nextrand++;
+		if (nextrand == N_RANDOM) {
+                    iseed++;
+		    if (iseed == N_RANDOM) iseed = 0;
+	            nextrand = (int) (fits_rand_value[iseed] * 500);
+                }
+            }
+    }
+    else        /* must check for null values */
+    {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    output[ii] = (float) (((double) input[ii] - fits_rand_value[nextrand] + 0.5) * scale + zero);
+                }
+
+                nextrand++;
+		if (nextrand == N_RANDOM) {
+                    iseed++;
+		    if (iseed == N_RANDOM) iseed = 0;
+	            nextrand = (int) (fits_rand_value[iseed] * 500);
+                }
+ 
+            }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+static int unquantize_i4r4(long row, /* tile number = row number in table    */
+            INT32BIT *input,      /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            INT32BIT tnull,       /* I - value of FITS TNULLn keyword if any */
+            float nullval,        /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            float *output,        /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+    Unquantize int integer values into the scaled floating point values
+*/
+{
+    long ii;
+    int nextrand, iseed;
+
+    if (fits_rand_value == 0) 
+       if (fits_init_randoms()) return(MEMORY_ALLOCATION);
+
+    /* initialize the index to the next random number in the list */
+    iseed = (int) ((row - 1) % N_RANDOM);
+    nextrand = (int) (fits_rand_value[iseed] * 500);
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                output[ii] = (float) (((double) input[ii] - fits_rand_value[nextrand] + 0.5) * scale + zero);
+
+                nextrand++;
+		if (nextrand == N_RANDOM) {
+                    iseed++;
+		    if (iseed == N_RANDOM) iseed = 0;
+	            nextrand = (int) (fits_rand_value[iseed] * 500);
+                }
+            }
+    }
+    else        /* must check for null values */
+    {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    output[ii] = (float) (((double) input[ii] - fits_rand_value[nextrand] + 0.5) * scale + zero);
+                }
+
+                nextrand++;
+		if (nextrand == N_RANDOM) {
+                    iseed++;
+		    if (iseed == N_RANDOM) iseed = 0;
+	            nextrand = (int) (fits_rand_value[iseed] * 500);
+                }
+
+            }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+static int unquantize_i1r8(long row, /* tile number = row number in table  */
+            unsigned char *input, /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            unsigned char tnull,  /* I - value of FITS TNULLn keyword if any */
+            double nullval,        /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            double *output,        /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+    Unquantize byte values into the scaled floating point values
+*/
+{
+    long ii;
+    int nextrand, iseed;
+
+    if (!fits_rand_value) 
+       if (fits_init_randoms()) return(MEMORY_ALLOCATION);
+
+    /* initialize the index to the next random number in the list */
+    iseed = (int) ((row - 1) % N_RANDOM);
+    nextrand = (int) (fits_rand_value[iseed] * 500);
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+           for (ii = 0; ii < ntodo; ii++)
+            {
+                output[ii] = (double) (((double) input[ii] - fits_rand_value[nextrand] + 0.5) * scale + zero);
+                nextrand++;
+		if (nextrand == N_RANDOM) {
+                    iseed++;
+		    if (iseed == N_RANDOM) iseed = 0;
+	            nextrand = (int) (fits_rand_value[iseed] * 500);
+                }
+            }
+    }
+    else        /* must check for null values */
+    {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    output[ii] = (double) (((double) input[ii] - fits_rand_value[nextrand] + 0.5) * scale + zero);
+                }
+
+                nextrand++;
+		if (nextrand == N_RANDOM) {
+                    iseed++;
+		    if (iseed == N_RANDOM) iseed = 0;
+	            nextrand = (int) (fits_rand_value[iseed] * 500);
+                }
+ 
+            }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+static int unquantize_i2r8(long row, /* tile number = row number in table  */
+            short *input,         /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            short tnull,          /* I - value of FITS TNULLn keyword if any */
+            double nullval,        /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            double *output,        /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+    Unquantize short integer values into the scaled floating point values
+*/
+{
+    long ii;
+    int nextrand, iseed;
+
+    if (!fits_rand_value) 
+       if (fits_init_randoms()) return(MEMORY_ALLOCATION);
+
+    /* initialize the index to the next random number in the list */
+    iseed = (int) ((row - 1) % N_RANDOM);
+    nextrand = (int) (fits_rand_value[iseed] * 500);
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+           for (ii = 0; ii < ntodo; ii++)
+            {
+                output[ii] = (double) (((double) input[ii] - fits_rand_value[nextrand] + 0.5) * scale + zero);
+                nextrand++;
+		if (nextrand == N_RANDOM) {
+                    iseed++;
+		    if (iseed == N_RANDOM) iseed = 0;
+	            nextrand = (int) (fits_rand_value[iseed] * 500);
+                }
+            }
+    }
+    else        /* must check for null values */
+    {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    output[ii] = (double) (((double) input[ii] - fits_rand_value[nextrand] + 0.5) * scale + zero);
+                }
+
+                nextrand++;
+		if (nextrand == N_RANDOM) {
+                    iseed++;
+		    if (iseed == N_RANDOM) iseed = 0;
+	            nextrand = (int) (fits_rand_value[iseed] * 500);
+                }
+ 
+            }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+static int unquantize_i4r8(long row, /* tile number = row number in table    */
+            INT32BIT *input,      /* I - array of values to be converted     */
+            long ntodo,           /* I - number of elements in the array     */
+            double scale,         /* I - FITS TSCALn or BSCALE value         */
+            double zero,          /* I - FITS TZEROn or BZERO  value         */
+            int nullcheck,        /* I - null checking code; 0 = don't check */
+                                  /*     1:set null pixels = nullval         */
+                                  /*     2: if null pixel, set nullarray = 1 */
+            INT32BIT tnull,       /* I - value of FITS TNULLn keyword if any */
+            double nullval,        /* I - set null pixels, if nullcheck = 1   */
+            char *nullarray,      /* I - bad pixel array, if nullcheck = 2   */
+            int  *anynull,        /* O - set to 1 if any pixels are null     */
+            double *output,        /* O - array of converted pixels           */
+            int *status)          /* IO - error status                       */
+/*
+    Unquantize int integer values into the scaled floating point values
+*/
+{
+    long ii;
+    int nextrand, iseed;
+
+    if (fits_rand_value == 0) 
+       if (fits_init_randoms()) return(MEMORY_ALLOCATION);
+
+    /* initialize the index to the next random number in the list */
+    iseed = (int) ((row - 1) % N_RANDOM);
+    nextrand = (int) (fits_rand_value[iseed] * 500);
+
+    if (nullcheck == 0)     /* no null checking required */
+    {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                output[ii] = (double) (((double) input[ii] - fits_rand_value[nextrand] + 0.5) * scale + zero);
+
+                nextrand++;
+		if (nextrand == N_RANDOM) {
+                    iseed++;
+		    if (iseed == N_RANDOM) iseed = 0;
+	            nextrand = (int) (fits_rand_value[iseed] * 500);
+                }
+            }
+    }
+    else        /* must check for null values */
+    {
+            for (ii = 0; ii < ntodo; ii++)
+            {
+                if (input[ii] == tnull)
+                {
+                    *anynull = 1;
+                    if (nullcheck == 1)
+                        output[ii] = nullval;
+                    else
+                        nullarray[ii] = 1;
+                }
+                else
+                {
+                    output[ii] = (double) (((double) input[ii] - fits_rand_value[nextrand] + 0.5) * scale + zero);
+                }
+
+                nextrand++;
+		if (nextrand == N_RANDOM) {
+                    iseed++;
+		    if (iseed == N_RANDOM) iseed = 0;
+	            nextrand = (int) (fits_rand_value[iseed] * 500);
+                }
+
+            }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+static int imcomp_float2nan(float *indata, 
+    long tilelen,
+    int *outdata,
+    float nullflagval, 
+    int *status)
+/*
+  convert pixels that are equal to nullflag to NaNs.
+  Note that indata and outdata point to the same location.
+*/
+{
+    int ii;
+    
+    for (ii = 0; ii < tilelen; ii++) {
+
+      if (indata[ii] == nullflagval)
+        outdata[ii] = -1;  /* integer -1 has the same bit pattern as a real*4 NaN */
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+static int imcomp_double2nan(double *indata, 
+    long tilelen,
+    LONGLONG *outdata,
+    double nullflagval, 
+    int *status)
+/*
+  convert pixels that are equal to nullflag to NaNs.
+  Note that indata and outdata point to the same location.
+*/
+{
+    int ii;
+    
+    for (ii = 0; ii < tilelen; ii++) {
+
+      if (indata[ii] == nullflagval)
+        outdata[ii] = -1;  /* integer -1 has the same bit pattern as a real*8 NaN */
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_transpose_table(fitsfile *infptr, fitsfile *outfptr, int *status)
+
+/*
+  Transpose the elements in the input table columns from row-major order into
+  column-major order, and write to the output table (which may be the same as
+  the input table).   For example, a table with 10000 rows and 2 '1I' columns
+  will be transformed into a 1 row table with 2 '10000I' columns.
+  
+  In addition, compress each column of data and write as a 1-row variable length
+  array column.
+*/
+{ 
+    LONGLONG nrows, incolwidth[999], inrepeat[999], outcolstart[1000], outbytespan[999];
+    LONGLONG headstart, datastart, dataend, startbyte, jj, kk, naxis1;
+    long repeat, width, pcount;
+    int ii, ncols, coltype, hdutype, ltrue = 1;
+    char *buffer, *cptr, keyname[9], tform[40], colcode[999], colname[999][50];
+    char comm[FLEN_COMMENT], *compressed_data;
+    size_t dlen, datasize;
+    float cratio[999];
+    
+    if (*status > 0)
+        return(*status);
+    
+    fits_get_hdu_type(infptr, &hdutype, status);
+    if (hdutype != BINARY_TBL) {
+        *status = NOT_BTABLE;
+        return(*status);
+    }
+        
+    fits_get_num_rowsll(infptr, &nrows, status);
+    fits_get_num_cols(infptr, &ncols, status);
+    fits_read_key(infptr, TLONGLONG, "NAXIS1", &naxis1, NULL, status);
+    if (*status > 0)
+        return(*status);
+
+    if (nrows < 1  || ncols < 1) {
+	/* just copy the HDU if the table has 0 columns or rows */
+	if (infptr != outfptr) {  /* copy input header to the output */
+		fits_copy_hdu (infptr, outfptr, 0, status);
+	}
+	return(*status);
+    }
+ 
+    /* allocate space for the transposed table */
+    buffer = calloc((size_t) naxis1, (size_t) nrows);
+    if (!buffer) {
+        ffpmsg("Could not allocate buffer for transformed table");
+        *status = MEMORY_ALLOCATION;
+        return(*status);
+    }
+
+    if (infptr != outfptr) {  /* copy input header to the output */
+	fits_copy_header(infptr, outfptr, status);
+    }
+
+    outcolstart[0] = 0;
+ 
+    /* do initial setup for each column */
+    for (ii = 0; ii < ncols; ii++) {
+
+	/* get the column name */
+	fits_make_keyn("TTYPE", ii+1, keyname, status);
+	fits_read_key(outfptr, TSTRING, keyname, colname[ii], comm, status);
+
+	/* get the column type, repeat count, and unit width */
+	fits_make_keyn("TFORM", ii+1, keyname, status);
+	fits_read_key(outfptr, TSTRING, keyname, tform, comm, status);
+
+	/* preserve the original TFORM value and comment string */
+	keyname[0] = 'Z';
+	fits_write_key(outfptr, TSTRING, keyname, tform, comm, status);
+	keyname[0] = 'T';
+ 
+        fits_binary_tform(tform, &coltype, &repeat, &width, status);
+
+   	/* BIT columns are a difficult case */
+	 /* round up to a multiple of 8 bits */
+/*
+	if (coltype == TBIT) {  
+	    repeat = (repeat + 7) / 8 * 8; 
+	}
+*/
+
+	cptr = tform;
+	while(isdigit(*cptr)) cptr++;
+	colcode[ii] = *cptr; /* save the column type code */
+
+        /* all columns are now VLAs */
+	fits_modify_key_str(outfptr, keyname, "1PB", "&", status);
+
+	if (coltype == TBIT) {
+	    repeat = (repeat + 7) / 8;  /* convert from bits to bytes */
+	} else if (coltype == TSTRING) {
+	    width = 1;  /* ignore the optional 'w' in 'rAw' format */
+	} else if (coltype < 0) {  /* pointer to variable length array */
+	    width = 8;
+	    if (colcode[ii] == 'Q') width = 16;  /* this is a 'Q' not a 'P' column */
+	    repeat = 1;
+	}
+
+	inrepeat[ii] = repeat;
+	
+	/* width (in bytes) of each element and field in the INPUT row-major table */
+	incolwidth[ii] = repeat * width;
+	
+	/* starting offset of each field in the OUTPUT column-major table */
+	outcolstart[ii + 1] = outcolstart[ii] + incolwidth[ii] * nrows;
+
+	/* length of each sequence of bytes, after sorting them in signicant order */
+	outbytespan[ii] = (incolwidth[ii] * nrows) / width;
+    }
+
+    /* the transformed table has only 1 row */
+    /* output table width 8 bytes per column */
+    fits_modify_key_lng(outfptr, "NAXIS2", 1, "&", status);
+    fits_modify_key_lng(outfptr, "NAXIS1", ncols * 8, "&", status);
+
+    /* move to the start of the input table */
+    fits_get_hduaddrll(infptr, &headstart, &datastart, &dataend, status);
+    ffmbyt(infptr, datastart, 0, status);
+
+    /* now transpose the table into an array in memory */
+    for (jj = 0; jj < nrows; jj++)   {    /* loop over rows */
+      for (ii = 0; ii < ncols; ii++) {  /* loop over columns */
+      
+	kk = 0;	
+
+	    cptr = buffer + (outcolstart[ii] + (jj * incolwidth[ii]));   /* addr to copy to */
+
+	    startbyte = (infptr->Fptr)->bytepos;  /* save the starting byte location */
+
+	    ffgbyt(infptr, incolwidth[ii], cptr, status);  /* copy all the bytes */
+
+	    if (incolwidth[ii] >= MINDIRECT) { /* have to explicitly move to next byte */
+		ffmbyt(infptr, startbyte + incolwidth[ii], 0, status);
+	    }
+      }
+    }
+
+    fits_set_hdustruc(outfptr, status);
+    
+    /* now compress each column with GZIP and write out to output table */
+    for (ii = 0; ii < ncols; ii++) {  /* loop over columns */
+
+	datasize = (size_t) (outcolstart[ii + 1] - outcolstart[ii]);
+
+	/* allocate memory for the compressed data */
+	compressed_data = malloc(datasize);
+	if (!compressed_data) {
+            ffpmsg("data memory allocation error");
+	    return(-1);
+	}
+
+	/* gzip compress the data */
+	compress2mem_from_mem(buffer + outcolstart[ii], datasize,
+	    &compressed_data,  &datasize, realloc, 
+	    &dlen, status);        
+
+	/* write the compressed data to the output column */
+	fits_set_tscale(outfptr, ii + 1, 1.0, 0.0, status);  /* turn off any data scaling, first */
+	fits_write_col(outfptr, TBYTE, ii + 1, 1, 1, dlen, compressed_data, status);
+
+        cratio[ii] = (float) datasize / (float) dlen;	
+	free(compressed_data);   /* don't need the compressed data any more */
+	
+	sprintf(results[ii]," %3d %10.10s %4d %c  %5.2f", ii+1, colname[ii], (int) inrepeat[ii],colcode[ii],cratio[ii]);
+	trans_ratio[ii] = cratio[ii];
+    }
+
+    /* save the original PCOUNT value */
+    fits_read_key(infptr, TLONG, "PCOUNT", &pcount, comm, status);
+    fits_write_key(outfptr, TLONG, "ZPCOUNT", &pcount, comm, status);
+
+    fits_write_key(outfptr, TLONGLONG, "ZNAXIS1", &naxis1, "original rows width",
+	status);
+    fits_write_key(outfptr, TLONGLONG, "ZNAXIS2", &nrows, "original number of rows",
+	status);
+
+    fits_write_key(outfptr, TLOGICAL, "TVIRTUAL", &ltrue, 
+        "this is a virtual table", status);
+    fits_write_key(outfptr, TSTRING, "ZMETHOD", "TRANSPOSED_SHUFFLED_GZIP",
+	   "table compression method", status);
+
+    fits_set_hdustruc(outfptr, status);
+
+    /* copy the heap from input to output file */
+    fits_gzip_heap(infptr, outfptr, status);
+       	
+    free(buffer);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_compress_table_rice(fitsfile *infptr, fitsfile *outfptr, int *status)
+
+/*
+  Transpose the elements in the input table columns from row-major order into
+  column-major order, and write to the output table (which may be the same as
+  the input table).   For example, a table with 10000 rows and 2 '1I' columns
+  will be transformed into a 1 row table with 2 '10000I' columns.
+  
+  Integer columns are then compressed with Rice; all other columns compressed
+  with GZIP.  In addition, the bytes in the floating point numeric data values 
+  (columns with TFORM =  E, and D) are shuffled so that the most significant
+  byte of every element occurs first in the array, followed by the next most
+  significant byte, and so on to the least significant byte.   Thus, if you
+  have 3 4-byte numeric values, the bytes 012301230123 get shuffled to
+  000111222333
+*/
+{ 
+    LONGLONG nrows, incolwidth[999], inrepeat[999], outcolstart[1000], outbytespan[999];
+    LONGLONG headstart, datastart, dataend, startbyte, jj, kk, naxis1;
+    long repeat, width, pcount;
+    int ii, ncols, coltype, hdutype, ltrue = 1;
+    char *buffer, *cptr, keyname[9], tform[40], colcode[999], tempstring[20];
+    char comm[FLEN_COMMENT], *compressed_data;
+    float cratio[999];
+    
+    size_t dlen, datasize;
+
+    if (*status > 0)
+        return(*status);
+    
+    fits_get_hdu_type(infptr, &hdutype, status);
+    if (hdutype != BINARY_TBL) {
+        *status = NOT_BTABLE;
+        return(*status);
+    }
+        
+    fits_get_num_rowsll(infptr, &nrows, status);
+    fits_get_num_cols(infptr, &ncols, status);
+    fits_read_key(infptr, TLONGLONG, "NAXIS1", &naxis1, NULL, status);
+    if (*status > 0)
+        return(*status);
+
+    if (nrows < 1  || ncols < 1) {
+	/* just copy the HDU if the table has 0 columns or rows */
+	if (infptr != outfptr) {  /* copy input header to the output */
+		fits_copy_hdu (infptr, outfptr, 0, status);
+	}
+	return(*status);
+    }
+   
+    /* allocate space for the transposed table */
+    buffer = calloc((size_t) naxis1, (size_t) nrows);
+    if (!buffer) {
+        ffpmsg("Could not allocate buffer for transformed table");
+        *status = MEMORY_ALLOCATION;
+        return(*status);
+    }
+
+    if (infptr != outfptr) {  /* copy input header to the output */
+	fits_copy_header(infptr, outfptr, status);
+    }
+
+    outcolstart[0] = 0;
+    for (ii = 0; ii < ncols; ii++) {
+
+	/* get the column type, repeat count, and unit width */
+	fits_make_keyn("TFORM", ii+1, keyname, status);
+	fits_read_key(outfptr, TSTRING, keyname, tform, comm, status);
+
+	/* preserve the original TFORM value and comment string */
+	keyname[0] = 'Z';
+	fits_write_key(outfptr, TSTRING, keyname, tform, comm, status);
+	keyname[0] = 'T';
+ 
+        fits_binary_tform(tform, &coltype, &repeat, &width, status);
+
+   	/* BIT columns are a difficult case */
+	 /* round up to a multiple of 8 bits */
+/*
+	if (coltype == TBIT) {  
+	    repeat = (repeat + 7) / 8 * 8; 
+	}
+*/
+	cptr = tform;
+	while(isdigit(*cptr)) cptr++;
+	colcode[ii] = *cptr; /* save the column type code */
+
+/* all columns are now VLAs */
+	fits_modify_key_str(outfptr, keyname, "1PB", "&", status);
+
+	if (coltype == TBIT) {
+	    repeat = (repeat + 7) / 8;  /* convert from bits to bytes */
+	} else if (coltype == TSTRING) {
+	    width = 1;  /* ignore the optional 'w' in 'rAw' format */
+	} else if (coltype < 0) {  /* pointer to variable length array */
+	    width = 8;
+	    if (colcode[ii] == 'Q') width = 16;  /* this is a 'Q' not a 'P' column */
+	    repeat = 1;
+	}
+
+	inrepeat[ii] = repeat;
+	
+	/* width (in bytes) of each element and field in the INPUT row-major table */
+	incolwidth[ii] = repeat * width;
+	
+	/* starting offset of each field in the OUTPUT column-major table */
+	outcolstart[ii + 1] = outcolstart[ii] + incolwidth[ii] * nrows;
+
+	/* length of each sequence of bytes, after sorting them in signicant order */
+	outbytespan[ii] = (incolwidth[ii] * nrows) / width;
+    }
+
+    /* the transformed table has only 1 row */
+    /* output table width 8 bytes per column */
+
+    fits_modify_key_lng(outfptr, "NAXIS2", 1, "&", status);
+    fits_modify_key_lng(outfptr, "NAXIS1", ncols * 8, "&", status);
+
+    /* move to the start of the input table */
+    fits_get_hduaddrll(infptr, &headstart, &datastart, &dataend, status);
+    ffmbyt(infptr, datastart, 0, status);
+
+    for (jj = 0; jj < nrows; jj++)   {    /* loop over rows */
+      for (ii = 0; ii < ncols; ii++) {  /* loop over columns */
+      
+	kk = 0;	
+
+	switch (colcode[ii]) {
+	/* separate the byte planes for the 2-byte, 4-byte, and 8-byte numeric columns */
+
+	case 'E':
+	  while(kk < incolwidth[ii]) {
+	    cptr = buffer + (outcolstart[ii] + (jj * inrepeat[ii]) + kk/4);  
+	    ffgbyt(infptr, 1, cptr, status);  /* copy 1 byte */
+	    cptr += outbytespan[ii];  
+	    ffgbyt(infptr, 1, cptr, status);  /* copy 1 byte */
+	    cptr += outbytespan[ii];  
+	    ffgbyt(infptr, 1, cptr, status);  /* copy 1 byte */
+	    cptr += outbytespan[ii];  
+	    ffgbyt(infptr, 1, cptr, status);  /* copy 1 byte */
+	    kk += 4;
+	  }
+	  break;
+
+	case 'D':
+	case 'K':
+	  while(kk < incolwidth[ii]) {
+	    cptr = buffer + (outcolstart[ii] + (jj * inrepeat[ii]) + kk/8);  
+	    ffgbyt(infptr, 1, cptr, status);  /* copy 1 byte */
+	    cptr += outbytespan[ii];  
+	    ffgbyt(infptr, 1, cptr, status);  /* copy 1 byte */
+	    cptr += outbytespan[ii];  
+	    ffgbyt(infptr, 1, cptr, status);  /* copy 1 byte */
+	    cptr += outbytespan[ii];  
+	    ffgbyt(infptr, 1, cptr, status);  /* copy 1 byte */
+	    cptr += outbytespan[ii];  
+	    ffgbyt(infptr, 1, cptr, status);  /* copy 1 byte */
+	    cptr += outbytespan[ii];  
+	    ffgbyt(infptr, 1, cptr, status);  /* copy 1 byte */
+	    cptr += outbytespan[ii];  
+	    ffgbyt(infptr, 1, cptr, status);  /* copy 1 byte */
+	    cptr += outbytespan[ii];  
+	    ffgbyt(infptr, 1, cptr, status);  /* copy 1 byte */
+	    kk += 8;
+	  }
+	  break;
+
+	default: /* don't bother separating the bytes for other column types */
+	    cptr = buffer + (outcolstart[ii] + (jj * incolwidth[ii]));   /* addr to copy to */
+	    startbyte = (infptr->Fptr)->bytepos;  /* save the starting byte location */
+
+	    ffgbyt(infptr, incolwidth[ii], cptr, status);  /* copy all the bytes */
+
+	    if (incolwidth[ii] >= MINDIRECT) { /* have to explicitly move to next byte */
+		ffmbyt(infptr, startbyte + incolwidth[ii], 0, status);
+	    }
+	}
+      }
+    }
+
+    fits_set_hdustruc(outfptr, status);
+    
+    for (ii = 0; ii < ncols; ii++) {  /* loop over columns */
+
+	datasize = (size_t) (outcolstart[ii + 1] - outcolstart[ii]);
+	/* allocate memory for the compressed data */
+	compressed_data = malloc(datasize*2);
+	if (!compressed_data) {
+            ffpmsg("data memory allocation error");
+	    return(-1);
+	}
+
+
+	switch (colcode[ii]) {
+
+
+	case 'I':
+#if BYTESWAPPED
+                ffswap2((short *) (buffer + outcolstart[ii]),  datasize / 2); 
+#endif
+  	        dlen = fits_rcomp_short ((short *)(buffer + outcolstart[ii]), datasize / 2, (unsigned char *) compressed_data,
+                       datasize * 2, 32);
+
+	  	fits_make_keyn("ZCTYP", ii+1, keyname, status);
+	  	fits_write_key(outfptr, TSTRING, keyname, "RICE_1",
+	     	     "compression algorithm for column", status);
+
+	  break;
+
+	case 'J':
+
+#if BYTESWAPPED
+                ffswap4((int *) (buffer + outcolstart[ii]),  datasize / 4); 
+#endif
+   	        dlen = fits_rcomp ((int *)(buffer + outcolstart[ii]), datasize / 4, (unsigned char *) compressed_data,
+                       datasize * 2, 32);
+
+	  	fits_make_keyn("ZCTYP", ii+1, keyname, status);
+	  	fits_write_key(outfptr, TSTRING, keyname, "RICE_1",
+	     	     "compression algorithm for column", status);
+	  break;
+
+	case 'B':
+  	        dlen = fits_rcomp_byte ((signed char *)(buffer + outcolstart[ii]), datasize, (unsigned char *) compressed_data,
+                       datasize * 2, 32);
+
+	  	fits_make_keyn("ZCTYP", ii+1, keyname, status);
+	  	fits_write_key(outfptr, TSTRING, keyname, "RICE_1",
+	     	     "compression algorithm for column", status);
+	  break;
+
+	default: 
+		/* gzip compress the data */
+		compress2mem_from_mem(buffer + outcolstart[ii], datasize,
+	    		&compressed_data,  &datasize, realloc, &dlen, status);        
+
+
+	  	fits_make_keyn("ZCTYP", ii+1, keyname, status);
+	  	fits_write_key(outfptr, TSTRING, keyname, "GZIP_2",
+	     	     "compression algorithm for column", status);
+
+	} /* end of switch block */
+
+	if (dlen != 0)
+	cratio[ii] = (float) datasize / (float) dlen;  /* compression ratio of the column */
+
+	/* write the compressed data to the output column */
+	fits_set_tscale(outfptr, ii + 1, 1.0, 0.0, status);  /* turn off any data scaling, first */
+	fits_write_col(outfptr, TBYTE, ii + 1, 1, 1, dlen, compressed_data, status);
+	
+	free(compressed_data);   /* don't need the compressed data any more */
+/*	printf("   %c  %5.2f\n",colcode[ii],cratio[ii]); */
+
+	    sprintf(tempstring,"  %5.2f\n",cratio[ii]);
+/*
+        if (colcode[ii] == 'I' || colcode[ii] == 'J' || colcode[ii] == 'B') 
+	    sprintf(tempstring,"  %5.2f\n",cratio[ii]);
+	else
+	    sprintf(tempstring," \n");	
+*/
+	strcat(results[ii],tempstring);
+    }  /* end of loop over ncols */
+
+    printf("                       Trans   Shuf   Rice\n");
+    for (ii = 0; ii < ncols; ii++) {  /* loop over columns */
+      printf("%s", results[ii]);
+    }
+    
+    /* save the original PCOUNT value */
+    fits_read_key(infptr, TLONG, "PCOUNT", &pcount, comm, status);
+    fits_write_key(outfptr, TLONG, "ZPCOUNT", &pcount, comm, status);
+
+	
+    fits_write_key(outfptr, TLONGLONG, "ZNAXIS1", &naxis1, "original rows width",
+	status);
+    fits_write_key(outfptr, TLONGLONG, "ZNAXIS2", &nrows, "original number of rows",
+	status);
+
+    fits_write_key(outfptr, TLOGICAL, "ZTABLE", &ltrue, 
+        "this is a compressed table", status);
+
+    free(buffer);
+
+    fits_gzip_heap(infptr, outfptr, status);
+    fits_set_hdustruc(outfptr, status);
+       	
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_compress_table_fast(fitsfile *infptr, fitsfile *outfptr, int *status)
+
+/*
+  Compress the input FITS binary table using the 'fast' method, which consists
+  of (a) transposing the rows and columns, and (b) shuffling the bytes for
+  the I, J, K, E, and D columns  so that the most significant byte of every
+  element occurs first in the array, followed by the next most significant byte,
+  and so on to the least significant byte.   Thus, if you have 3 4-byte numeric
+  values, the bytes 012301230123 get shuffled to 000111222333
+
+  Finally, (c) compress each column of bytes with gzip and copy to the output table.
+  
+*/
+{ 
+    LONGLONG nrows, incolwidth[999], inrepeat[999], outcolstart[1000], outbytespan[999];
+    LONGLONG headstart, datastart, dataend, startbyte, jj, kk, naxis1;
+    long repeat, width, pcount;
+    int ii, ncols, coltype, hdutype, ltrue = 1;
+    char *buffer, *cptr, keyname[9], tform[40], colcode[999];
+    char comm[FLEN_COMMENT], *compressed_data, tempstring[20];
+    size_t dlen, datasize;
+    float cratio[999];
+    
+    if (*status > 0)
+        return(*status);
+    
+    fits_get_hdu_type(infptr, &hdutype, status);
+    if (hdutype != BINARY_TBL) {
+        *status = NOT_BTABLE;
+        return(*status);
+    }
+        
+    fits_get_num_rowsll(infptr, &nrows, status);
+    fits_get_num_cols(infptr, &ncols, status);
+    fits_read_key(infptr, TLONGLONG, "NAXIS1", &naxis1, NULL, status);
+    if (*status > 0)
+        return(*status);
+
+    if (nrows < 1  || ncols < 1) {
+	/* just copy the HDU if the table has 0 columns or rows */
+	if (infptr != outfptr) {  /* copy input header to the output */
+		fits_copy_hdu (infptr, outfptr, 0, status);
+	}
+	return(*status);
+    }
+ 
+    /* allocate space for the transposed table */
+    buffer = calloc((size_t) naxis1, (size_t) nrows);
+    if (!buffer) {
+        ffpmsg("Could not allocate buffer for transformed table");
+        *status = MEMORY_ALLOCATION;
+        return(*status);
+    }
+
+    if (infptr != outfptr) {  /* copy input header to the output */
+	fits_copy_header(infptr, outfptr, status);
+    }
+
+    fits_write_key_log(outfptr, "ZTABLE", 1, 
+          "extension contains compressed binary table", status);
+
+    fits_write_key(outfptr, TLONGLONG, "ZTILELEN", &nrows,
+          "number of rows in each tile", status);
+
+    fits_write_key(outfptr, TLONGLONG, "ZNAXIS1", &naxis1, "original rows width",
+	status);
+    fits_write_key(outfptr, TLONGLONG, "ZNAXIS2", &nrows, "original number of rows",
+	status);
+
+    /* save the original PCOUNT value */
+    fits_read_key(infptr, TLONG, "PCOUNT", &pcount, comm, status);
+    fits_write_key(outfptr, TLONG, "ZPCOUNT", &pcount, comm, status);
+
+    /* reset the PCOUNT keyword to zero */
+    pcount = 0;
+    fits_modify_key_lng(outfptr, "PCOUNT", pcount, NULL, status);
+
+    outcolstart[0] = 0;
+    for (ii = 0; ii < ncols; ii++) {
+
+	/* get the column type, repeat count, and unit width */
+	fits_make_keyn("TFORM", ii+1, keyname, status);
+	fits_read_key(outfptr, TSTRING, keyname, tform, comm, status);
+
+	/* preserve the original TFORM value and comment string */
+	keyname[0] = 'Z';
+	fits_write_key(outfptr, TSTRING, keyname, tform, comm, status);
+	keyname[0] = 'T';
+ 
+       /* all columns are now VLAs */
+	fits_modify_key_str(outfptr, keyname, "1PB", "&", status);
+
+ 	fits_make_keyn("ZCTYP", ii+1, keyname, status);
+	fits_write_key(outfptr, TSTRING, keyname, "GZIP_2",
+	     "compression algorithm for column", status);
+
+        fits_binary_tform(tform, &coltype, &repeat, &width, status);
+
+	cptr = tform;
+	while(isdigit(*cptr)) cptr++;
+	colcode[ii] = *cptr; /* save the column type code */
+
+	if (coltype == TBIT) {
+	    repeat = (repeat + 7) / 8;  /* convert from bits to bytes */
+	} else if (coltype == TSTRING) {
+	    width = 1;  /* ignore the optional 'w' in 'rAw' format */
+	} else if (coltype < 0) {  /* pointer to variable length array */
+	    width = 8;
+	    if (colcode[ii] == 'Q') width = 16;  /* this is a 'Q' not a 'P' column */
+	    repeat = 1;
+	}
+
+	inrepeat[ii] = repeat;
+	
+	/* width (in bytes) of each element and field in the INPUT row-major table */
+	incolwidth[ii] = repeat * width;
+	
+	/* starting offset of each field in the OUTPUT column-major table */
+	outcolstart[ii + 1] = outcolstart[ii] + incolwidth[ii] * nrows;
+
+	/* length of each sequence of bytes, after sorting them in signicant order */
+	outbytespan[ii] = (incolwidth[ii] * nrows) / width;
+
+    }
+
+    /* the transformed table has only 1 row */
+    /* output table width 8 bytes per column */
+
+    fits_modify_key_lng(outfptr, "NAXIS2", 1, "&", status);
+    fits_modify_key_lng(outfptr, "NAXIS1", ncols * 8, "&", status);
+
+    /* move to the start of the input table */
+    fits_get_hduaddrll(infptr, &headstart, &datastart, &dataend, status);
+    ffmbyt(infptr, datastart, 0, status);
+
+    for (jj = 0; jj < nrows; jj++)   {    /* loop over rows */
+      for (ii = 0; ii < ncols; ii++) {  /* loop over columns */
+      
+	kk = 0;	
+
+	switch (colcode[ii]) {
+	/* separate the byte planes for the 2-byte, 4-byte, and 8-byte numeric columns */
+	case 'I':
+	  while(kk < incolwidth[ii]) {
+
+	    cptr = buffer + (outcolstart[ii] + (jj * inrepeat[ii]) + kk/2);  
+	    ffgbyt(infptr, 1, cptr, status);  /* copy 1st byte */
+	    cptr += outbytespan[ii];  
+	    ffgbyt(infptr, 1, cptr, status);  /* copy 2nd byte */
+	    kk += 2;
+	  }
+
+	  break;
+	
+	case 'J':
+	case 'E':
+	  while(kk < incolwidth[ii]) {
+	    cptr = buffer + (outcolstart[ii] + (jj * inrepeat[ii]) + kk/4);  
+	    ffgbyt(infptr, 1, cptr, status);  /* copy 1 byte */
+	    cptr += outbytespan[ii];  
+	    ffgbyt(infptr, 1, cptr, status);  /* copy 1 byte */
+	    cptr += outbytespan[ii];  
+	    ffgbyt(infptr, 1, cptr, status);  /* copy 1 byte */
+	    cptr += outbytespan[ii];  
+	    ffgbyt(infptr, 1, cptr, status);  /* copy 1 byte */
+	    kk += 4;
+	  }
+	  break;
+
+	case 'D':
+	case 'K':
+	  while(kk < incolwidth[ii]) {
+	    cptr = buffer + (outcolstart[ii] + (jj * inrepeat[ii]) + kk/8);  
+	    ffgbyt(infptr, 1, cptr, status);  /* copy 1 byte */
+	    cptr += outbytespan[ii];  
+	    ffgbyt(infptr, 1, cptr, status);  /* copy 1 byte */
+	    cptr += outbytespan[ii];  
+	    ffgbyt(infptr, 1, cptr, status);  /* copy 1 byte */
+	    cptr += outbytespan[ii];  
+	    ffgbyt(infptr, 1, cptr, status);  /* copy 1 byte */
+	    cptr += outbytespan[ii];  
+	    ffgbyt(infptr, 1, cptr, status);  /* copy 1 byte */
+	    cptr += outbytespan[ii];  
+	    ffgbyt(infptr, 1, cptr, status);  /* copy 1 byte */
+	    cptr += outbytespan[ii];  
+	    ffgbyt(infptr, 1, cptr, status);  /* copy 1 byte */
+	    cptr += outbytespan[ii];  
+	    ffgbyt(infptr, 1, cptr, status);  /* copy 1 byte */
+	    kk += 8;
+	  }
+	  break;
+
+	default: /* don't bother separating the bytes for other column types */
+	    cptr = buffer + (outcolstart[ii] + (jj * incolwidth[ii]));   /* addr to copy to */
+
+	    startbyte = (infptr->Fptr)->bytepos;  /* save the starting byte location */
+
+	    ffgbyt(infptr, incolwidth[ii], cptr, status);  /* copy all the bytes */
+
+	    if (incolwidth[ii] >= MINDIRECT) { /* have to explicitly move to next byte */
+		ffmbyt(infptr, startbyte + incolwidth[ii], 0, status);
+	    }
+	}
+      }
+    }
+
+    fits_set_hdustruc(outfptr, status);
+    
+    /* now compress each column */
+    for (ii = 0; ii < ncols; ii++) {  /* loop over columns */
+
+	/* write the compression type code for this column */
+	switch (colcode[ii]) {
+	case 'I':
+	case 'J':
+	case 'K':
+	case 'E':
+	case 'D':
+	  fits_make_keyn("ZCTYP", ii+1, keyname, status);
+	  fits_write_key(outfptr, TSTRING, keyname, "GZIP_2",
+	     "compression algorithm for column", status);
+	  break;
+	default:
+	  fits_make_keyn("ZCTYP", ii+1, keyname, status);
+	  fits_write_key(outfptr, TSTRING, keyname, "GZIP_1",
+	     "compression algorithm for column", status);
+        }
+
+	datasize = (size_t) (outcolstart[ii + 1] - outcolstart[ii]);
+
+	/* allocate memory for the compressed data */
+	compressed_data = malloc(datasize);
+	if (!compressed_data) {
+            ffpmsg("data memory allocation error");
+	    return(-1);
+	}
+
+	/* gzip compress the data */
+	compress2mem_from_mem(buffer + outcolstart[ii], datasize,
+	    &compressed_data,  &datasize, realloc, 
+	    &dlen, status);        
+
+	/* write the compressed data to the output column */
+	fits_set_tscale(outfptr, ii + 1, 1.0, 0.0, status);  /* turn off any data scaling, first */
+	fits_write_col(outfptr, TBYTE, ii + 1, 1, 1, dlen, compressed_data, status);
+
+        cratio[ii] = (float) datasize / (float) dlen;	
+	free(compressed_data);   /* don't need the compressed data any more */
+
+	sprintf(tempstring,"  %5.2f",cratio[ii]);
+
+	strcat(results[ii],tempstring);
+    }
+
+    free(buffer);
+
+    /* shuffle and compress the input heap and append to the output file */
+
+    fits_gzip_heap(infptr, outfptr, status);
+    fits_set_hdustruc(outfptr, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_compress_table_best(fitsfile *infptr, fitsfile *outfptr, int *status)
+
+/*
+  Compress the input FITS binary table using the 'best' compression method, i.e,
+  whichever method produces the highest compression for each column.
+  
+  First, transpose the rows and columns in the table, then, depending on the 
+  data type of the column, try the different compression methods to see
+  which one produces the highest amount of compression.
+  
+*/
+{ 
+    LONGLONG nrows, incolwidth[999], inrepeat[999], outcolstart[1000], outbytespan[999];
+    LONGLONG headstart, datastart, dataend, startbyte, jj, naxis1;
+    long repeat, width, pcount;
+    int ii, ncols, coltype, hdutype, ltrue = 1;
+    char *buffer, *cptr, keyname[9], tform[40], colcode[999];
+    char comm[FLEN_COMMENT];
+    char *gzip1_data = 0, *gzip2_data = 0, *rice_data = 0;
+    size_t gzip1_len, gzip2_len, rice_len, datasize, buffsize;
+    
+    if (*status > 0)
+        return(*status);
+    
+    fits_get_hdu_type(infptr, &hdutype, status);
+    if (hdutype != BINARY_TBL) {
+        *status = NOT_BTABLE;
+        return(*status);
+    }
+        
+    fits_get_num_rowsll(infptr, &nrows, status);
+    fits_get_num_cols(infptr, &ncols, status);
+    fits_read_key(infptr, TLONGLONG, "NAXIS1", &naxis1, NULL, status);
+    if (*status > 0)
+        return(*status);
+
+    if (nrows < 1  || ncols < 1) {
+	/* just copy the HDU if the table has 0 columns or rows */
+	if (infptr != outfptr) {  /* copy input header to the output */
+		fits_copy_hdu (infptr, outfptr, 0, status);
+	}
+	return(*status);
+    }
+ 
+    /* allocate space for the transposed table */
+    buffer = calloc((size_t) naxis1, (size_t) nrows);
+    if (!buffer) {
+        ffpmsg("Could not allocate buffer for transformed table");
+        *status = MEMORY_ALLOCATION;
+        return(*status);
+    }
+
+    if (infptr != outfptr) {  /* copy input header to the output */
+	fits_copy_header(infptr, outfptr, status);
+    }
+
+    fits_write_key_log(outfptr, "ZTABLE", 1, 
+          "extension contains compressed binary table", status);
+
+    fits_write_key(outfptr, TLONGLONG, "ZTILELEN", &nrows,
+          "number of rows in each tile", status);
+
+    fits_write_key(outfptr, TLONGLONG, "ZNAXIS1", &naxis1, "original rows width",
+	status);
+    fits_write_key(outfptr, TLONGLONG, "ZNAXIS2", &nrows, "original number of rows",
+	status);
+
+    /* save the original PCOUNT value */
+    fits_read_key(infptr, TLONG, "PCOUNT", &pcount, comm, status);
+    fits_write_key(outfptr, TLONG, "ZPCOUNT", &pcount, comm, status);
+    /* reset the PCOUNT keyword to zero */
+    pcount = 0;
+    fits_modify_key_lng(outfptr, "PCOUNT", pcount, NULL, status);
+
+    /* Modify the TFORMn keywords; all columns become variable-length arrays. */
+    /* Save the original TFORMn values in the corresponding ZFORMn keyword.   */
+    outcolstart[0] = 0;
+    for (ii = 0; ii < ncols; ii++) {
+
+	/* get the column type, repeat count, and unit width */
+	fits_make_keyn("TFORM", ii+1, keyname, status);
+	fits_read_key(outfptr, TSTRING, keyname, tform, comm, status);
+
+	/* preserve the original TFORM value and comment string */
+	keyname[0] = 'Z';
+	fits_write_key(outfptr, TSTRING, keyname, tform, comm, status);
+	keyname[0] = 'T';
+ 
+       /* all columns are now VLAs */
+	fits_modify_key_str(outfptr, keyname, "1PB", "&", status);
+
+        fits_binary_tform(tform, &coltype, &repeat, &width, status);
+
+	cptr = tform;
+	while(isdigit(*cptr)) cptr++;
+	colcode[ii] = *cptr; /* save the column type code */
+
+        /* deal with special cases */
+	if (coltype == TBIT) {
+	    repeat = (repeat + 7) / 8;  /* convert from bits to bytes */
+	} else if (coltype == TSTRING) {
+	    width = 1;  /* ignore the optional 'w' in 'rAw' format */
+	} else if (coltype < 0) {  /* pointer to variable length array */
+	    repeat = 1;
+
+	    if (colcode[ii] == 'Q')
+	       width = 16;  /* this is a 'Q' column */
+	    else
+	       width = 8;  /* this is a 'P' column */
+	}
+
+	inrepeat[ii] = repeat;
+	
+	/* width (in bytes) of each element and field in the INPUT row-major table */
+	incolwidth[ii] = repeat * width;
+	
+	/* starting offset of each field in the OUTPUT column-major table */
+	outcolstart[ii + 1] = outcolstart[ii] + incolwidth[ii] * nrows;
+
+	/* length of each sequence of bytes, after sorting them in signicant order */
+	outbytespan[ii] = (incolwidth[ii] * nrows) / width;
+    }
+
+    /* the transformed table has only 1 row */
+    /* output table width 8 bytes per column */
+
+    fits_modify_key_lng(outfptr, "NAXIS2", 1, "&", status);
+    fits_modify_key_lng(outfptr, "NAXIS1", ncols * 8, "&", status);
+
+    /* move to the start of the input table */
+    fits_get_hduaddrll(infptr, &headstart, &datastart, &dataend, status);
+    ffmbyt(infptr, datastart, 0, status);
+
+    /* now transpose the rows and columns in the table into an array in memory */
+    for (jj = 0; jj < nrows; jj++)   {    /* loop over rows */
+        for (ii = 0; ii < ncols; ii++) {  /* loop over columns */
+      
+	    cptr = buffer + (outcolstart[ii] + (jj * incolwidth[ii])); /* output address */
+	    startbyte = (infptr->Fptr)->bytepos;  /* save the starting byte location */
+	    ffgbyt(infptr, incolwidth[ii], cptr, status);  /* copy the column element */
+
+	    if (incolwidth[ii] >= MINDIRECT) { /* have to explicitly move to next byte */
+		ffmbyt(infptr, startbyte + incolwidth[ii], 0, status);
+	    }
+        }
+    }
+
+    fits_set_hdustruc(outfptr, status);  /* reinitialize internal pointers */
+    
+    /* Now compress each column.  Depending on the column data type, try */
+    /* all the various available compression algorithms, then choose the one */
+    /* that gives the most compression. */
+    for (ii = 0; ii < ncols; ii++) {  /* loop over columns */
+
+	datasize = (size_t) (outcolstart[ii + 1] - outcolstart[ii]);
+
+	/* allocate memory for the gzip compressed data */
+	gzip1_data = malloc(datasize);
+	if (!gzip1_data) {
+            ffpmsg("data memory allocation error");
+	    return(-1);
+	}
+	buffsize = datasize;
+
+	/*  First, simply compress the bytes with gzip (GZIP_1 algorithm code). */
+	/*  This algorithm can be applied to every type of column. */
+	compress2mem_from_mem(buffer + outcolstart[ii], datasize,
+	    &gzip1_data,  &buffsize, realloc, &gzip1_len, status);        
+	
+	/* depending on the data type, try other compression methods */
+	switch (colcode[ii]) {
+
+	case 'I':   /* 2-byte Integer columns */
+
+		/************* first, try rice compression *****************/
+		rice_data = malloc(datasize * 2);  /* memory for the compressed bytes */
+		if (!rice_data) {
+                    ffpmsg("data memory allocation error");
+		    return(-1);
+		}
+
+#if BYTESWAPPED
+		/* have to swap the bytes on little endian machines */
+                ffswap2((short *) (buffer + outcolstart[ii]),  datasize / 2); 
+#endif
+  	        rice_len = fits_rcomp_short ((short *)(buffer + outcolstart[ii]), datasize / 2, 
+		   (unsigned char *) rice_data, datasize * 2, 32);
+	
+#if BYTESWAPPED
+		/* un-swap the bytes, to restore the original order */
+                ffswap2((short *) (buffer + outcolstart[ii]),  datasize / 2); 
+#endif
+	   
+		/************* Second, try shuffled gzip compression *****************/
+		fits_shuffle_2bytes(buffer + outcolstart[ii], datasize / 2, status);
+
+		/* allocate memory for the shuffled gzip compressed data */
+		gzip2_data = malloc(datasize);
+		if (!gzip2_data) {
+                    ffpmsg("data memory allocation error");
+		    return(-1);
+		}
+		buffsize = datasize;
+
+		compress2mem_from_mem(buffer + outcolstart[ii], datasize,
+		    &gzip2_data,  &buffsize, realloc, &gzip2_len, status);        
+           break;
+
+	case 'J':   /* 4-byte Integer columns */
+
+		/************* first, try rice compression *****************/
+		rice_data = malloc(datasize * 2);  /* memory for the compressed bytes */
+		if (!rice_data) {
+                    ffpmsg("data memory allocation error");
+		    return(-1);
+		}
+#if BYTESWAPPED
+		/* have to swap the bytes on little endian machines */
+                ffswap4((int *) (buffer + outcolstart[ii]),  datasize / 4); 
+#endif
+  	        rice_len = fits_rcomp ((int *)(buffer + outcolstart[ii]), datasize / 4, 
+		   (unsigned char *) rice_data, datasize * 2, 32);
+	
+#if BYTESWAPPED
+		/* un-swap the bytes, to restore the original order */
+                ffswap4((int *) (buffer + outcolstart[ii]),  datasize / 4); 
+#endif
+	   
+		/************* Second, try shuffled gzip compression *****************/
+		fits_shuffle_4bytes(buffer + outcolstart[ii], datasize / 4, status);
+
+		/* allocate memory for the shuffled gzip compressed data */
+		gzip2_data = malloc(datasize);
+		if (!gzip2_data) {
+                    ffpmsg("data memory allocation error");
+		    return(-1);
+		}
+		buffsize = datasize;
+
+		compress2mem_from_mem(buffer + outcolstart[ii], datasize,
+		    &gzip2_data,  &buffsize, realloc, &gzip2_len, status);        
+           break;
+
+	case 'E':   /* 4-byte floating-point */
+
+		/************* try shuffled gzip compression *****************/
+		fits_shuffle_4bytes(buffer + outcolstart[ii], datasize / 4, status);
+
+		/* allocate memory for the gzip compressed data */
+		gzip2_data = malloc(datasize);
+		if (!gzip2_data) {
+                    ffpmsg("data memory allocation error");
+		    return(-1);
+		}
+		buffsize = datasize;
+		
+		compress2mem_from_mem(buffer + outcolstart[ii], datasize,
+		    &gzip2_data,  &buffsize, realloc, &gzip2_len, status);        
+
+		rice_len = 100 * datasize;  /* rice is not applicable to R*4 data */
+
+	   break;
+
+	case 'K':
+	case 'D':  /* 8-byte floating-point or integers */
+
+		/************* try shuffled gzip compression *****************/
+		fits_shuffle_8bytes(buffer + outcolstart[ii], datasize / 8, status);
+
+		/* allocate memory for the gzip compressed data */
+		gzip2_data = malloc(datasize);
+		if (!gzip2_data) {
+                    ffpmsg("data memory allocation error");
+		    return(-1);
+		}
+		buffsize = datasize;
+
+		compress2mem_from_mem(buffer + outcolstart[ii], datasize,
+		    &gzip2_data,  &buffsize, realloc, &gzip2_len, status);        
+
+		rice_len = 100 * datasize;  /* rice is not applicable to R*8 or I*8 data */
+
+	   break;
+
+	default:  /* L, X, B, A, C, M, P, Q type columns: no other compression options */
+		rice_len = 100 * datasize;   /* rice is not applicable */
+		gzip2_len = 100 * datasize;  /* shuffled-gzip is not applicable */
+
+	}  /* end of switch block */
+
+	/* now write the compressed bytes from the best algorithm */
+	fits_set_tscale(outfptr, ii + 1, 1.0, 0.0, status);  /* turn off any data scaling, first */
+	if (gzip1_len <= gzip2_len && gzip1_len <= rice_len) {
+
+	    fits_write_col(outfptr, TBYTE, ii + 1, 1, 1, gzip1_len, gzip1_data, status);
+	    fits_make_keyn("ZCTYP", ii+1, keyname, status);
+	    fits_write_key(outfptr, TSTRING, keyname, "GZIP_1",
+	         "compression algorithm for column", status);
+	} else if (gzip2_len <= gzip1_len && gzip2_len <= rice_len) {
+	    fits_write_col(outfptr, TBYTE, ii + 1, 1, 1, gzip2_len, gzip2_data, status);
+	    fits_make_keyn("ZCTYP", ii+1, keyname, status);
+	    fits_write_key(outfptr, TSTRING, keyname, "GZIP_2",
+	         "compression algorithm for column", status);
+	} else {
+	    fits_write_col(outfptr, TBYTE, ii + 1, 1, 1, rice_len, rice_data, status);
+	    fits_make_keyn("ZCTYP", ii+1, keyname, status);
+	    fits_write_key(outfptr, TSTRING, keyname, "RICE_1",
+	         "compression algorithm for column", status);
+	}
+
+	/* free the temporary memory */
+	if (gzip1_data) free(gzip1_data);   
+	if (gzip2_data) free(gzip2_data);   
+	gzip1_data = 0;
+	gzip2_data = 0;
+    }
+
+    free(buffer);
+
+    /* shuffle and compress the input heap and append to the output file */
+
+    fits_gzip_heap(infptr, outfptr, status);
+    fits_set_hdustruc(outfptr, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_uncompress_table(fitsfile *infptr, fitsfile *outfptr, int *status)
+
+/*
+  Uncompress the table that was compressed with fits_compress_table_fast or
+  fits_compress_table_best.
+*/
+{ 
+    LONGLONG nrows, rmajor_colwidth[999], rmajor_colstart[1000], cmajor_colstart[1000];
+    LONGLONG cmajor_repeat[999], rmajor_repeat[999], cmajor_bytespan[999], kk;
+    LONGLONG headstart, datastart, dataend;
+    long repeat, width, vla_repeat;
+    int  ncols, coltype, hdutype, anynull, tstatus, zctype[999];
+    char *buffer, *transbuffer, *cptr, keyname[9], tform[40], colcode[999];
+    long  pcount, zheapptr, naxis1, naxis2, ii, jj;
+    char *ptr, comm[FLEN_COMMENT], zvalue[FLEN_VALUE];
+    size_t dlen, fullsize;
+    
+    /**** do initial sanity checks *****/
+    if (*status > 0)
+        return(*status);
+     
+    fits_get_hdu_type(infptr, &hdutype, status);
+    if (hdutype != BINARY_TBL) {
+        *status = NOT_BTABLE;
+        return(*status);
+    }
+ 
+    fits_get_num_rowsll(infptr, &nrows, status);
+    fits_get_num_cols(infptr, &ncols, status);
+
+    if (nrows != 1  || (ncols < 1)) {
+	/* just copy the HDU if the table does not have 1 row and 
+	   more than 0 columns */
+	if (infptr != outfptr) { 
+		fits_copy_hdu (infptr, outfptr, 0, status);
+	}
+	return(*status);
+    }
+
+    /**** get size of the uncompressed table */
+    fits_read_key(infptr, TLONG, "ZNAXIS1", &naxis1, comm, status);
+    if (*status > 0) {
+        ffpmsg("Could not find the required ZNAXIS1 keyword");
+        *status = 1;
+        return(*status);
+    }
+
+    fits_read_key(infptr, TLONG, "ZNAXIS2", &naxis2, comm, status);
+    if (*status > 0) {
+        ffpmsg("Could not find the required ZNAXIS2 keyword");
+        *status = 1;
+        return(*status);
+    }
+
+    fits_read_key(infptr, TLONG, "ZPCOUNT", &pcount, comm, status);
+    if (*status > 0) {
+        ffpmsg("Could not find the required ZPCOUNT keyword");
+        *status = 1;
+        return(*status);
+    }
+
+    tstatus = 0;
+    fits_read_key(infptr, TLONG, "ZHEAPPTR", &zheapptr, comm, &tstatus);
+    if (tstatus > 0) {
+        zheapptr = 0;  /* uncompressed table has no heap */
+    }
+
+    /**** recreate the uncompressed table header keywords ****/
+    fits_copy_header(infptr, outfptr, status);
+
+    /* reset the NAXISn keywords to what they were in the original uncompressed table */
+    fits_modify_key_lng(outfptr, "NAXIS1", naxis1, "&", status);
+    fits_modify_key_lng(outfptr, "NAXIS2", naxis2, "&", status);
+    fits_modify_key_lng(outfptr, "PCOUNT", pcount, "&", status);
+
+    fits_delete_key(outfptr, "ZTABLE", status);
+    fits_delete_key(outfptr, "ZNAXIS1", status);
+    fits_delete_key(outfptr, "ZNAXIS2", status);
+    fits_delete_key(outfptr, "ZPCOUNT", status);
+    fits_delete_key(outfptr, "ZTILELEN", status);
+    tstatus = 0;
+    fits_delete_key(outfptr, "ZHEAPPTR", &tstatus);
+
+    /**** get the compression method that was used for each column ****/
+    for (ii = 0; ii < ncols; ii++) {
+
+	/* construct the ZCTYPn keyword name then read the keyword */
+	fits_make_keyn("ZCTYP", ii+1, keyname, status);
+	tstatus = 0;
+        fits_read_key(infptr, TSTRING, keyname, zvalue, NULL, &tstatus);
+	if (tstatus) {
+           zctype[ii] = GZIP_2;
+	} else {
+	   if (!strcmp(zvalue, "GZIP_2")) {
+               zctype[ii] = GZIP_2;
+	   } else if (!strcmp(zvalue, "GZIP_1")) {
+               zctype[ii] = GZIP_1;
+	   } else if (!strcmp(zvalue, "RICE_1")) {
+               zctype[ii] = RICE_1;
+	   } else {
+	       ffpmsg("Unrecognized ZCTYPn keyword compression code:");
+	       ffpmsg(zvalue);
+	       *status = DATA_DECOMPRESSION_ERR;
+	       return(*status);
+	   }
+	   
+	   /* delete this keyword from the uncompressed header */
+	   fits_delete_key(outfptr, keyname, status);
+	}
+    }
+
+    /**** allocate space for the full transposed and untransposed table ****/
+    fullsize = naxis1 * naxis2;
+    transbuffer = malloc(fullsize);
+    if (!transbuffer) {
+        ffpmsg("Could not allocate buffer for shuffled table");
+        *status = MEMORY_ALLOCATION;
+        return(*status);
+    }
+
+    buffer = malloc(fullsize);
+    if (!buffer) {
+        ffpmsg("Could not allocate buffer for unshuffled table");
+        *status = MEMORY_ALLOCATION;
+        return(*status);
+    }
+
+    /*** loop over each column: read and uncompress the bytes ****/
+    rmajor_colstart[0] = 0;
+    cmajor_colstart[0] = 0;
+    for (ii = 0; ii < ncols; ii++) {
+
+	/* get the original column type, repeat count, and unit width */
+	fits_make_keyn("ZFORM", ii+1, keyname, status);
+	fits_read_key(infptr, TSTRING, keyname, tform, comm, status);
+
+	/* restore the original TFORM value and comment */
+        keyname[0] = 'T';
+	fits_modify_key_str(outfptr, keyname, tform, comm, status);
+
+	/* now delete the ZFORM keyword */
+        keyname[0] = 'Z';
+	fits_delete_key(outfptr, keyname, status);
+
+	cptr = tform;
+	while(isdigit(*cptr)) cptr++;
+	colcode[ii] = *cptr; /* save the column type code */
+
+        fits_binary_tform(tform, &coltype, &repeat, &width, status);
+
+	/* deal with special cases */
+	if (coltype == TBIT) { 
+	    repeat = (repeat + 7) / 8 ;   /* convert from bits to bytes */
+	} else if (coltype == TSTRING) {
+	    width = 1;
+	} else if (coltype < 0) {  /* pointer to variable length array */
+	    if (colcode[ii] == 'P')
+	       width = 8;  /* this is a 'P' column */
+	    else
+	       width = 16;  /* this is a 'Q' not a 'P' column */
+	}
+
+	rmajor_repeat[ii] = repeat;
+	cmajor_repeat[ii] = repeat * naxis2;
+
+	/* width (in bytes) of each field in the row-major table */
+	rmajor_colwidth[ii] = rmajor_repeat[ii] * width;
+
+	/* starting offset of each field in the column-major table */
+	cmajor_colstart[ii + 1] = cmajor_colstart[ii] + rmajor_colwidth[ii] * naxis2;
+
+	/* length of each sequence of bytes, after sorting them in signicant order */
+	cmajor_bytespan[ii] = (rmajor_colwidth[ii] * naxis2) / width;
+
+	/* starting offset of each field in the  row-major table */
+	rmajor_colstart[ii + 1] = rmajor_colstart[ii] + rmajor_colwidth[ii];
+
+	/* read compressed bytes from input table */
+	fits_read_descript(infptr, ii + 1, 1, &vla_repeat, NULL, status);
+	
+	/* allocate memory and read in the compressed bytes */
+	ptr = malloc(vla_repeat);
+	if (!ptr) {
+            ffpmsg("Could not allocate buffer for compressed bytes");
+            *status = MEMORY_ALLOCATION;
+            return(*status);
+	}
+
+	fits_set_tscale(infptr, ii + 1, 1.0, 0.0, status);  /* turn off any data scaling, first */
+	fits_read_col_byt(infptr, ii + 1, 1, 1, vla_repeat, 0, (unsigned char *) ptr, &anynull, status);
+
+        cptr = transbuffer + cmajor_colstart[ii];
+	
+	fullsize = (size_t) (cmajor_colstart[ii+1] - cmajor_colstart[ii]);
+
+	switch (colcode[ii]) {
+	/* separate the byte planes for the 2-byte, 4-byte, and 8-byte numeric columns */
+
+
+	case 'I':
+
+	    if (zctype[ii] == RICE_1) {
+   	        dlen = fits_rdecomp_short((unsigned char *)ptr, vla_repeat, (unsigned short *)cptr, 
+		       fullsize / 2, 32);
+#if BYTESWAPPED
+                ffswap2((short *) cptr, fullsize / 2); 
+#endif
+	    } else { /* gunzip the data into the correct location */
+	        uncompress2mem_from_mem(ptr, vla_repeat, &cptr, &fullsize, realloc, &dlen, status);        
+	    }
+	  break;
+
+	case 'J':
+
+	    if (zctype[ii] == RICE_1) {
+   	        dlen = fits_rdecomp ((unsigned char *) ptr, vla_repeat, (unsigned int *)cptr, 
+		     fullsize / 4, 32);
+#if BYTESWAPPED
+                ffswap4((int *) cptr,  fullsize / 4); 
+#endif
+	    } else { /* gunzip the data into the correct location */
+	        uncompress2mem_from_mem(ptr, vla_repeat, &cptr, &fullsize, realloc, &dlen, status);        
+	    }
+	  break;
+
+	case 'B':
+
+	    if (zctype[ii] == RICE_1) {
+   	        dlen = fits_rdecomp_byte ((unsigned char *) ptr, vla_repeat, (unsigned char *)cptr, 
+		     fullsize, 32);
+	    } else { /* gunzip the data into the correct location */
+	        uncompress2mem_from_mem(ptr, vla_repeat, &cptr, &fullsize, realloc, &dlen, status);        
+	    }
+	  break;
+
+	default: 
+	    /* gunzip the data into the correct location in the full table buffer */
+	    uncompress2mem_from_mem(ptr, vla_repeat,
+	        &cptr,  &fullsize, realloc, &dlen, status);              
+
+	} /* end of switch block */
+
+	free(ptr);
+    }
+
+    /* now transpose the rows and columns (from transbuffer to buffer) */
+    ptr = transbuffer;
+    for (ii = 0; ii < ncols; ii++) {  /* loop over columns */
+
+      if ((zctype[ii] == GZIP_2)) {  /*  need to unshuffle the bytes */
+
+	switch (colcode[ii]) {
+	
+	/* recombine the byte planes for the 2-byte, 4-byte, and 8-byte numeric columns */
+
+	case 'I':
+	  /* get the 1st byte of each I*2 value */
+          for (jj = 0; jj < naxis2; jj++) {  /* loop over number of rows in the output table */
+	    cptr = buffer + (rmajor_colstart[ii] + (jj * rmajor_colstart[ncols]));  
+	    for (kk = 0; kk < rmajor_repeat[ii]; kk++) {
+	      *cptr = *ptr;  /* copy 1 byte */
+	      ptr++;
+	      cptr += 2;  
+	    }
+	  }
+
+	  /* get the 2nd byte of each I*2 value */
+          for (jj = 0; jj < naxis2; jj++) {  /* loop over number of rows in the output table */
+	    cptr = buffer + (rmajor_colstart[ii] + (jj * rmajor_colstart[ncols]) + 1);  
+	    for (kk = 0; kk < rmajor_repeat[ii]; kk++) {
+	      *cptr = *ptr;  /* copy 1 byte */
+	      ptr++;
+	      cptr += 2;  
+	    }
+	  }
+
+	  break;
+
+	case 'J':
+	case 'E':
+
+	  /* get the 1st byte of each 4-byte value */
+          for (jj = 0; jj < naxis2; jj++) {  /* loop over number of rows in the output table */
+	    cptr = buffer + (rmajor_colstart[ii] + (jj * rmajor_colstart[ncols]));  
+	    for (kk = 0; kk < rmajor_repeat[ii]; kk++) {
+	      *cptr = *ptr;  /* copy 1 byte */
+	      ptr++;
+	      cptr += 4;  
+	    }
+	  }
+
+	  /* get the 2nd byte  */
+          for (jj = 0; jj < naxis2; jj++) {  /* loop over number of rows in the output table */
+	    cptr = buffer + (rmajor_colstart[ii] + (jj * rmajor_colstart[ncols]) + 1);  
+	    for (kk = 0; kk < rmajor_repeat[ii]; kk++) {
+	      *cptr = *ptr;  /* copy 1 byte */
+	      ptr++;
+	      cptr += 4;  
+	    }
+	  }
+
+	  /* get the 3rd byte  */
+          for (jj = 0; jj < naxis2; jj++) {  /* loop over number of rows in the output table */
+	    cptr = buffer + (rmajor_colstart[ii] + (jj * rmajor_colstart[ncols]) + 2);  
+	    for (kk = 0; kk < rmajor_repeat[ii]; kk++) {
+	      *cptr = *ptr;  /* copy 1 byte */
+	      ptr++;
+	      cptr += 4;  
+	    }
+	  }
+
+	  /* get the 4th byte  */
+          for (jj = 0; jj < naxis2; jj++) {  /* loop over number of rows in the output table */
+	    cptr = buffer + (rmajor_colstart[ii] + (jj * rmajor_colstart[ncols]) + 3);  
+	    for (kk = 0; kk < rmajor_repeat[ii]; kk++) {
+	      *cptr = *ptr;  /* copy 1 byte */
+	      ptr++;
+	      cptr += 4;  
+	    }
+	  }
+
+	  break;
+
+	case 'D':
+	case 'K':
+
+	  /* get the 1st byte of each 8-byte value */
+          for (jj = 0; jj < naxis2; jj++) {  /* loop over number of rows in the output table */
+	    cptr = buffer + (rmajor_colstart[ii] + (jj * rmajor_colstart[ncols]));  
+	    for (kk = 0; kk < rmajor_repeat[ii]; kk++) {
+	      *cptr = *ptr;  /* copy 1 byte */
+	      ptr++;
+	      cptr += 8;  
+	    }
+	  }
+
+	  /* get the 2nd byte  */
+          for (jj = 0; jj < naxis2; jj++) {  /* loop over number of rows in the output table */
+	    cptr = buffer + (rmajor_colstart[ii] + (jj * rmajor_colstart[ncols]) + 1);  
+	    for (kk = 0; kk < rmajor_repeat[ii]; kk++) {
+	      *cptr = *ptr;  /* copy 1 byte */
+	      ptr++;
+	      cptr += 8;  
+	    }
+	  }
+
+	  /* get the 3rd byte  */
+          for (jj = 0; jj < naxis2; jj++) {  /* loop over number of rows in the output table */
+	    cptr = buffer + (rmajor_colstart[ii] + (jj * rmajor_colstart[ncols]) + 2);  
+	    for (kk = 0; kk < rmajor_repeat[ii]; kk++) {
+	      *cptr = *ptr;  /* copy 1 byte */
+	      ptr++;
+	      cptr += 8;  
+	    }
+	  }
+
+	  /* get the 4th byte  */
+          for (jj = 0; jj < naxis2; jj++) {  /* loop over number of rows in the output table */
+	    cptr = buffer + (rmajor_colstart[ii] + (jj * rmajor_colstart[ncols]) + 3);  
+	    for (kk = 0; kk < rmajor_repeat[ii]; kk++) {
+	      *cptr = *ptr;  /* copy 1 byte */
+	      ptr++;
+	      cptr += 8;  
+	    }
+	  }
+
+	  /* get the 5th byte */
+          for (jj = 0; jj < naxis2; jj++) {  /* loop over number of rows in the output table */
+	    cptr = buffer + (rmajor_colstart[ii] + (jj * rmajor_colstart[ncols]) + 4);  
+	    for (kk = 0; kk < rmajor_repeat[ii]; kk++) {
+	      *cptr = *ptr;  /* copy 1 byte */
+	      ptr++;
+	      cptr += 8;  
+	    }
+	  }
+
+	  /* get the 6th byte  */
+          for (jj = 0; jj < naxis2; jj++) {  /* loop over number of rows in the output table */
+	    cptr = buffer + (rmajor_colstart[ii] + (jj * rmajor_colstart[ncols]) + 5);  
+	    for (kk = 0; kk < rmajor_repeat[ii]; kk++) {
+	      *cptr = *ptr;  /* copy 1 byte */
+	      ptr++;
+	      cptr += 8;  
+	    }
+	  }
+
+	  /* get the 7th byte  */
+          for (jj = 0; jj < naxis2; jj++) {  /* loop over number of rows in the output table */
+	    cptr = buffer + (rmajor_colstart[ii] + (jj * rmajor_colstart[ncols]) + 6);  
+	    for (kk = 0; kk < rmajor_repeat[ii]; kk++) {
+	      *cptr = *ptr;  /* copy 1 byte */
+	      ptr++;
+	      cptr += 8;  
+	    }
+	  }
+
+	  /* get the 8th byte  */
+          for (jj = 0; jj < naxis2; jj++) {  /* loop over number of rows in the output table */
+	    cptr = buffer + (rmajor_colstart[ii] + (jj * rmajor_colstart[ncols]) + 7);  
+	    for (kk = 0; kk < rmajor_repeat[ii]; kk++) {
+	      *cptr = *ptr;  /* copy 1 byte */
+	      ptr++;
+	      cptr += 8;  
+	    }
+	  }
+
+	  break;
+	default: /*  should never get here */
+            ffpmsg("Error: unexpected use of GZIP_2 to compress a column");
+	    *status = DATA_DECOMPRESSION_ERR;
+            return(*status);
+
+        }  /* end of switch */
+
+      } else {  /* not GZIP_2, so just transpose the bytes */
+
+          for (jj = 0; jj < naxis2; jj++) {  /* loop over number of rows in the output table */
+	    cptr = buffer + (rmajor_colstart[ii] + jj * rmajor_colstart[ncols]);   /* addr to copy to */
+	    memcpy(cptr, ptr, (size_t) rmajor_colwidth[ii]);
+	 
+	    ptr += (rmajor_colwidth[ii]);
+	  }
+      }
+
+    }  /* end of ncols loop */
+
+    /* copy the buffer of data to the output data unit */
+    fits_get_hduaddrll(outfptr, &headstart, &datastart, &dataend, status);        
+    ffmbyt(outfptr, datastart, 1, status);
+    ffpbyt(outfptr, naxis1 * naxis2, buffer, status);
+    free(buffer);
+    free(transbuffer);
+	
+    /* reset internal table structure parameters */
+    fits_set_hdustruc(outfptr, status);
+
+    /* unshuffle the heap, if it exists */
+    fits_gunzip_heap(infptr, outfptr, status);
+
+    return(*status);
+}
+
+/*--------------------------------------------------------------------------*/
+int fits_gzip_datablocks(fitsfile *fptr, size_t *size, int *status)
+/*
+  GZIP compress all the data blocks in the binary table HDU.
+  Store the size of the compressed byte stream in the PCOUNT keyword.  
+  Save the original PCOUNT value in the ZPCOUNT keyword.  
+*/
+{ 
+    long headstart, datastart, dataend;
+    char *ptr, *cptr, *iptr;
+    size_t dlen, datasize, ii;
+
+    /* allocate memory for the data and the compressed data */
+    fits_get_hduaddr(fptr, &headstart, &datastart, &dataend, status); 
+    datasize = dataend - datastart;
+    ptr = malloc(datasize);
+    cptr = malloc(datasize);
+    if (!ptr || !cptr) {
+        ffpmsg("data memory allocation error in fits_gzip_datablocks\n");
+	return(-1);
+    }
+
+    /* copy the data into memory */
+    ffmbyt(fptr,datastart, REPORT_EOF, status);
+    iptr = ptr;
+    for (ii = 0; ii < datasize; ii+= 2880) {
+	ffgbyt(fptr, 2880, iptr, status);
+	iptr += 2880;
+    }
+	
+    /* gzip compress the data */
+    compress2mem_from_mem(ptr, datasize,
+	&cptr,  &datasize, realloc, 
+	&dlen, status);        
+
+    *size = dlen;
+
+    free(cptr);   /* don't need the compressed data any more */
+    free(ptr);  /* don't need the original data any more */
+   
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+static int fits_gzip_heap(fitsfile *infptr, fitsfile *outfptr, int *status)
+
+/*
+  Compress the binary table heap in the input file and write it to the output file.
+  First, shuffle the bytes for the numeric arrays in the heap, so that
+  the bytes are sorted in order of decreasing significance.  Then gzip
+  the entire heap as a single block of data.  Then append this compressed heap
+  to the end of any existing data in the output file heap.
+*/
+{ 
+    LONGLONG datastart, dataend, nrows, naxis1, heapsize, length, offset, pcount, jj;
+    int coltype, ncols, ii;
+    char *heap, *compheap, card[FLEN_CARD];
+    size_t theapsize, compsize;
+    
+    if (*status > 0)
+        return(*status);
+
+    /* insert a set of COMMENT keyword to indicate that this is a compressed table */
+    fits_read_card(outfptr, "TFIELDS", card, status);
+    fits_insert_card(outfptr, "COMMENT [FPACK] This is a compressed binary table generated by fpack.", status);
+    fits_insert_card(outfptr, "COMMENT [FPACK] It can be uncompressed using funpack.", status);
+    fits_insert_card(outfptr, "COMMENT [FPACK] fpack and funpack are available from the HEASARC Web site.", status);     
+    
+    /* get the size of the heap (value of PCOUNT keyword) */
+    fits_read_key(infptr, TLONGLONG, "PCOUNT", &heapsize, NULL, status);
+
+    /* return if there is no heap */
+    if (*status != 0 || heapsize == 0)
+        return(*status);
+
+    /* allocate memory for the heap and compressed heap */
+         
+    heap = malloc((size_t) heapsize);
+    if (!heap) {
+        ffpmsg("Could not allocate buffer for the heap (fits_gzip_heap");
+        *status = MEMORY_ALLOCATION;
+        return(*status);
+    }
+
+    compheap = malloc((size_t) heapsize);
+    if (!compheap) {
+        ffpmsg("Could not allocate buffer for compressed heap (fits_gzip_heap");
+ 	free(heap);
+        *status = MEMORY_ALLOCATION;
+        return(*status);
+    }
+
+    fits_get_hduaddrll(infptr, NULL, &datastart, NULL, status); 
+    fits_get_num_rowsll(infptr, &nrows, status);
+    fits_get_num_cols(infptr, &ncols, status);
+    fits_read_key(infptr, TLONGLONG, "NAXIS1", &naxis1, NULL, status);
+
+    /* move to start of the heap and copy the heap into memory */
+    ffmbyt(infptr, datastart + (nrows * naxis1), REPORT_EOF, status);
+    ffgbyt(infptr, heapsize, heap, status);
+    
+    /* shuffle the bytes for the numeric columns */
+    for (ii = 1; ii <= ncols; ii++) {
+
+        fits_get_coltype(infptr, ii, &coltype, NULL, NULL, status);
+
+	if (coltype >= 0) continue;   /* only interested in variable length columns */
+	
+	coltype = coltype * (-1);
+	
+	switch (coltype) {
+	/* shuffle the bytes for the 2-byte, 4-byte, and 8-byte numeric columns */
+	case TSHORT:
+
+	  for (jj = 1; jj <= nrows; jj++) {
+	    fits_read_descriptll(infptr, ii, jj, &length, &offset, status);
+	    fits_shuffle_2bytes(heap + offset, length, status);    
+	  }
+	  break;
+	
+	case TLONG:
+	case TFLOAT:
+	  for (jj = 1; jj <= nrows; jj++) {
+	    fits_read_descriptll(infptr, ii, jj, &length, &offset, status);
+	    fits_shuffle_4bytes(heap + offset, length, status);    
+	  }
+	  break;
+
+	case TDOUBLE:
+	case TLONGLONG:
+	  for (jj = 1; jj <= nrows; jj++) {
+	    fits_read_descriptll(infptr, ii, jj, &length, &offset, status);
+	    fits_shuffle_8bytes(heap + offset, length,  status);    
+	  }
+	  break;
+
+	default: /* don't have to do anything for other column types */
+	  break;
+
+	}   /* end of switch block */
+    }
+
+    /* gzip compress the shuffled heap */
+    theapsize = (size_t) heapsize;
+    compress2mem_from_mem(heap, (size_t) heapsize, &compheap,  &theapsize, 
+                          realloc, &compsize, status);        
+    free(heap);  /* don't need the uncompresse heap any more */
+    
+    /* update the internal pointers */
+    fits_set_hdustruc(outfptr, status);
+    
+    /* save offset to the start of the compressed heap, relative to the
+       start of the main data table in the ZHEAPPTR keyword, and
+       update PCOUNT to the new extended heap size */
+       
+    fits_read_key(outfptr, TLONGLONG, "PCOUNT", &pcount, NULL, status);
+    fits_get_num_rowsll(outfptr, &nrows, status);
+    fits_read_key(outfptr, TLONGLONG, "NAXIS1", &naxis1, NULL, status);
+
+    fits_write_key_lng(outfptr, "ZHEAPPTR", (LONGLONG) ((nrows * naxis1) + pcount), 
+                   "byte offset to compressed heap", status);
+    fits_modify_key_lng(outfptr, "PCOUNT", pcount + compsize, NULL, status);
+
+    /* now append the compressed heap to the heap in the output file */
+    dataend = (outfptr->Fptr)->datastart + (outfptr->Fptr)->heapstart + 
+                    (outfptr->Fptr)->heapsize;
+
+    ffmbyt(outfptr, dataend, IGNORE_EOF, status);
+    ffpbyt(outfptr, compsize, compheap, status);
+    free(compheap);   
+    
+    /* also update the internal pointer to the heap size */
+    (outfptr->Fptr)->heapsize = (outfptr->Fptr)->heapsize + compsize;
+
+    /* update the internal pointers again */
+    fits_set_hdustruc(outfptr, status);
+ 
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+static int fits_shuffle_2bytes(char *heap, LONGLONG length, int *status)
+
+/* shuffle the bytes in an array of 2-byte integers in the heap */
+
+{
+    LONGLONG ii;
+    char *ptr, *cptr, *heapptr;
+    
+    ptr = malloc((size_t) (length * 2));
+    heapptr = heap;
+    cptr = ptr;
+    
+    for (ii = 0; ii < length; ii++) {
+       *cptr = *heapptr;
+       heapptr++;
+       *(cptr + length) = *heapptr;
+       heapptr++;
+       cptr++;
+    }
+         
+    memcpy(heap, ptr, (size_t) (length * 2));
+    free(ptr);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+static int fits_shuffle_4bytes(char *heap, LONGLONG length, int *status)
+
+/* shuffle the bytes in an array of 4-byte integers or floats  */
+
+{
+    LONGLONG ii;
+    char *ptr, *cptr, *heapptr;
+    
+    ptr = malloc((size_t) (length * 4));
+    if (!ptr) {
+      ffpmsg("malloc failed\n");
+      return(*status);
+    }
+
+    heapptr = heap;
+    cptr = ptr;
+ 
+    for (ii = 0; ii < length; ii++) {
+       *cptr = *heapptr;
+       heapptr++;
+       *(cptr + length) = *heapptr;
+       heapptr++;
+       *(cptr + (length * 2)) = *heapptr;
+       heapptr++;
+       *(cptr + (length * 3)) = *heapptr;
+       heapptr++;
+       cptr++;
+    }
+        
+    memcpy(heap, ptr, (size_t) (length * 4));
+    free(ptr);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+static int fits_shuffle_8bytes(char *heap, LONGLONG length, int *status)
+
+/* shuffle the bytes in an array of 8-byte integers or doubles in the heap */
+
+{
+    LONGLONG ii;
+    char *ptr, *cptr, *heapptr;
+    
+    ptr = calloc(1, (size_t) (length * 8));
+    heapptr = heap;
+    
+/* for some bizarre reason this loop fails to compile under OpenSolaris using
+   the proprietary SunStudioExpress C compiler;  use the following equivalent
+   loop instead.
+   
+    cptr = ptr;
+
+    for (ii = 0; ii < length; ii++) {
+       *cptr = *heapptr;
+       heapptr++;
+       *(cptr + length) = *heapptr;
+       heapptr++;
+       *(cptr + (length * 2)) = *heapptr;
+       heapptr++;
+       *(cptr + (length * 3)) = *heapptr;
+       heapptr++;
+       *(cptr + (length * 4)) = *heapptr;
+       heapptr++;
+       *(cptr + (length * 5)) = *heapptr;
+       heapptr++;
+       *(cptr + (length * 6)) = *heapptr;
+       heapptr++;
+       *(cptr + (length * 7)) = *heapptr;
+       heapptr++;
+       cptr++;
+     }
+*/
+     for (ii = 0; ii < length; ii++) {
+        cptr = ptr + ii;
+
+        *cptr = *heapptr;
+
+        heapptr++;
+        cptr += length;
+        *cptr = *heapptr;
+
+        heapptr++;
+        cptr += length;
+        *cptr = *heapptr;
+
+        heapptr++;
+        cptr += length;
+        *cptr = *heapptr;
+
+        heapptr++;
+        cptr += length;
+        *cptr = *heapptr;
+
+        heapptr++;
+        cptr += length;
+        *cptr = *heapptr;
+
+        heapptr++;
+        cptr += length;
+        *cptr = *heapptr;
+
+        heapptr++;
+        cptr += length;
+        *cptr = *heapptr;
+
+        heapptr++;
+     }
+        
+    memcpy(heap, ptr, (size_t) (length * 8));
+    free(ptr);
+ 
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+static int fits_gunzip_heap(fitsfile *infptr, fitsfile *outfptr, int *status)
+
+/*
+   inverse of the fits_gzip_heap function: uncompress and unshuffle the heap
+   in the input file and write it to the output file
+*/
+{ 
+    LONGLONG datastart, nrows, naxis1, length, offset, pcount, jj;
+    LONGLONG zpcount, zheapptr, cheapsize;
+    int coltype, ncols, ii;
+    char *heap, *compheap;
+    size_t arraysize, theapsize;
+
+    if (*status > 0)
+        return(*status);
+
+    /* first, delete any COMMENT keywords written by fits_gzip_heap */
+    while (*status == 0) {
+        fits_delete_str(outfptr, "COMMENT [FPACK]", status);
+    }
+    if (*status == KEY_NO_EXIST) *status = 0;
+
+    /* ZPCOUNT = size of original uncompressed heap */
+    fits_read_key(infptr, TLONGLONG, "ZPCOUNT", &zpcount, NULL, status);
+
+    /* just return if there is no heap */
+    if (*status != 0 || zpcount == 0)
+        return(*status);
+
+    fits_get_num_rowsll(infptr, &nrows, status);
+    fits_read_key(infptr, TLONGLONG, "NAXIS1", &naxis1, NULL, status);
+
+    /* ZHEAPPTR = offset to the start of the compressed heap */
+    fits_read_key(infptr, TLONGLONG, "ZHEAPPTR", &zheapptr, NULL, status);
+
+    /* PCOUNT = total size of the compressed 2D table plus the compressed heap */
+    fits_read_key(infptr, TLONGLONG, "PCOUNT", &pcount, NULL, status);
+
+    /* size of the compressed heap */
+    cheapsize = pcount - (zheapptr - (naxis1 * nrows));
+
+    /* allocate memory for the heap and uncompressed heap */
+    arraysize = (size_t) zpcount;
+    heap = malloc(arraysize);
+    if (!heap) {
+        ffpmsg("Could not allocate buffer for the heap (fits_gunzip_heap");
+        *status = MEMORY_ALLOCATION;
+        return(*status);
+    }
+
+    compheap = malloc((size_t) cheapsize);
+    if (!compheap) {
+        ffpmsg("Could not allocate buffer for compressed heap (fits_gunzip_heap");
+ 	free(heap);
+        *status = MEMORY_ALLOCATION;
+        return(*status);
+    }
+
+    fits_get_hduaddrll(infptr, NULL, &datastart, NULL, status); 
+
+    /* read the compressed heap into memory */
+    ffmbyt(infptr, datastart + zheapptr, REPORT_EOF, status);
+    ffgbyt(infptr, cheapsize, compheap, status);
+
+    /* uncompress the heap */
+    theapsize = (size_t) zpcount;
+    uncompress2mem_from_mem(compheap, (size_t) cheapsize, &heap, &arraysize, 
+        realloc, &theapsize, status);        
+
+    free(compheap);   /* don't need the compressed heap any more */
+
+    if (theapsize != zpcount) {
+       /* something is wrong */
+       ffpmsg("uncompressed heap size != to ZPCOUNT");
+       free(heap);
+       *status = MEMORY_ALLOCATION;
+       return(*status);
+    }
+
+    /* get dimensions of the uncompressed table */
+    fits_get_num_rowsll(outfptr, &nrows, status);
+    fits_read_key(outfptr, TLONGLONG, "NAXIS1", &naxis1, NULL, status);
+    fits_get_num_cols(outfptr, &ncols, status);
+
+    for (ii = ncols; ii > 0; ii--) {
+
+        fits_get_coltype(outfptr, ii, &coltype, NULL, NULL, status);
+
+	if (coltype >= 0) continue;   /* only interested in variable length columns */
+	
+	coltype = coltype * (-1);
+	
+	switch (coltype) {
+	/* recombine the byte planes for the 2-byte, 4-byte, and 8-byte numeric columns */
+	case TSHORT:
+
+	  for (jj = nrows; jj > 0; jj--) {
+	    fits_read_descriptll(outfptr, ii, jj, &length, &offset, status);
+	    fits_unshuffle_2bytes(heap + offset, length, status);    
+	  }
+	  break;
+	
+	case TLONG:
+	case TFLOAT:
+	  for (jj = nrows; jj > 0; jj--) {
+	    fits_read_descriptll(outfptr, ii, jj, &length, &offset, status);
+	    fits_unshuffle_4bytes(heap + offset, length, status);    
+	  }
+	  break;
+
+	case TDOUBLE:
+	case TLONGLONG:
+	  for (jj = nrows; jj > 0; jj--) {
+	    fits_read_descriptll(outfptr, ii, jj, &length, &offset, status);
+	    fits_unshuffle_8bytes(heap + offset, length,  status);    
+	  }
+	  break;
+
+	default: /* don't need to recombine bytes for other column types */
+	  break;
+
+	}   /* end of switch block */
+    }
+
+    /* copy the unshuffled heap back to the output file */
+    fits_get_hduaddrll(outfptr, NULL, &datastart, NULL, status); 
+
+    ffmbyt(outfptr, datastart + (nrows * naxis1), IGNORE_EOF, status);
+    ffpbyt(outfptr, zpcount, heap, status);
+
+    free(heap);   
+
+    /* also update the internal pointer to the heap size */
+    (outfptr->Fptr)->heapsize = zpcount;
+
+    /* update the internal pointers again */
+    fits_set_hdustruc(outfptr, status);
+ 
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+static int fits_unshuffle_2bytes(char *heap, LONGLONG length, int *status)
+
+/* unshuffle the bytes in an array of 2-byte integers */
+
+{
+    LONGLONG ii;
+    char *ptr, *cptr, *heapptr;
+    
+    ptr = malloc((size_t) (length * 2));
+    heapptr = heap + (2 * length) - 1;
+    cptr = ptr + (2 * length) - 1;
+    
+    for (ii = 0; ii < length; ii++) {
+       *cptr = *heapptr;
+       cptr--;
+       *cptr = *(heapptr - length);
+       cptr--;
+       heapptr--;
+    }
+         
+    memcpy(heap, ptr, (size_t) (length * 2));
+    free(ptr);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+static int fits_unshuffle_4bytes(char *heap, LONGLONG length, int *status)
+
+/* unshuffle the bytes in an array of 4-byte integers or floats */
+
+{
+    LONGLONG ii;
+    char *ptr, *cptr, *heapptr;
+    
+    ptr = malloc((size_t) (length * 4));
+    heapptr = heap + (4 * length) -1;
+    cptr = ptr + (4 * length) -1;
+ 
+    for (ii = 0; ii < length; ii++) {
+       *cptr = *heapptr;
+       cptr--;
+       *cptr = *(heapptr - length);
+       cptr--;
+       *cptr = *(heapptr - (2 * length));
+       cptr--;
+       *cptr = *(heapptr - (3 * length));
+       cptr--;
+       heapptr--;
+    }
+        
+    memcpy(heap, ptr, (size_t) (length * 4));
+    free(ptr);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+static int fits_unshuffle_8bytes(char *heap, LONGLONG length, int *status)
+
+/* unshuffle the bytes in an array of 8-byte integers or doubles */
+
+{
+    LONGLONG ii;
+    char *ptr, *cptr, *heapptr;
+    
+    ptr = malloc((size_t) (length * 8));
+    heapptr = heap + (8 * length) - 1;
+    cptr = ptr + (8 * length)  -1;
+    
+    for (ii = 0; ii < length; ii++) {
+       *cptr = *heapptr;
+       cptr--;
+       *cptr = *(heapptr - length);
+       cptr--;
+       *cptr = *(heapptr - (2 * length));
+       cptr--;
+       *cptr = *(heapptr - (3 * length));
+       cptr--;
+       *cptr = *(heapptr - (4 * length));
+       cptr--;
+       *cptr = *(heapptr - (5 * length));
+       cptr--;
+       *cptr = *(heapptr - (6 * length));
+       cptr--;
+       *cptr = *(heapptr - (7 * length));
+       cptr--;
+       heapptr--;
+    }
+       
+    memcpy(heap, ptr, (size_t) (length * 8));
+    free(ptr);
+    return(*status);
+}
diff --git a/src/plugins/cfitsio/infback.c b/src/plugins/cfitsio/infback.c
new file mode 100644
index 0000000..af3a8c9
--- /dev/null
+++ b/src/plugins/cfitsio/infback.c
@@ -0,0 +1,632 @@
+/* infback.c -- inflate using a call-back interface
+ * Copyright (C) 1995-2009 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/*
+   This code is largely copied from inflate.c.  Normally either infback.o or
+   inflate.o would be linked into an application--not both.  The interface
+   with inffast.c is retained so that optimized assembler-coded versions of
+   inflate_fast() can be used with either inflate.c or infback.c.
+ */
+
+#include "zutil.h"
+#include "inftrees.h"
+#include "inflate.h"
+#include "inffast.h"
+
+/* function prototypes */
+local void fixedtables OF((struct inflate_state FAR *state));
+
+/*
+   strm provides memory allocation functions in zalloc and zfree, or
+   Z_NULL to use the library memory allocation functions.
+
+   windowBits is in the range 8..15, and window is a user-supplied
+   window and output buffer that is 2**windowBits bytes.
+ */
+int ZEXPORT inflateBackInit_(strm, windowBits, window, version, stream_size)
+z_streamp strm;
+int windowBits;
+unsigned char FAR *window;
+const char *version;
+int stream_size;
+{
+    struct inflate_state FAR *state;
+
+    if (version == Z_NULL || version[0] != ZLIB_VERSION[0] ||
+        stream_size != (int)(sizeof(z_stream)))
+        return Z_VERSION_ERROR;
+    if (strm == Z_NULL || window == Z_NULL ||
+        windowBits < 8 || windowBits > 15)
+        return Z_STREAM_ERROR;
+    strm->msg = Z_NULL;                 /* in case we return an error */
+    if (strm->zalloc == (alloc_func)0) {
+        strm->zalloc = zcalloc;
+        strm->opaque = (voidpf)0;
+    }
+    if (strm->zfree == (free_func)0) strm->zfree = zcfree;
+    state = (struct inflate_state FAR *)ZALLOC(strm, 1,
+                                               sizeof(struct inflate_state));
+    if (state == Z_NULL) return Z_MEM_ERROR;
+    Tracev((stderr, "inflate: allocated\n"));
+    strm->state = (struct internal_state FAR *)state;
+    state->dmax = 32768U;
+    state->wbits = windowBits;
+    state->wsize = 1U << windowBits;
+    state->window = window;
+    state->wnext = 0;
+    state->whave = 0;
+    return Z_OK;
+}
+
+/*
+   Return state with length and distance decoding tables and index sizes set to
+   fixed code decoding.  Normally this returns fixed tables from inffixed.h.
+   If BUILDFIXED is defined, then instead this routine builds the tables the
+   first time it's called, and returns those tables the first time and
+   thereafter.  This reduces the size of the code by about 2K bytes, in
+   exchange for a little execution time.  However, BUILDFIXED should not be
+   used for threaded applications, since the rewriting of the tables and virgin
+   may not be thread-safe.
+ */
+local void fixedtables(state)
+struct inflate_state FAR *state;
+{
+#ifdef BUILDFIXED
+    static int virgin = 1;
+    static code *lenfix, *distfix;
+    static code fixed[544];
+
+    /* build fixed huffman tables if first call (may not be thread safe) */
+    if (virgin) {
+        unsigned sym, bits;
+        static code *next;
+
+        /* literal/length table */
+        sym = 0;
+        while (sym < 144) state->lens[sym++] = 8;
+        while (sym < 256) state->lens[sym++] = 9;
+        while (sym < 280) state->lens[sym++] = 7;
+        while (sym < 288) state->lens[sym++] = 8;
+        next = fixed;
+        lenfix = next;
+        bits = 9;
+        inflate_table(LENS, state->lens, 288, &(next), &(bits), state->work);
+
+        /* distance table */
+        sym = 0;
+        while (sym < 32) state->lens[sym++] = 5;
+        distfix = next;
+        bits = 5;
+        inflate_table(DISTS, state->lens, 32, &(next), &(bits), state->work);
+
+        /* do this just once */
+        virgin = 0;
+    }
+#else /* !BUILDFIXED */
+#   include "inffixed.h"
+#endif /* BUILDFIXED */
+    state->lencode = lenfix;
+    state->lenbits = 9;
+    state->distcode = distfix;
+    state->distbits = 5;
+}
+
+/* Macros for inflateBack(): */
+
+/* Load returned state from inflate_fast() */
+#define LOAD() \
+    do { \
+        put = strm->next_out; \
+        left = strm->avail_out; \
+        next = strm->next_in; \
+        have = strm->avail_in; \
+        hold = state->hold; \
+        bits = state->bits; \
+    } while (0)
+
+/* Set state from registers for inflate_fast() */
+#define RESTORE() \
+    do { \
+        strm->next_out = put; \
+        strm->avail_out = left; \
+        strm->next_in = next; \
+        strm->avail_in = have; \
+        state->hold = hold; \
+        state->bits = bits; \
+    } while (0)
+
+/* Clear the input bit accumulator */
+#define INITBITS() \
+    do { \
+        hold = 0; \
+        bits = 0; \
+    } while (0)
+
+/* Assure that some input is available.  If input is requested, but denied,
+   then return a Z_BUF_ERROR from inflateBack(). */
+#define PULL() \
+    do { \
+        if (have == 0) { \
+            have = in(in_desc, &next); \
+            if (have == 0) { \
+                next = Z_NULL; \
+                ret = Z_BUF_ERROR; \
+                goto inf_leave; \
+            } \
+        } \
+    } while (0)
+
+/* Get a byte of input into the bit accumulator, or return from inflateBack()
+   with an error if there is no input available. */
+#define PULLBYTE() \
+    do { \
+        PULL(); \
+        have--; \
+        hold += (unsigned long)(*next++) << bits; \
+        bits += 8; \
+    } while (0)
+
+/* Assure that there are at least n bits in the bit accumulator.  If there is
+   not enough available input to do that, then return from inflateBack() with
+   an error. */
+#define NEEDBITS(n) \
+    do { \
+        while (bits < (unsigned)(n)) \
+            PULLBYTE(); \
+    } while (0)
+
+/* Return the low n bits of the bit accumulator (n < 16) */
+#define BITS(n) \
+    ((unsigned)hold & ((1U << (n)) - 1))
+
+/* Remove n bits from the bit accumulator */
+#define DROPBITS(n) \
+    do { \
+        hold >>= (n); \
+        bits -= (unsigned)(n); \
+    } while (0)
+
+/* Remove zero to seven bits as needed to go to a byte boundary */
+#define BYTEBITS() \
+    do { \
+        hold >>= bits & 7; \
+        bits -= bits & 7; \
+    } while (0)
+
+/* Assure that some output space is available, by writing out the window
+   if it's full.  If the write fails, return from inflateBack() with a
+   Z_BUF_ERROR. */
+#define ROOM() \
+    do { \
+        if (left == 0) { \
+            put = state->window; \
+            left = state->wsize; \
+            state->whave = left; \
+            if (out(out_desc, put, left)) { \
+                ret = Z_BUF_ERROR; \
+                goto inf_leave; \
+            } \
+        } \
+    } while (0)
+
+/*
+   strm provides the memory allocation functions and window buffer on input,
+   and provides information on the unused input on return.  For Z_DATA_ERROR
+   returns, strm will also provide an error message.
+
+   in() and out() are the call-back input and output functions.  When
+   inflateBack() needs more input, it calls in().  When inflateBack() has
+   filled the window with output, or when it completes with data in the
+   window, it calls out() to write out the data.  The application must not
+   change the provided input until in() is called again or inflateBack()
+   returns.  The application must not change the window/output buffer until
+   inflateBack() returns.
+
+   in() and out() are called with a descriptor parameter provided in the
+   inflateBack() call.  This parameter can be a structure that provides the
+   information required to do the read or write, as well as accumulated
+   information on the input and output such as totals and check values.
+
+   in() should return zero on failure.  out() should return non-zero on
+   failure.  If either in() or out() fails, than inflateBack() returns a
+   Z_BUF_ERROR.  strm->next_in can be checked for Z_NULL to see whether it
+   was in() or out() that caused in the error.  Otherwise,  inflateBack()
+   returns Z_STREAM_END on success, Z_DATA_ERROR for an deflate format
+   error, or Z_MEM_ERROR if it could not allocate memory for the state.
+   inflateBack() can also return Z_STREAM_ERROR if the input parameters
+   are not correct, i.e. strm is Z_NULL or the state was not initialized.
+ */
+int ZEXPORT inflateBack(strm, in, in_desc, out, out_desc)
+z_streamp strm;
+in_func in;
+void FAR *in_desc;
+out_func out;
+void FAR *out_desc;
+{
+    struct inflate_state FAR *state;
+    unsigned char FAR *next;    /* next input */
+    unsigned char FAR *put;     /* next output */
+    unsigned have, left;        /* available input and output */
+    unsigned long hold;         /* bit buffer */
+    unsigned bits;              /* bits in bit buffer */
+    unsigned copy;              /* number of stored or match bytes to copy */
+    unsigned char FAR *from;    /* where to copy match bytes from */
+    code here;                  /* current decoding table entry */
+    code last;                  /* parent table entry */
+    unsigned len;               /* length to copy for repeats, bits to drop */
+    int ret;                    /* return code */
+    static const unsigned short order[19] = /* permutation of code lengths */
+        {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15};
+
+    /* Check that the strm exists and that the state was initialized */
+    if (strm == Z_NULL || strm->state == Z_NULL)
+        return Z_STREAM_ERROR;
+    state = (struct inflate_state FAR *)strm->state;
+
+    /* Reset the state */
+    strm->msg = Z_NULL;
+    state->mode = TYPE;
+    state->last = 0;
+    state->whave = 0;
+    next = strm->next_in;
+    have = next != Z_NULL ? strm->avail_in : 0;
+    hold = 0;
+    bits = 0;
+    put = state->window;
+    left = state->wsize;
+
+    /* Inflate until end of block marked as last */
+    for (;;)
+        switch (state->mode) {
+        case TYPE:
+            /* determine and dispatch block type */
+            if (state->last) {
+                BYTEBITS();
+                state->mode = DONE;
+                break;
+            }
+            NEEDBITS(3);
+            state->last = BITS(1);
+            DROPBITS(1);
+            switch (BITS(2)) {
+            case 0:                             /* stored block */
+                Tracev((stderr, "inflate:     stored block%s\n",
+                        state->last ? " (last)" : ""));
+                state->mode = STORED;
+                break;
+            case 1:                             /* fixed block */
+                fixedtables(state);
+                Tracev((stderr, "inflate:     fixed codes block%s\n",
+                        state->last ? " (last)" : ""));
+                state->mode = LEN;              /* decode codes */
+                break;
+            case 2:                             /* dynamic block */
+                Tracev((stderr, "inflate:     dynamic codes block%s\n",
+                        state->last ? " (last)" : ""));
+                state->mode = TABLE;
+                break;
+            case 3:
+                strm->msg = (char *)"invalid block type";
+                state->mode = BAD;
+            }
+            DROPBITS(2);
+            break;
+
+        case STORED:
+            /* get and verify stored block length */
+            BYTEBITS();                         /* go to byte boundary */
+            NEEDBITS(32);
+            if ((hold & 0xffff) != ((hold >> 16) ^ 0xffff)) {
+                strm->msg = (char *)"invalid stored block lengths";
+                state->mode = BAD;
+                break;
+            }
+            state->length = (unsigned)hold & 0xffff;
+            Tracev((stderr, "inflate:       stored length %u\n",
+                    state->length));
+            INITBITS();
+
+            /* copy stored block from input to output */
+            while (state->length != 0) {
+                copy = state->length;
+                PULL();
+                ROOM();
+                if (copy > have) copy = have;
+                if (copy > left) copy = left;
+                zmemcpy(put, next, copy);
+                have -= copy;
+                next += copy;
+                left -= copy;
+                put += copy;
+                state->length -= copy;
+            }
+            Tracev((stderr, "inflate:       stored end\n"));
+            state->mode = TYPE;
+            break;
+
+        case TABLE:
+            /* get dynamic table entries descriptor */
+            NEEDBITS(14);
+            state->nlen = BITS(5) + 257;
+            DROPBITS(5);
+            state->ndist = BITS(5) + 1;
+            DROPBITS(5);
+            state->ncode = BITS(4) + 4;
+            DROPBITS(4);
+#ifndef PKZIP_BUG_WORKAROUND
+            if (state->nlen > 286 || state->ndist > 30) {
+                strm->msg = (char *)"too many length or distance symbols";
+                state->mode = BAD;
+                break;
+            }
+#endif
+            Tracev((stderr, "inflate:       table sizes ok\n"));
+
+            /* get code length code lengths (not a typo) */
+            state->have = 0;
+            while (state->have < state->ncode) {
+                NEEDBITS(3);
+                state->lens[order[state->have++]] = (unsigned short)BITS(3);
+                DROPBITS(3);
+            }
+            while (state->have < 19)
+                state->lens[order[state->have++]] = 0;
+            state->next = state->codes;
+            state->lencode = (code const FAR *)(state->next);
+            state->lenbits = 7;
+            ret = inflate_table(CODES, state->lens, 19, &(state->next),
+                                &(state->lenbits), state->work);
+            if (ret) {
+                strm->msg = (char *)"invalid code lengths set";
+                state->mode = BAD;
+                break;
+            }
+            Tracev((stderr, "inflate:       code lengths ok\n"));
+
+            /* get length and distance code code lengths */
+            state->have = 0;
+            while (state->have < state->nlen + state->ndist) {
+                for (;;) {
+                    here = state->lencode[BITS(state->lenbits)];
+                    if ((unsigned)(here.bits) <= bits) break;
+                    PULLBYTE();
+                }
+                if (here.val < 16) {
+                    NEEDBITS(here.bits);
+                    DROPBITS(here.bits);
+                    state->lens[state->have++] = here.val;
+                }
+                else {
+                    if (here.val == 16) {
+                        NEEDBITS(here.bits + 2);
+                        DROPBITS(here.bits);
+                        if (state->have == 0) {
+                            strm->msg = (char *)"invalid bit length repeat";
+                            state->mode = BAD;
+                            break;
+                        }
+                        len = (unsigned)(state->lens[state->have - 1]);
+                        copy = 3 + BITS(2);
+                        DROPBITS(2);
+                    }
+                    else if (here.val == 17) {
+                        NEEDBITS(here.bits + 3);
+                        DROPBITS(here.bits);
+                        len = 0;
+                        copy = 3 + BITS(3);
+                        DROPBITS(3);
+                    }
+                    else {
+                        NEEDBITS(here.bits + 7);
+                        DROPBITS(here.bits);
+                        len = 0;
+                        copy = 11 + BITS(7);
+                        DROPBITS(7);
+                    }
+                    if (state->have + copy > state->nlen + state->ndist) {
+                        strm->msg = (char *)"invalid bit length repeat";
+                        state->mode = BAD;
+                        break;
+                    }
+                    while (copy--)
+                        state->lens[state->have++] = (unsigned short)len;
+                }
+            }
+
+            /* handle error breaks in while */
+            if (state->mode == BAD) break;
+
+            /* check for end-of-block code (better have one) */
+            if (state->lens[256] == 0) {
+                strm->msg = (char *)"invalid code -- missing end-of-block";
+                state->mode = BAD;
+                break;
+            }
+
+            /* build code tables -- note: do not change the lenbits or distbits
+               values here (9 and 6) without reading the comments in inftrees.h
+               concerning the ENOUGH constants, which depend on those values */
+            state->next = state->codes;
+            state->lencode = (code const FAR *)(state->next);
+            state->lenbits = 9;
+            ret = inflate_table(LENS, state->lens, state->nlen, &(state->next),
+                                &(state->lenbits), state->work);
+            if (ret) {
+                strm->msg = (char *)"invalid literal/lengths set";
+                state->mode = BAD;
+                break;
+            }
+            state->distcode = (code const FAR *)(state->next);
+            state->distbits = 6;
+            ret = inflate_table(DISTS, state->lens + state->nlen, state->ndist,
+                            &(state->next), &(state->distbits), state->work);
+            if (ret) {
+                strm->msg = (char *)"invalid distances set";
+                state->mode = BAD;
+                break;
+            }
+            Tracev((stderr, "inflate:       codes ok\n"));
+            state->mode = LEN;
+
+        case LEN:
+            /* use inflate_fast() if we have enough input and output */
+            if (have >= 6 && left >= 258) {
+                RESTORE();
+                if (state->whave < state->wsize)
+                    state->whave = state->wsize - left;
+                inflate_fast(strm, state->wsize);
+                LOAD();
+                break;
+            }
+
+            /* get a literal, length, or end-of-block code */
+            for (;;) {
+                here = state->lencode[BITS(state->lenbits)];
+                if ((unsigned)(here.bits) <= bits) break;
+                PULLBYTE();
+            }
+            if (here.op && (here.op & 0xf0) == 0) {
+                last = here;
+                for (;;) {
+                    here = state->lencode[last.val +
+                            (BITS(last.bits + last.op) >> last.bits)];
+                    if ((unsigned)(last.bits + here.bits) <= bits) break;
+                    PULLBYTE();
+                }
+                DROPBITS(last.bits);
+            }
+            DROPBITS(here.bits);
+            state->length = (unsigned)here.val;
+
+            /* process literal */
+            if (here.op == 0) {
+                Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ?
+                        "inflate:         literal '%c'\n" :
+                        "inflate:         literal 0x%02x\n", here.val));
+                ROOM();
+                *put++ = (unsigned char)(state->length);
+                left--;
+                state->mode = LEN;
+                break;
+            }
+
+            /* process end of block */
+            if (here.op & 32) {
+                Tracevv((stderr, "inflate:         end of block\n"));
+                state->mode = TYPE;
+                break;
+            }
+
+            /* invalid code */
+            if (here.op & 64) {
+                strm->msg = (char *)"invalid literal/length code";
+                state->mode = BAD;
+                break;
+            }
+
+            /* length code -- get extra bits, if any */
+            state->extra = (unsigned)(here.op) & 15;
+            if (state->extra != 0) {
+                NEEDBITS(state->extra);
+                state->length += BITS(state->extra);
+                DROPBITS(state->extra);
+            }
+            Tracevv((stderr, "inflate:         length %u\n", state->length));
+
+            /* get distance code */
+            for (;;) {
+                here = state->distcode[BITS(state->distbits)];
+                if ((unsigned)(here.bits) <= bits) break;
+                PULLBYTE();
+            }
+            if ((here.op & 0xf0) == 0) {
+                last = here;
+                for (;;) {
+                    here = state->distcode[last.val +
+                            (BITS(last.bits + last.op) >> last.bits)];
+                    if ((unsigned)(last.bits + here.bits) <= bits) break;
+                    PULLBYTE();
+                }
+                DROPBITS(last.bits);
+            }
+            DROPBITS(here.bits);
+            if (here.op & 64) {
+                strm->msg = (char *)"invalid distance code";
+                state->mode = BAD;
+                break;
+            }
+            state->offset = (unsigned)here.val;
+
+            /* get distance extra bits, if any */
+            state->extra = (unsigned)(here.op) & 15;
+            if (state->extra != 0) {
+                NEEDBITS(state->extra);
+                state->offset += BITS(state->extra);
+                DROPBITS(state->extra);
+            }
+            if (state->offset > state->wsize - (state->whave < state->wsize ?
+                                                left : 0)) {
+                strm->msg = (char *)"invalid distance too far back";
+                state->mode = BAD;
+                break;
+            }
+            Tracevv((stderr, "inflate:         distance %u\n", state->offset));
+
+            /* copy match from window to output */
+            do {
+                ROOM();
+                copy = state->wsize - state->offset;
+                if (copy < left) {
+                    from = put + copy;
+                    copy = left - copy;
+                }
+                else {
+                    from = put - state->offset;
+                    copy = left;
+                }
+                if (copy > state->length) copy = state->length;
+                state->length -= copy;
+                left -= copy;
+                do {
+                    *put++ = *from++;
+                } while (--copy);
+            } while (state->length != 0);
+            break;
+
+        case DONE:
+            /* inflate stream terminated properly -- write leftover output */
+            ret = Z_STREAM_END;
+            if (left < state->wsize) {
+                if (out(out_desc, state->window, state->wsize - left))
+                    ret = Z_BUF_ERROR;
+            }
+            goto inf_leave;
+
+        case BAD:
+            ret = Z_DATA_ERROR;
+            goto inf_leave;
+
+        default:                /* can't happen, but makes compilers happy */
+            ret = Z_STREAM_ERROR;
+            goto inf_leave;
+        }
+
+    /* Return unused input */
+  inf_leave:
+    strm->next_in = next;
+    strm->avail_in = have;
+    return ret;
+}
+
+int ZEXPORT inflateBackEnd(strm)
+z_streamp strm;
+{
+    if (strm == Z_NULL || strm->state == Z_NULL || strm->zfree == (free_func)0)
+        return Z_STREAM_ERROR;
+    ZFREE(strm, strm->state);
+    strm->state = Z_NULL;
+    Tracev((stderr, "inflate: end\n"));
+    return Z_OK;
+}
diff --git a/src/plugins/cfitsio/inffast.c b/src/plugins/cfitsio/inffast.c
new file mode 100644
index 0000000..2f1d60b
--- /dev/null
+++ b/src/plugins/cfitsio/inffast.c
@@ -0,0 +1,340 @@
+/* inffast.c -- fast decoding
+ * Copyright (C) 1995-2008, 2010 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+#include "zutil.h"
+#include "inftrees.h"
+#include "inflate.h"
+#include "inffast.h"
+
+#ifndef ASMINF
+
+/* Allow machine dependent optimization for post-increment or pre-increment.
+   Based on testing to date,
+   Pre-increment preferred for:
+   - PowerPC G3 (Adler)
+   - MIPS R5000 (Randers-Pehrson)
+   Post-increment preferred for:
+   - none
+   No measurable difference:
+   - Pentium III (Anderson)
+   - M68060 (Nikl)
+ */
+#ifdef POSTINC
+#  define OFF 0
+#  define PUP(a) *(a)++
+#else
+#  define OFF 1
+#  define PUP(a) *++(a)
+#endif
+
+/*
+   Decode literal, length, and distance codes and write out the resulting
+   literal and match bytes until either not enough input or output is
+   available, an end-of-block is encountered, or a data error is encountered.
+   When large enough input and output buffers are supplied to inflate(), for
+   example, a 16K input buffer and a 64K output buffer, more than 95% of the
+   inflate execution time is spent in this routine.
+
+   Entry assumptions:
+
+        state->mode == LEN
+        strm->avail_in >= 6
+        strm->avail_out >= 258
+        start >= strm->avail_out
+        state->bits < 8
+
+   On return, state->mode is one of:
+
+        LEN -- ran out of enough output space or enough available input
+        TYPE -- reached end of block code, inflate() to interpret next block
+        BAD -- error in block data
+
+   Notes:
+
+    - The maximum input bits used by a length/distance pair is 15 bits for the
+      length code, 5 bits for the length extra, 15 bits for the distance code,
+      and 13 bits for the distance extra.  This totals 48 bits, or six bytes.
+      Therefore if strm->avail_in >= 6, then there is enough input to avoid
+      checking for available input while decoding.
+
+    - The maximum bytes that a single length/distance pair can output is 258
+      bytes, which is the maximum length that can be coded.  inflate_fast()
+      requires strm->avail_out >= 258 for each loop to avoid checking for
+      output space.
+ */
+void ZLIB_INTERNAL inflate_fast(strm, start)
+z_streamp strm;
+unsigned start;         /* inflate()'s starting value for strm->avail_out */
+{
+    struct inflate_state FAR *state;
+    unsigned char FAR *in;      /* local strm->next_in */
+    unsigned char FAR *last;    /* while in < last, enough input available */
+    unsigned char FAR *out;     /* local strm->next_out */
+    unsigned char FAR *beg;     /* inflate()'s initial strm->next_out */
+    unsigned char FAR *end;     /* while out < end, enough space available */
+#ifdef INFLATE_STRICT
+    unsigned dmax;              /* maximum distance from zlib header */
+#endif
+    unsigned wsize;             /* window size or zero if not using window */
+    unsigned whave;             /* valid bytes in the window */
+    unsigned wnext;             /* window write index */
+    unsigned char FAR *window;  /* allocated sliding window, if wsize != 0 */
+    unsigned long hold;         /* local strm->hold */
+    unsigned bits;              /* local strm->bits */
+    code const FAR *lcode;      /* local strm->lencode */
+    code const FAR *dcode;      /* local strm->distcode */
+    unsigned lmask;             /* mask for first level of length codes */
+    unsigned dmask;             /* mask for first level of distance codes */
+    code here;                  /* retrieved table entry */
+    unsigned op;                /* code bits, operation, extra bits, or */
+                                /*  window position, window bytes to copy */
+    unsigned len;               /* match length, unused bytes */
+    unsigned dist;              /* match distance */
+    unsigned char FAR *from;    /* where to copy match from */
+
+    /* copy state to local variables */
+    state = (struct inflate_state FAR *)strm->state;
+    in = strm->next_in - OFF;
+    last = in + (strm->avail_in - 5);
+    out = strm->next_out - OFF;
+    beg = out - (start - strm->avail_out);
+    end = out + (strm->avail_out - 257);
+#ifdef INFLATE_STRICT
+    dmax = state->dmax;
+#endif
+    wsize = state->wsize;
+    whave = state->whave;
+    wnext = state->wnext;
+    window = state->window;
+    hold = state->hold;
+    bits = state->bits;
+    lcode = state->lencode;
+    dcode = state->distcode;
+    lmask = (1U << state->lenbits) - 1;
+    dmask = (1U << state->distbits) - 1;
+
+    /* decode literals and length/distances until end-of-block or not enough
+       input data or output space */
+    do {
+        if (bits < 15) {
+            hold += (unsigned long)(PUP(in)) << bits;
+            bits += 8;
+            hold += (unsigned long)(PUP(in)) << bits;
+            bits += 8;
+        }
+        here = lcode[hold & lmask];
+      dolen:
+        op = (unsigned)(here.bits);
+        hold >>= op;
+        bits -= op;
+        op = (unsigned)(here.op);
+        if (op == 0) {                          /* literal */
+            Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ?
+                    "inflate:         literal '%c'\n" :
+                    "inflate:         literal 0x%02x\n", here.val));
+            PUP(out) = (unsigned char)(here.val);
+        }
+        else if (op & 16) {                     /* length base */
+            len = (unsigned)(here.val);
+            op &= 15;                           /* number of extra bits */
+            if (op) {
+                if (bits < op) {
+                    hold += (unsigned long)(PUP(in)) << bits;
+                    bits += 8;
+                }
+                len += (unsigned)hold & ((1U << op) - 1);
+                hold >>= op;
+                bits -= op;
+            }
+            Tracevv((stderr, "inflate:         length %u\n", len));
+            if (bits < 15) {
+                hold += (unsigned long)(PUP(in)) << bits;
+                bits += 8;
+                hold += (unsigned long)(PUP(in)) << bits;
+                bits += 8;
+            }
+            here = dcode[hold & dmask];
+          dodist:
+            op = (unsigned)(here.bits);
+            hold >>= op;
+            bits -= op;
+            op = (unsigned)(here.op);
+            if (op & 16) {                      /* distance base */
+                dist = (unsigned)(here.val);
+                op &= 15;                       /* number of extra bits */
+                if (bits < op) {
+                    hold += (unsigned long)(PUP(in)) << bits;
+                    bits += 8;
+                    if (bits < op) {
+                        hold += (unsigned long)(PUP(in)) << bits;
+                        bits += 8;
+                    }
+                }
+                dist += (unsigned)hold & ((1U << op) - 1);
+#ifdef INFLATE_STRICT
+                if (dist > dmax) {
+                    strm->msg = (char *)"invalid distance too far back";
+                    state->mode = BAD;
+                    break;
+                }
+#endif
+                hold >>= op;
+                bits -= op;
+                Tracevv((stderr, "inflate:         distance %u\n", dist));
+                op = (unsigned)(out - beg);     /* max distance in output */
+                if (dist > op) {                /* see if copy from window */
+                    op = dist - op;             /* distance back in window */
+                    if (op > whave) {
+                        if (state->sane) {
+                            strm->msg =
+                                (char *)"invalid distance too far back";
+                            state->mode = BAD;
+                            break;
+                        }
+#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
+                        if (len <= op - whave) {
+                            do {
+                                PUP(out) = 0;
+                            } while (--len);
+                            continue;
+                        }
+                        len -= op - whave;
+                        do {
+                            PUP(out) = 0;
+                        } while (--op > whave);
+                        if (op == 0) {
+                            from = out - dist;
+                            do {
+                                PUP(out) = PUP(from);
+                            } while (--len);
+                            continue;
+                        }
+#endif
+                    }
+                    from = window - OFF;
+                    if (wnext == 0) {           /* very common case */
+                        from += wsize - op;
+                        if (op < len) {         /* some from window */
+                            len -= op;
+                            do {
+                                PUP(out) = PUP(from);
+                            } while (--op);
+                            from = out - dist;  /* rest from output */
+                        }
+                    }
+                    else if (wnext < op) {      /* wrap around window */
+                        from += wsize + wnext - op;
+                        op -= wnext;
+                        if (op < len) {         /* some from end of window */
+                            len -= op;
+                            do {
+                                PUP(out) = PUP(from);
+                            } while (--op);
+                            from = window - OFF;
+                            if (wnext < len) {  /* some from start of window */
+                                op = wnext;
+                                len -= op;
+                                do {
+                                    PUP(out) = PUP(from);
+                                } while (--op);
+                                from = out - dist;      /* rest from output */
+                            }
+                        }
+                    }
+                    else {                      /* contiguous in window */
+                        from += wnext - op;
+                        if (op < len) {         /* some from window */
+                            len -= op;
+                            do {
+                                PUP(out) = PUP(from);
+                            } while (--op);
+                            from = out - dist;  /* rest from output */
+                        }
+                    }
+                    while (len > 2) {
+                        PUP(out) = PUP(from);
+                        PUP(out) = PUP(from);
+                        PUP(out) = PUP(from);
+                        len -= 3;
+                    }
+                    if (len) {
+                        PUP(out) = PUP(from);
+                        if (len > 1)
+                            PUP(out) = PUP(from);
+                    }
+                }
+                else {
+                    from = out - dist;          /* copy direct from output */
+                    do {                        /* minimum length is three */
+                        PUP(out) = PUP(from);
+                        PUP(out) = PUP(from);
+                        PUP(out) = PUP(from);
+                        len -= 3;
+                    } while (len > 2);
+                    if (len) {
+                        PUP(out) = PUP(from);
+                        if (len > 1)
+                            PUP(out) = PUP(from);
+                    }
+                }
+            }
+            else if ((op & 64) == 0) {          /* 2nd level distance code */
+                here = dcode[here.val + (hold & ((1U << op) - 1))];
+                goto dodist;
+            }
+            else {
+                strm->msg = (char *)"invalid distance code";
+                state->mode = BAD;
+                break;
+            }
+        }
+        else if ((op & 64) == 0) {              /* 2nd level length code */
+            here = lcode[here.val + (hold & ((1U << op) - 1))];
+            goto dolen;
+        }
+        else if (op & 32) {                     /* end-of-block */
+            Tracevv((stderr, "inflate:         end of block\n"));
+            state->mode = TYPE;
+            break;
+        }
+        else {
+            strm->msg = (char *)"invalid literal/length code";
+            state->mode = BAD;
+            break;
+        }
+    } while (in < last && out < end);
+
+    /* return unused bytes (on entry, bits < 8, so in won't go too far back) */
+    len = bits >> 3;
+    in -= len;
+    bits -= len << 3;
+    hold &= (1U << bits) - 1;
+
+    /* update state and return */
+    strm->next_in = in + OFF;
+    strm->next_out = out + OFF;
+    strm->avail_in = (unsigned)(in < last ? 5 + (last - in) : 5 - (in - last));
+    strm->avail_out = (unsigned)(out < end ?
+                                 257 + (end - out) : 257 - (out - end));
+    state->hold = hold;
+    state->bits = bits;
+    return;
+}
+
+/*
+   inflate_fast() speedups that turned out slower (on a PowerPC G3 750CXe):
+   - Using bit fields for code structure
+   - Different op definition to avoid & for extra bits (do & for table bits)
+   - Three separate decoding do-loops for direct, window, and wnext == 0
+   - Special case for distance > 1 copies to do overlapped load and store copy
+   - Explicit branch predictions (based on measured branch probabilities)
+   - Deferring match copy and interspersed it with decoding subsequent codes
+   - Swapping literal/length else
+   - Swapping window/direct else
+   - Larger unrolled copy loops (three is about right)
+   - Moving len -= 3 statement into middle of loop
+ */
+
+#endif /* !ASMINF */
diff --git a/src/plugins/cfitsio/inffast.h b/src/plugins/cfitsio/inffast.h
new file mode 100644
index 0000000..e5c1aa4
--- /dev/null
+++ b/src/plugins/cfitsio/inffast.h
@@ -0,0 +1,11 @@
+/* inffast.h -- header to use inffast.c
+ * Copyright (C) 1995-2003, 2010 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* WARNING: this file should *not* be used by applications. It is
+   part of the implementation of the compression library and is
+   subject to change. Applications should only use zlib.h.
+ */
+
+void ZLIB_INTERNAL inflate_fast OF((z_streamp strm, unsigned start));
diff --git a/src/plugins/cfitsio/inffixed.h b/src/plugins/cfitsio/inffixed.h
new file mode 100644
index 0000000..75ed4b5
--- /dev/null
+++ b/src/plugins/cfitsio/inffixed.h
@@ -0,0 +1,94 @@
+    /* inffixed.h -- table for decoding fixed codes
+     * Generated automatically by makefixed().
+     */
+
+    /* WARNING: this file should *not* be used by applications. It
+       is part of the implementation of the compression library and
+       is subject to change. Applications should only use zlib.h.
+     */
+
+    static const code lenfix[512] = {
+        {96,7,0},{0,8,80},{0,8,16},{20,8,115},{18,7,31},{0,8,112},{0,8,48},
+        {0,9,192},{16,7,10},{0,8,96},{0,8,32},{0,9,160},{0,8,0},{0,8,128},
+        {0,8,64},{0,9,224},{16,7,6},{0,8,88},{0,8,24},{0,9,144},{19,7,59},
+        {0,8,120},{0,8,56},{0,9,208},{17,7,17},{0,8,104},{0,8,40},{0,9,176},
+        {0,8,8},{0,8,136},{0,8,72},{0,9,240},{16,7,4},{0,8,84},{0,8,20},
+        {21,8,227},{19,7,43},{0,8,116},{0,8,52},{0,9,200},{17,7,13},{0,8,100},
+        {0,8,36},{0,9,168},{0,8,4},{0,8,132},{0,8,68},{0,9,232},{16,7,8},
+        {0,8,92},{0,8,28},{0,9,152},{20,7,83},{0,8,124},{0,8,60},{0,9,216},
+        {18,7,23},{0,8,108},{0,8,44},{0,9,184},{0,8,12},{0,8,140},{0,8,76},
+        {0,9,248},{16,7,3},{0,8,82},{0,8,18},{21,8,163},{19,7,35},{0,8,114},
+        {0,8,50},{0,9,196},{17,7,11},{0,8,98},{0,8,34},{0,9,164},{0,8,2},
+        {0,8,130},{0,8,66},{0,9,228},{16,7,7},{0,8,90},{0,8,26},{0,9,148},
+        {20,7,67},{0,8,122},{0,8,58},{0,9,212},{18,7,19},{0,8,106},{0,8,42},
+        {0,9,180},{0,8,10},{0,8,138},{0,8,74},{0,9,244},{16,7,5},{0,8,86},
+        {0,8,22},{64,8,0},{19,7,51},{0,8,118},{0,8,54},{0,9,204},{17,7,15},
+        {0,8,102},{0,8,38},{0,9,172},{0,8,6},{0,8,134},{0,8,70},{0,9,236},
+        {16,7,9},{0,8,94},{0,8,30},{0,9,156},{20,7,99},{0,8,126},{0,8,62},
+        {0,9,220},{18,7,27},{0,8,110},{0,8,46},{0,9,188},{0,8,14},{0,8,142},
+        {0,8,78},{0,9,252},{96,7,0},{0,8,81},{0,8,17},{21,8,131},{18,7,31},
+        {0,8,113},{0,8,49},{0,9,194},{16,7,10},{0,8,97},{0,8,33},{0,9,162},
+        {0,8,1},{0,8,129},{0,8,65},{0,9,226},{16,7,6},{0,8,89},{0,8,25},
+        {0,9,146},{19,7,59},{0,8,121},{0,8,57},{0,9,210},{17,7,17},{0,8,105},
+        {0,8,41},{0,9,178},{0,8,9},{0,8,137},{0,8,73},{0,9,242},{16,7,4},
+        {0,8,85},{0,8,21},{16,8,258},{19,7,43},{0,8,117},{0,8,53},{0,9,202},
+        {17,7,13},{0,8,101},{0,8,37},{0,9,170},{0,8,5},{0,8,133},{0,8,69},
+        {0,9,234},{16,7,8},{0,8,93},{0,8,29},{0,9,154},{20,7,83},{0,8,125},
+        {0,8,61},{0,9,218},{18,7,23},{0,8,109},{0,8,45},{0,9,186},{0,8,13},
+        {0,8,141},{0,8,77},{0,9,250},{16,7,3},{0,8,83},{0,8,19},{21,8,195},
+        {19,7,35},{0,8,115},{0,8,51},{0,9,198},{17,7,11},{0,8,99},{0,8,35},
+        {0,9,166},{0,8,3},{0,8,131},{0,8,67},{0,9,230},{16,7,7},{0,8,91},
+        {0,8,27},{0,9,150},{20,7,67},{0,8,123},{0,8,59},{0,9,214},{18,7,19},
+        {0,8,107},{0,8,43},{0,9,182},{0,8,11},{0,8,139},{0,8,75},{0,9,246},
+        {16,7,5},{0,8,87},{0,8,23},{64,8,0},{19,7,51},{0,8,119},{0,8,55},
+        {0,9,206},{17,7,15},{0,8,103},{0,8,39},{0,9,174},{0,8,7},{0,8,135},
+        {0,8,71},{0,9,238},{16,7,9},{0,8,95},{0,8,31},{0,9,158},{20,7,99},
+        {0,8,127},{0,8,63},{0,9,222},{18,7,27},{0,8,111},{0,8,47},{0,9,190},
+        {0,8,15},{0,8,143},{0,8,79},{0,9,254},{96,7,0},{0,8,80},{0,8,16},
+        {20,8,115},{18,7,31},{0,8,112},{0,8,48},{0,9,193},{16,7,10},{0,8,96},
+        {0,8,32},{0,9,161},{0,8,0},{0,8,128},{0,8,64},{0,9,225},{16,7,6},
+        {0,8,88},{0,8,24},{0,9,145},{19,7,59},{0,8,120},{0,8,56},{0,9,209},
+        {17,7,17},{0,8,104},{0,8,40},{0,9,177},{0,8,8},{0,8,136},{0,8,72},
+        {0,9,241},{16,7,4},{0,8,84},{0,8,20},{21,8,227},{19,7,43},{0,8,116},
+        {0,8,52},{0,9,201},{17,7,13},{0,8,100},{0,8,36},{0,9,169},{0,8,4},
+        {0,8,132},{0,8,68},{0,9,233},{16,7,8},{0,8,92},{0,8,28},{0,9,153},
+        {20,7,83},{0,8,124},{0,8,60},{0,9,217},{18,7,23},{0,8,108},{0,8,44},
+        {0,9,185},{0,8,12},{0,8,140},{0,8,76},{0,9,249},{16,7,3},{0,8,82},
+        {0,8,18},{21,8,163},{19,7,35},{0,8,114},{0,8,50},{0,9,197},{17,7,11},
+        {0,8,98},{0,8,34},{0,9,165},{0,8,2},{0,8,130},{0,8,66},{0,9,229},
+        {16,7,7},{0,8,90},{0,8,26},{0,9,149},{20,7,67},{0,8,122},{0,8,58},
+        {0,9,213},{18,7,19},{0,8,106},{0,8,42},{0,9,181},{0,8,10},{0,8,138},
+        {0,8,74},{0,9,245},{16,7,5},{0,8,86},{0,8,22},{64,8,0},{19,7,51},
+        {0,8,118},{0,8,54},{0,9,205},{17,7,15},{0,8,102},{0,8,38},{0,9,173},
+        {0,8,6},{0,8,134},{0,8,70},{0,9,237},{16,7,9},{0,8,94},{0,8,30},
+        {0,9,157},{20,7,99},{0,8,126},{0,8,62},{0,9,221},{18,7,27},{0,8,110},
+        {0,8,46},{0,9,189},{0,8,14},{0,8,142},{0,8,78},{0,9,253},{96,7,0},
+        {0,8,81},{0,8,17},{21,8,131},{18,7,31},{0,8,113},{0,8,49},{0,9,195},
+        {16,7,10},{0,8,97},{0,8,33},{0,9,163},{0,8,1},{0,8,129},{0,8,65},
+        {0,9,227},{16,7,6},{0,8,89},{0,8,25},{0,9,147},{19,7,59},{0,8,121},
+        {0,8,57},{0,9,211},{17,7,17},{0,8,105},{0,8,41},{0,9,179},{0,8,9},
+        {0,8,137},{0,8,73},{0,9,243},{16,7,4},{0,8,85},{0,8,21},{16,8,258},
+        {19,7,43},{0,8,117},{0,8,53},{0,9,203},{17,7,13},{0,8,101},{0,8,37},
+        {0,9,171},{0,8,5},{0,8,133},{0,8,69},{0,9,235},{16,7,8},{0,8,93},
+        {0,8,29},{0,9,155},{20,7,83},{0,8,125},{0,8,61},{0,9,219},{18,7,23},
+        {0,8,109},{0,8,45},{0,9,187},{0,8,13},{0,8,141},{0,8,77},{0,9,251},
+        {16,7,3},{0,8,83},{0,8,19},{21,8,195},{19,7,35},{0,8,115},{0,8,51},
+        {0,9,199},{17,7,11},{0,8,99},{0,8,35},{0,9,167},{0,8,3},{0,8,131},
+        {0,8,67},{0,9,231},{16,7,7},{0,8,91},{0,8,27},{0,9,151},{20,7,67},
+        {0,8,123},{0,8,59},{0,9,215},{18,7,19},{0,8,107},{0,8,43},{0,9,183},
+        {0,8,11},{0,8,139},{0,8,75},{0,9,247},{16,7,5},{0,8,87},{0,8,23},
+        {64,8,0},{19,7,51},{0,8,119},{0,8,55},{0,9,207},{17,7,15},{0,8,103},
+        {0,8,39},{0,9,175},{0,8,7},{0,8,135},{0,8,71},{0,9,239},{16,7,9},
+        {0,8,95},{0,8,31},{0,9,159},{20,7,99},{0,8,127},{0,8,63},{0,9,223},
+        {18,7,27},{0,8,111},{0,8,47},{0,9,191},{0,8,15},{0,8,143},{0,8,79},
+        {0,9,255}
+    };
+
+    static const code distfix[32] = {
+        {16,5,1},{23,5,257},{19,5,17},{27,5,4097},{17,5,5},{25,5,1025},
+        {21,5,65},{29,5,16385},{16,5,3},{24,5,513},{20,5,33},{28,5,8193},
+        {18,5,9},{26,5,2049},{22,5,129},{64,5,0},{16,5,2},{23,5,385},
+        {19,5,25},{27,5,6145},{17,5,7},{25,5,1537},{21,5,97},{29,5,24577},
+        {16,5,4},{24,5,769},{20,5,49},{28,5,12289},{18,5,13},{26,5,3073},
+        {22,5,193},{64,5,0}
+    };
diff --git a/src/plugins/cfitsio/inflate.c b/src/plugins/cfitsio/inflate.c
new file mode 100644
index 0000000..a8431ab
--- /dev/null
+++ b/src/plugins/cfitsio/inflate.c
@@ -0,0 +1,1480 @@
+/* inflate.c -- zlib decompression
+ * Copyright (C) 1995-2010 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/*
+ * Change history:
+ *
+ * 1.2.beta0    24 Nov 2002
+ * - First version -- complete rewrite of inflate to simplify code, avoid
+ *   creation of window when not needed, minimize use of window when it is
+ *   needed, make inffast.c even faster, implement gzip decoding, and to
+ *   improve code readability and style over the previous zlib inflate code
+ *
+ * 1.2.beta1    25 Nov 2002
+ * - Use pointers for available input and output checking in inffast.c
+ * - Remove input and output counters in inffast.c
+ * - Change inffast.c entry and loop from avail_in >= 7 to >= 6
+ * - Remove unnecessary second byte pull from length extra in inffast.c
+ * - Unroll direct copy to three copies per loop in inffast.c
+ *
+ * 1.2.beta2    4 Dec 2002
+ * - Change external routine names to reduce potential conflicts
+ * - Correct filename to inffixed.h for fixed tables in inflate.c
+ * - Make hbuf[] unsigned char to match parameter type in inflate.c
+ * - Change strm->next_out[-state->offset] to *(strm->next_out - state->offset)
+ *   to avoid negation problem on Alphas (64 bit) in inflate.c
+ *
+ * 1.2.beta3    22 Dec 2002
+ * - Add comments on state->bits assertion in inffast.c
+ * - Add comments on op field in inftrees.h
+ * - Fix bug in reuse of allocated window after inflateReset()
+ * - Remove bit fields--back to byte structure for speed
+ * - Remove distance extra == 0 check in inflate_fast()--only helps for lengths
+ * - Change post-increments to pre-increments in inflate_fast(), PPC biased?
+ * - Add compile time option, POSTINC, to use post-increments instead (Intel?)
+ * - Make MATCH copy in inflate() much faster for when inflate_fast() not used
+ * - Use local copies of stream next and avail values, as well as local bit
+ *   buffer and bit count in inflate()--for speed when inflate_fast() not used
+ *
+ * 1.2.beta4    1 Jan 2003
+ * - Split ptr - 257 statements in inflate_table() to avoid compiler warnings
+ * - Move a comment on output buffer sizes from inffast.c to inflate.c
+ * - Add comments in inffast.c to introduce the inflate_fast() routine
+ * - Rearrange window copies in inflate_fast() for speed and simplification
+ * - Unroll last copy for window match in inflate_fast()
+ * - Use local copies of window variables in inflate_fast() for speed
+ * - Pull out common wnext == 0 case for speed in inflate_fast()
+ * - Make op and len in inflate_fast() unsigned for consistency
+ * - Add FAR to lcode and dcode declarations in inflate_fast()
+ * - Simplified bad distance check in inflate_fast()
+ * - Added inflateBackInit(), inflateBack(), and inflateBackEnd() in new
+ *   source file infback.c to provide a call-back interface to inflate for
+ *   programs like gzip and unzip -- uses window as output buffer to avoid
+ *   window copying
+ *
+ * 1.2.beta5    1 Jan 2003
+ * - Improved inflateBack() interface to allow the caller to provide initial
+ *   input in strm.
+ * - Fixed stored blocks bug in inflateBack()
+ *
+ * 1.2.beta6    4 Jan 2003
+ * - Added comments in inffast.c on effectiveness of POSTINC
+ * - Typecasting all around to reduce compiler warnings
+ * - Changed loops from while (1) or do {} while (1) to for (;;), again to
+ *   make compilers happy
+ * - Changed type of window in inflateBackInit() to unsigned char *
+ *
+ * 1.2.beta7    27 Jan 2003
+ * - Changed many types to unsigned or unsigned short to avoid warnings
+ * - Added inflateCopy() function
+ *
+ * 1.2.0        9 Mar 2003
+ * - Changed inflateBack() interface to provide separate opaque descriptors
+ *   for the in() and out() functions
+ * - Changed inflateBack() argument and in_func typedef to swap the length
+ *   and buffer address return values for the input function
+ * - Check next_in and next_out for Z_NULL on entry to inflate()
+ *
+ * The history for versions after 1.2.0 are in ChangeLog in zlib distribution.
+ */
+
+#include "zutil.h"
+#include "inftrees.h"
+#include "inflate.h"
+#include "inffast.h"
+
+#ifdef MAKEFIXED
+#  ifndef BUILDFIXED
+#    define BUILDFIXED
+#  endif
+#endif
+
+/* function prototypes */
+local void fixedtables OF((struct inflate_state FAR *state));
+local int updatewindow OF((z_streamp strm, unsigned out));
+#ifdef BUILDFIXED
+   void makefixed OF((void));
+#endif
+local unsigned syncsearch OF((unsigned FAR *have, unsigned char FAR *buf,
+                              unsigned len));
+
+int ZEXPORT inflateReset(strm)
+z_streamp strm;
+{
+    struct inflate_state FAR *state;
+
+    if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+    state = (struct inflate_state FAR *)strm->state;
+    strm->total_in = strm->total_out = state->total = 0;
+    strm->msg = Z_NULL;
+    strm->adler = 1;        /* to support ill-conceived Java test suite */
+    state->mode = HEAD;
+    state->last = 0;
+    state->havedict = 0;
+    state->dmax = 32768U;
+    state->head = Z_NULL;
+    state->wsize = 0;
+    state->whave = 0;
+    state->wnext = 0;
+    state->hold = 0;
+    state->bits = 0;
+    state->lencode = state->distcode = state->next = state->codes;
+    state->sane = 1;
+    state->back = -1;
+    Tracev((stderr, "inflate: reset\n"));
+    return Z_OK;
+}
+
+int ZEXPORT inflateReset2(strm, windowBits)
+z_streamp strm;
+int windowBits;
+{
+    int wrap;
+    struct inflate_state FAR *state;
+
+    /* get the state */
+    if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+    state = (struct inflate_state FAR *)strm->state;
+
+    /* extract wrap request from windowBits parameter */
+    if (windowBits < 0) {
+        wrap = 0;
+        windowBits = -windowBits;
+    }
+    else {
+        wrap = (windowBits >> 4) + 1;
+#ifdef GUNZIP
+        if (windowBits < 48)
+            windowBits &= 15;
+#endif
+    }
+
+    /* set number of window bits, free window if different */
+    if (windowBits && (windowBits < 8 || windowBits > 15))
+        return Z_STREAM_ERROR;
+    if (state->window != Z_NULL && state->wbits != (unsigned)windowBits) {
+        ZFREE(strm, state->window);
+        state->window = Z_NULL;
+    }
+
+    /* update state and reset the rest of it */
+    state->wrap = wrap;
+    state->wbits = (unsigned)windowBits;
+    return inflateReset(strm);
+}
+
+int ZEXPORT inflateInit2_(strm, windowBits, version, stream_size)
+z_streamp strm;
+int windowBits;
+const char *version;
+int stream_size;
+{
+    int ret;
+    struct inflate_state FAR *state;
+
+    if (version == Z_NULL || version[0] != ZLIB_VERSION[0] ||
+        stream_size != (int)(sizeof(z_stream)))
+        return Z_VERSION_ERROR;
+    if (strm == Z_NULL) return Z_STREAM_ERROR;
+    strm->msg = Z_NULL;                 /* in case we return an error */
+    if (strm->zalloc == (alloc_func)0) {
+        strm->zalloc = zcalloc;
+        strm->opaque = (voidpf)0;
+    }
+    if (strm->zfree == (free_func)0) strm->zfree = zcfree;
+    state = (struct inflate_state FAR *)
+            ZALLOC(strm, 1, sizeof(struct inflate_state));
+    if (state == Z_NULL) return Z_MEM_ERROR;
+    Tracev((stderr, "inflate: allocated\n"));
+    strm->state = (struct internal_state FAR *)state;
+    state->window = Z_NULL;
+    ret = inflateReset2(strm, windowBits);
+    if (ret != Z_OK) {
+        ZFREE(strm, state);
+        strm->state = Z_NULL;
+    }
+    return ret;
+}
+
+int ZEXPORT inflateInit_(strm, version, stream_size)
+z_streamp strm;
+const char *version;
+int stream_size;
+{
+    return inflateInit2_(strm, DEF_WBITS, version, stream_size);
+}
+
+int ZEXPORT inflatePrime(strm, bits, value)
+z_streamp strm;
+int bits;
+int value;
+{
+    struct inflate_state FAR *state;
+
+    if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+    state = (struct inflate_state FAR *)strm->state;
+    if (bits < 0) {
+        state->hold = 0;
+        state->bits = 0;
+        return Z_OK;
+    }
+    if (bits > 16 || state->bits + bits > 32) return Z_STREAM_ERROR;
+    value &= (1L << bits) - 1;
+    state->hold += value << state->bits;
+    state->bits += bits;
+    return Z_OK;
+}
+
+/*
+   Return state with length and distance decoding tables and index sizes set to
+   fixed code decoding.  Normally this returns fixed tables from inffixed.h.
+   If BUILDFIXED is defined, then instead this routine builds the tables the
+   first time it's called, and returns those tables the first time and
+   thereafter.  This reduces the size of the code by about 2K bytes, in
+   exchange for a little execution time.  However, BUILDFIXED should not be
+   used for threaded applications, since the rewriting of the tables and virgin
+   may not be thread-safe.
+ */
+local void fixedtables(state)
+struct inflate_state FAR *state;
+{
+#ifdef BUILDFIXED
+    static int virgin = 1;
+    static code *lenfix, *distfix;
+    static code fixed[544];
+
+    /* build fixed huffman tables if first call (may not be thread safe) */
+    if (virgin) {
+        unsigned sym, bits;
+        static code *next;
+
+        /* literal/length table */
+        sym = 0;
+        while (sym < 144) state->lens[sym++] = 8;
+        while (sym < 256) state->lens[sym++] = 9;
+        while (sym < 280) state->lens[sym++] = 7;
+        while (sym < 288) state->lens[sym++] = 8;
+        next = fixed;
+        lenfix = next;
+        bits = 9;
+        inflate_table(LENS, state->lens, 288, &(next), &(bits), state->work);
+
+        /* distance table */
+        sym = 0;
+        while (sym < 32) state->lens[sym++] = 5;
+        distfix = next;
+        bits = 5;
+        inflate_table(DISTS, state->lens, 32, &(next), &(bits), state->work);
+
+        /* do this just once */
+        virgin = 0;
+    }
+#else /* !BUILDFIXED */
+#   include "inffixed.h"
+#endif /* BUILDFIXED */
+    state->lencode = lenfix;
+    state->lenbits = 9;
+    state->distcode = distfix;
+    state->distbits = 5;
+}
+
+#ifdef MAKEFIXED
+#include <stdio.h>
+
+/*
+   Write out the inffixed.h that is #include'd above.  Defining MAKEFIXED also
+   defines BUILDFIXED, so the tables are built on the fly.  makefixed() writes
+   those tables to stdout, which would be piped to inffixed.h.  A small program
+   can simply call makefixed to do this:
+
+    void makefixed(void);
+
+    int main(void)
+    {
+        makefixed();
+        return 0;
+    }
+
+   Then that can be linked with zlib built with MAKEFIXED defined and run:
+
+    a.out > inffixed.h
+ */
+void makefixed()
+{
+    unsigned low, size;
+    struct inflate_state state;
+
+    fixedtables(&state);
+    puts("    /* inffixed.h -- table for decoding fixed codes");
+    puts("     * Generated automatically by makefixed().");
+    puts("     */");
+    puts("");
+    puts("    /* WARNING: this file should *not* be used by applications.");
+    puts("       It is part of the implementation of this library and is");
+    puts("       subject to change. Applications should only use zlib.h.");
+    puts("     */");
+    puts("");
+    size = 1U << 9;
+    printf("    static const code lenfix[%u] = {", size);
+    low = 0;
+    for (;;) {
+        if ((low % 7) == 0) printf("\n        ");
+        printf("{%u,%u,%d}", state.lencode[low].op, state.lencode[low].bits,
+               state.lencode[low].val);
+        if (++low == size) break;
+        putchar(',');
+    }
+    puts("\n    };");
+    size = 1U << 5;
+    printf("\n    static const code distfix[%u] = {", size);
+    low = 0;
+    for (;;) {
+        if ((low % 6) == 0) printf("\n        ");
+        printf("{%u,%u,%d}", state.distcode[low].op, state.distcode[low].bits,
+               state.distcode[low].val);
+        if (++low == size) break;
+        putchar(',');
+    }
+    puts("\n    };");
+}
+#endif /* MAKEFIXED */
+
+/*
+   Update the window with the last wsize (normally 32K) bytes written before
+   returning.  If window does not exist yet, create it.  This is only called
+   when a window is already in use, or when output has been written during this
+   inflate call, but the end of the deflate stream has not been reached yet.
+   It is also called to create a window for dictionary data when a dictionary
+   is loaded.
+
+   Providing output buffers larger than 32K to inflate() should provide a speed
+   advantage, since only the last 32K of output is copied to the sliding window
+   upon return from inflate(), and since all distances after the first 32K of
+   output will fall in the output data, making match copies simpler and faster.
+   The advantage may be dependent on the size of the processor's data caches.
+ */
+local int updatewindow(strm, out)
+z_streamp strm;
+unsigned out;
+{
+    struct inflate_state FAR *state;
+    unsigned copy, dist;
+
+    state = (struct inflate_state FAR *)strm->state;
+
+    /* if it hasn't been done already, allocate space for the window */
+    if (state->window == Z_NULL) {
+        state->window = (unsigned char FAR *)
+                        ZALLOC(strm, 1U << state->wbits,
+                               sizeof(unsigned char));
+        if (state->window == Z_NULL) return 1;
+    }
+
+    /* if window not in use yet, initialize */
+    if (state->wsize == 0) {
+        state->wsize = 1U << state->wbits;
+        state->wnext = 0;
+        state->whave = 0;
+    }
+
+    /* copy state->wsize or less output bytes into the circular window */
+    copy = out - strm->avail_out;
+    if (copy >= state->wsize) {
+        zmemcpy(state->window, strm->next_out - state->wsize, state->wsize);
+        state->wnext = 0;
+        state->whave = state->wsize;
+    }
+    else {
+        dist = state->wsize - state->wnext;
+        if (dist > copy) dist = copy;
+        zmemcpy(state->window + state->wnext, strm->next_out - copy, dist);
+        copy -= dist;
+        if (copy) {
+            zmemcpy(state->window, strm->next_out - copy, copy);
+            state->wnext = copy;
+            state->whave = state->wsize;
+        }
+        else {
+            state->wnext += dist;
+            if (state->wnext == state->wsize) state->wnext = 0;
+            if (state->whave < state->wsize) state->whave += dist;
+        }
+    }
+    return 0;
+}
+
+/* Macros for inflate(): */
+
+/* check function to use adler32() for zlib or crc32() for gzip */
+#ifdef GUNZIP
+#  define UPDATE(check, buf, len) \
+    (state->flags ? crc32(check, buf, len) : adler32(check, buf, len))
+#else
+#  define UPDATE(check, buf, len) adler32(check, buf, len)
+#endif
+
+/* check macros for header crc */
+#ifdef GUNZIP
+#  define CRC2(check, word) \
+    do { \
+        hbuf[0] = (unsigned char)(word); \
+        hbuf[1] = (unsigned char)((word) >> 8); \
+        check = crc32(check, hbuf, 2); \
+    } while (0)
+
+#  define CRC4(check, word) \
+    do { \
+        hbuf[0] = (unsigned char)(word); \
+        hbuf[1] = (unsigned char)((word) >> 8); \
+        hbuf[2] = (unsigned char)((word) >> 16); \
+        hbuf[3] = (unsigned char)((word) >> 24); \
+        check = crc32(check, hbuf, 4); \
+    } while (0)
+#endif
+
+/* Load registers with state in inflate() for speed */
+#define LOAD() \
+    do { \
+        put = strm->next_out; \
+        left = strm->avail_out; \
+        next = strm->next_in; \
+        have = strm->avail_in; \
+        hold = state->hold; \
+        bits = state->bits; \
+    } while (0)
+
+/* Restore state from registers in inflate() */
+#define RESTORE() \
+    do { \
+        strm->next_out = put; \
+        strm->avail_out = left; \
+        strm->next_in = next; \
+        strm->avail_in = have; \
+        state->hold = hold; \
+        state->bits = bits; \
+    } while (0)
+
+/* Clear the input bit accumulator */
+#define INITBITS() \
+    do { \
+        hold = 0; \
+        bits = 0; \
+    } while (0)
+
+/* Get a byte of input into the bit accumulator, or return from inflate()
+   if there is no input available. */
+#define PULLBYTE() \
+    do { \
+        if (have == 0) goto inf_leave; \
+        have--; \
+        hold += (unsigned long)(*next++) << bits; \
+        bits += 8; \
+    } while (0)
+
+/* Assure that there are at least n bits in the bit accumulator.  If there is
+   not enough available input to do that, then return from inflate(). */
+#define NEEDBITS(n) \
+    do { \
+        while (bits < (unsigned)(n)) \
+            PULLBYTE(); \
+    } while (0)
+
+/* Return the low n bits of the bit accumulator (n < 16) */
+#define BITS(n) \
+    ((unsigned)hold & ((1U << (n)) - 1))
+
+/* Remove n bits from the bit accumulator */
+#define DROPBITS(n) \
+    do { \
+        hold >>= (n); \
+        bits -= (unsigned)(n); \
+    } while (0)
+
+/* Remove zero to seven bits as needed to go to a byte boundary */
+#define BYTEBITS() \
+    do { \
+        hold >>= bits & 7; \
+        bits -= bits & 7; \
+    } while (0)
+
+/* Reverse the bytes in a 32-bit value */
+#define REVERSE(q) \
+    ((((q) >> 24) & 0xff) + (((q) >> 8) & 0xff00) + \
+     (((q) & 0xff00) << 8) + (((q) & 0xff) << 24))
+
+/*
+   inflate() uses a state machine to process as much input data and generate as
+   much output data as possible before returning.  The state machine is
+   structured roughly as follows:
+
+    for (;;) switch (state) {
+    ...
+    case STATEn:
+        if (not enough input data or output space to make progress)
+            return;
+        ... make progress ...
+        state = STATEm;
+        break;
+    ...
+    }
+
+   so when inflate() is called again, the same case is attempted again, and
+   if the appropriate resources are provided, the machine proceeds to the
+   next state.  The NEEDBITS() macro is usually the way the state evaluates
+   whether it can proceed or should return.  NEEDBITS() does the return if
+   the requested bits are not available.  The typical use of the BITS macros
+   is:
+
+        NEEDBITS(n);
+        ... do something with BITS(n) ...
+        DROPBITS(n);
+
+   where NEEDBITS(n) either returns from inflate() if there isn't enough
+   input left to load n bits into the accumulator, or it continues.  BITS(n)
+   gives the low n bits in the accumulator.  When done, DROPBITS(n) drops
+   the low n bits off the accumulator.  INITBITS() clears the accumulator
+   and sets the number of available bits to zero.  BYTEBITS() discards just
+   enough bits to put the accumulator on a byte boundary.  After BYTEBITS()
+   and a NEEDBITS(8), then BITS(8) would return the next byte in the stream.
+
+   NEEDBITS(n) uses PULLBYTE() to get an available byte of input, or to return
+   if there is no input available.  The decoding of variable length codes uses
+   PULLBYTE() directly in order to pull just enough bytes to decode the next
+   code, and no more.
+
+   Some states loop until they get enough input, making sure that enough
+   state information is maintained to continue the loop where it left off
+   if NEEDBITS() returns in the loop.  For example, want, need, and keep
+   would all have to actually be part of the saved state in case NEEDBITS()
+   returns:
+
+    case STATEw:
+        while (want < need) {
+            NEEDBITS(n);
+            keep[want++] = BITS(n);
+            DROPBITS(n);
+        }
+        state = STATEx;
+    case STATEx:
+
+   As shown above, if the next state is also the next case, then the break
+   is omitted.
+
+   A state may also return if there is not enough output space available to
+   complete that state.  Those states are copying stored data, writing a
+   literal byte, and copying a matching string.
+
+   When returning, a "goto inf_leave" is used to update the total counters,
+   update the check value, and determine whether any progress has been made
+   during that inflate() call in order to return the proper return code.
+   Progress is defined as a change in either strm->avail_in or strm->avail_out.
+   When there is a window, goto inf_leave will update the window with the last
+   output written.  If a goto inf_leave occurs in the middle of decompression
+   and there is no window currently, goto inf_leave will create one and copy
+   output to the window for the next call of inflate().
+
+   In this implementation, the flush parameter of inflate() only affects the
+   return code (per zlib.h).  inflate() always writes as much as possible to
+   strm->next_out, given the space available and the provided input--the effect
+   documented in zlib.h of Z_SYNC_FLUSH.  Furthermore, inflate() always defers
+   the allocation of and copying into a sliding window until necessary, which
+   provides the effect documented in zlib.h for Z_FINISH when the entire input
+   stream available.  So the only thing the flush parameter actually does is:
+   when flush is set to Z_FINISH, inflate() cannot return Z_OK.  Instead it
+   will return Z_BUF_ERROR if it has not reached the end of the stream.
+ */
+
+int ZEXPORT inflate(strm, flush)
+z_streamp strm;
+int flush;
+{
+    struct inflate_state FAR *state;
+    unsigned char FAR *next;    /* next input */
+    unsigned char FAR *put;     /* next output */
+    unsigned have, left;        /* available input and output */
+    unsigned long hold;         /* bit buffer */
+    unsigned bits;              /* bits in bit buffer */
+    unsigned in, out;           /* save starting available input and output */
+    unsigned copy;              /* number of stored or match bytes to copy */
+    unsigned char FAR *from;    /* where to copy match bytes from */
+    code here;                  /* current decoding table entry */
+    code last;                  /* parent table entry */
+    unsigned len;               /* length to copy for repeats, bits to drop */
+    int ret;                    /* return code */
+#ifdef GUNZIP
+    unsigned char hbuf[4];      /* buffer for gzip header crc calculation */
+#endif
+    static const unsigned short order[19] = /* permutation of code lengths */
+        {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15};
+
+    if (strm == Z_NULL || strm->state == Z_NULL || strm->next_out == Z_NULL ||
+        (strm->next_in == Z_NULL && strm->avail_in != 0))
+        return Z_STREAM_ERROR;
+
+    state = (struct inflate_state FAR *)strm->state;
+    if (state->mode == TYPE) state->mode = TYPEDO;      /* skip check */
+    LOAD();
+    in = have;
+    out = left;
+    ret = Z_OK;
+    for (;;)
+        switch (state->mode) {
+        case HEAD:
+            if (state->wrap == 0) {
+                state->mode = TYPEDO;
+                break;
+            }
+            NEEDBITS(16);
+#ifdef GUNZIP
+            if ((state->wrap & 2) && hold == 0x8b1f) {  /* gzip header */
+                state->check = crc32(0L, Z_NULL, 0);
+                CRC2(state->check, hold);
+                INITBITS();
+                state->mode = FLAGS;
+                break;
+            }
+            state->flags = 0;           /* expect zlib header */
+            if (state->head != Z_NULL)
+                state->head->done = -1;
+            if (!(state->wrap & 1) ||   /* check if zlib header allowed */
+#else
+            if (
+#endif
+                ((BITS(8) << 8) + (hold >> 8)) % 31) {
+                strm->msg = (char *)"incorrect header check";
+                state->mode = BAD;
+                break;
+            }
+            if (BITS(4) != Z_DEFLATED) {
+                strm->msg = (char *)"unknown compression method";
+                state->mode = BAD;
+                break;
+            }
+            DROPBITS(4);
+            len = BITS(4) + 8;
+            if (state->wbits == 0)
+                state->wbits = len;
+            else if (len > state->wbits) {
+                strm->msg = (char *)"invalid window size";
+                state->mode = BAD;
+                break;
+            }
+            state->dmax = 1U << len;
+            Tracev((stderr, "inflate:   zlib header ok\n"));
+            strm->adler = state->check = adler32(0L, Z_NULL, 0);
+            state->mode = hold & 0x200 ? DICTID : TYPE;
+            INITBITS();
+            break;
+#ifdef GUNZIP
+        case FLAGS:
+            NEEDBITS(16);
+            state->flags = (int)(hold);
+            if ((state->flags & 0xff) != Z_DEFLATED) {
+                strm->msg = (char *)"unknown compression method";
+                state->mode = BAD;
+                break;
+            }
+            if (state->flags & 0xe000) {
+                strm->msg = (char *)"unknown header flags set";
+                state->mode = BAD;
+                break;
+            }
+            if (state->head != Z_NULL)
+                state->head->text = (int)((hold >> 8) & 1);
+            if (state->flags & 0x0200) CRC2(state->check, hold);
+            INITBITS();
+            state->mode = TIME;
+        case TIME:
+            NEEDBITS(32);
+            if (state->head != Z_NULL)
+                state->head->time = hold;
+            if (state->flags & 0x0200) CRC4(state->check, hold);
+            INITBITS();
+            state->mode = OS;
+        case OS:
+            NEEDBITS(16);
+            if (state->head != Z_NULL) {
+                state->head->xflags = (int)(hold & 0xff);
+                state->head->os = (int)(hold >> 8);
+            }
+            if (state->flags & 0x0200) CRC2(state->check, hold);
+            INITBITS();
+            state->mode = EXLEN;
+        case EXLEN:
+            if (state->flags & 0x0400) {
+                NEEDBITS(16);
+                state->length = (unsigned)(hold);
+                if (state->head != Z_NULL)
+                    state->head->extra_len = (unsigned)hold;
+                if (state->flags & 0x0200) CRC2(state->check, hold);
+                INITBITS();
+            }
+            else if (state->head != Z_NULL)
+                state->head->extra = Z_NULL;
+            state->mode = EXTRA;
+        case EXTRA:
+            if (state->flags & 0x0400) {
+                copy = state->length;
+                if (copy > have) copy = have;
+                if (copy) {
+                    if (state->head != Z_NULL &&
+                        state->head->extra != Z_NULL) {
+                        len = state->head->extra_len - state->length;
+                        zmemcpy(state->head->extra + len, next,
+                                len + copy > state->head->extra_max ?
+                                state->head->extra_max - len : copy);
+                    }
+                    if (state->flags & 0x0200)
+                        state->check = crc32(state->check, next, copy);
+                    have -= copy;
+                    next += copy;
+                    state->length -= copy;
+                }
+                if (state->length) goto inf_leave;
+            }
+            state->length = 0;
+            state->mode = NAME;
+        case NAME:
+            if (state->flags & 0x0800) {
+                if (have == 0) goto inf_leave;
+                copy = 0;
+                do {
+                    len = (unsigned)(next[copy++]);
+                    if (state->head != Z_NULL &&
+                            state->head->name != Z_NULL &&
+                            state->length < state->head->name_max)
+                        state->head->name[state->length++] = len;
+                } while (len && copy < have);
+                if (state->flags & 0x0200)
+                    state->check = crc32(state->check, next, copy);
+                have -= copy;
+                next += copy;
+                if (len) goto inf_leave;
+            }
+            else if (state->head != Z_NULL)
+                state->head->name = Z_NULL;
+            state->length = 0;
+            state->mode = COMMENT;
+        case COMMENT:
+            if (state->flags & 0x1000) {
+                if (have == 0) goto inf_leave;
+                copy = 0;
+                do {
+                    len = (unsigned)(next[copy++]);
+                    if (state->head != Z_NULL &&
+                            state->head->comment != Z_NULL &&
+                            state->length < state->head->comm_max)
+                        state->head->comment[state->length++] = len;
+                } while (len && copy < have);
+                if (state->flags & 0x0200)
+                    state->check = crc32(state->check, next, copy);
+                have -= copy;
+                next += copy;
+                if (len) goto inf_leave;
+            }
+            else if (state->head != Z_NULL)
+                state->head->comment = Z_NULL;
+            state->mode = HCRC;
+        case HCRC:
+            if (state->flags & 0x0200) {
+                NEEDBITS(16);
+                if (hold != (state->check & 0xffff)) {
+                    strm->msg = (char *)"header crc mismatch";
+                    state->mode = BAD;
+                    break;
+                }
+                INITBITS();
+            }
+            if (state->head != Z_NULL) {
+                state->head->hcrc = (int)((state->flags >> 9) & 1);
+                state->head->done = 1;
+            }
+            strm->adler = state->check = crc32(0L, Z_NULL, 0);
+            state->mode = TYPE;
+            break;
+#endif
+        case DICTID:
+            NEEDBITS(32);
+            strm->adler = state->check = REVERSE(hold);
+            INITBITS();
+            state->mode = DICT;
+        case DICT:
+            if (state->havedict == 0) {
+                RESTORE();
+                return Z_NEED_DICT;
+            }
+            strm->adler = state->check = adler32(0L, Z_NULL, 0);
+            state->mode = TYPE;
+        case TYPE:
+            if (flush == Z_BLOCK || flush == Z_TREES) goto inf_leave;
+        case TYPEDO:
+            if (state->last) {
+                BYTEBITS();
+                state->mode = CHECK;
+                break;
+            }
+            NEEDBITS(3);
+            state->last = BITS(1);
+            DROPBITS(1);
+            switch (BITS(2)) {
+            case 0:                             /* stored block */
+                Tracev((stderr, "inflate:     stored block%s\n",
+                        state->last ? " (last)" : ""));
+                state->mode = STORED;
+                break;
+            case 1:                             /* fixed block */
+                fixedtables(state);
+                Tracev((stderr, "inflate:     fixed codes block%s\n",
+                        state->last ? " (last)" : ""));
+                state->mode = LEN_;             /* decode codes */
+                if (flush == Z_TREES) {
+                    DROPBITS(2);
+                    goto inf_leave;
+                }
+                break;
+            case 2:                             /* dynamic block */
+                Tracev((stderr, "inflate:     dynamic codes block%s\n",
+                        state->last ? " (last)" : ""));
+                state->mode = TABLE;
+                break;
+            case 3:
+                strm->msg = (char *)"invalid block type";
+                state->mode = BAD;
+            }
+            DROPBITS(2);
+            break;
+        case STORED:
+            BYTEBITS();                         /* go to byte boundary */
+            NEEDBITS(32);
+            if ((hold & 0xffff) != ((hold >> 16) ^ 0xffff)) {
+                strm->msg = (char *)"invalid stored block lengths";
+                state->mode = BAD;
+                break;
+            }
+            state->length = (unsigned)hold & 0xffff;
+            Tracev((stderr, "inflate:       stored length %u\n",
+                    state->length));
+            INITBITS();
+            state->mode = COPY_;
+            if (flush == Z_TREES) goto inf_leave;
+        case COPY_:
+            state->mode = COPY;
+        case COPY:
+            copy = state->length;
+            if (copy) {
+                if (copy > have) copy = have;
+                if (copy > left) copy = left;
+                if (copy == 0) goto inf_leave;
+                zmemcpy(put, next, copy);
+                have -= copy;
+                next += copy;
+                left -= copy;
+                put += copy;
+                state->length -= copy;
+                break;
+            }
+            Tracev((stderr, "inflate:       stored end\n"));
+            state->mode = TYPE;
+            break;
+        case TABLE:
+            NEEDBITS(14);
+            state->nlen = BITS(5) + 257;
+            DROPBITS(5);
+            state->ndist = BITS(5) + 1;
+            DROPBITS(5);
+            state->ncode = BITS(4) + 4;
+            DROPBITS(4);
+#ifndef PKZIP_BUG_WORKAROUND
+            if (state->nlen > 286 || state->ndist > 30) {
+                strm->msg = (char *)"too many length or distance symbols";
+                state->mode = BAD;
+                break;
+            }
+#endif
+            Tracev((stderr, "inflate:       table sizes ok\n"));
+            state->have = 0;
+            state->mode = LENLENS;
+        case LENLENS:
+            while (state->have < state->ncode) {
+                NEEDBITS(3);
+                state->lens[order[state->have++]] = (unsigned short)BITS(3);
+                DROPBITS(3);
+            }
+            while (state->have < 19)
+                state->lens[order[state->have++]] = 0;
+            state->next = state->codes;
+            state->lencode = (code const FAR *)(state->next);
+            state->lenbits = 7;
+            ret = inflate_table(CODES, state->lens, 19, &(state->next),
+                                &(state->lenbits), state->work);
+            if (ret) {
+                strm->msg = (char *)"invalid code lengths set";
+                state->mode = BAD;
+                break;
+            }
+            Tracev((stderr, "inflate:       code lengths ok\n"));
+            state->have = 0;
+            state->mode = CODELENS;
+        case CODELENS:
+            while (state->have < state->nlen + state->ndist) {
+                for (;;) {
+                    here = state->lencode[BITS(state->lenbits)];
+                    if ((unsigned)(here.bits) <= bits) break;
+                    PULLBYTE();
+                }
+                if (here.val < 16) {
+                    NEEDBITS(here.bits);
+                    DROPBITS(here.bits);
+                    state->lens[state->have++] = here.val;
+                }
+                else {
+                    if (here.val == 16) {
+                        NEEDBITS(here.bits + 2);
+                        DROPBITS(here.bits);
+                        if (state->have == 0) {
+                            strm->msg = (char *)"invalid bit length repeat";
+                            state->mode = BAD;
+                            break;
+                        }
+                        len = state->lens[state->have - 1];
+                        copy = 3 + BITS(2);
+                        DROPBITS(2);
+                    }
+                    else if (here.val == 17) {
+                        NEEDBITS(here.bits + 3);
+                        DROPBITS(here.bits);
+                        len = 0;
+                        copy = 3 + BITS(3);
+                        DROPBITS(3);
+                    }
+                    else {
+                        NEEDBITS(here.bits + 7);
+                        DROPBITS(here.bits);
+                        len = 0;
+                        copy = 11 + BITS(7);
+                        DROPBITS(7);
+                    }
+                    if (state->have + copy > state->nlen + state->ndist) {
+                        strm->msg = (char *)"invalid bit length repeat";
+                        state->mode = BAD;
+                        break;
+                    }
+                    while (copy--)
+                        state->lens[state->have++] = (unsigned short)len;
+                }
+            }
+
+            /* handle error breaks in while */
+            if (state->mode == BAD) break;
+
+            /* check for end-of-block code (better have one) */
+            if (state->lens[256] == 0) {
+                strm->msg = (char *)"invalid code -- missing end-of-block";
+                state->mode = BAD;
+                break;
+            }
+
+            /* build code tables -- note: do not change the lenbits or distbits
+               values here (9 and 6) without reading the comments in inftrees.h
+               concerning the ENOUGH constants, which depend on those values */
+            state->next = state->codes;
+            state->lencode = (code const FAR *)(state->next);
+            state->lenbits = 9;
+            ret = inflate_table(LENS, state->lens, state->nlen, &(state->next),
+                                &(state->lenbits), state->work);
+            if (ret) {
+                strm->msg = (char *)"invalid literal/lengths set";
+                state->mode = BAD;
+                break;
+            }
+            state->distcode = (code const FAR *)(state->next);
+            state->distbits = 6;
+            ret = inflate_table(DISTS, state->lens + state->nlen, state->ndist,
+                            &(state->next), &(state->distbits), state->work);
+            if (ret) {
+                strm->msg = (char *)"invalid distances set";
+                state->mode = BAD;
+                break;
+            }
+            Tracev((stderr, "inflate:       codes ok\n"));
+            state->mode = LEN_;
+            if (flush == Z_TREES) goto inf_leave;
+        case LEN_:
+            state->mode = LEN;
+        case LEN:
+            if (have >= 6 && left >= 258) {
+                RESTORE();
+                inflate_fast(strm, out);
+                LOAD();
+                if (state->mode == TYPE)
+                    state->back = -1;
+                break;
+            }
+            state->back = 0;
+            for (;;) {
+                here = state->lencode[BITS(state->lenbits)];
+                if ((unsigned)(here.bits) <= bits) break;
+                PULLBYTE();
+            }
+            if (here.op && (here.op & 0xf0) == 0) {
+                last = here;
+                for (;;) {
+                    here = state->lencode[last.val +
+                            (BITS(last.bits + last.op) >> last.bits)];
+                    if ((unsigned)(last.bits + here.bits) <= bits) break;
+                    PULLBYTE();
+                }
+                DROPBITS(last.bits);
+                state->back += last.bits;
+            }
+            DROPBITS(here.bits);
+            state->back += here.bits;
+            state->length = (unsigned)here.val;
+            if ((int)(here.op) == 0) {
+                Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ?
+                        "inflate:         literal '%c'\n" :
+                        "inflate:         literal 0x%02x\n", here.val));
+                state->mode = LIT;
+                break;
+            }
+            if (here.op & 32) {
+                Tracevv((stderr, "inflate:         end of block\n"));
+                state->back = -1;
+                state->mode = TYPE;
+                break;
+            }
+            if (here.op & 64) {
+                strm->msg = (char *)"invalid literal/length code";
+                state->mode = BAD;
+                break;
+            }
+            state->extra = (unsigned)(here.op) & 15;
+            state->mode = LENEXT;
+        case LENEXT:
+            if (state->extra) {
+                NEEDBITS(state->extra);
+                state->length += BITS(state->extra);
+                DROPBITS(state->extra);
+                state->back += state->extra;
+            }
+            Tracevv((stderr, "inflate:         length %u\n", state->length));
+            state->was = state->length;
+            state->mode = DIST;
+        case DIST:
+            for (;;) {
+                here = state->distcode[BITS(state->distbits)];
+                if ((unsigned)(here.bits) <= bits) break;
+                PULLBYTE();
+            }
+            if ((here.op & 0xf0) == 0) {
+                last = here;
+                for (;;) {
+                    here = state->distcode[last.val +
+                            (BITS(last.bits + last.op) >> last.bits)];
+                    if ((unsigned)(last.bits + here.bits) <= bits) break;
+                    PULLBYTE();
+                }
+                DROPBITS(last.bits);
+                state->back += last.bits;
+            }
+            DROPBITS(here.bits);
+            state->back += here.bits;
+            if (here.op & 64) {
+                strm->msg = (char *)"invalid distance code";
+                state->mode = BAD;
+                break;
+            }
+            state->offset = (unsigned)here.val;
+            state->extra = (unsigned)(here.op) & 15;
+            state->mode = DISTEXT;
+        case DISTEXT:
+            if (state->extra) {
+                NEEDBITS(state->extra);
+                state->offset += BITS(state->extra);
+                DROPBITS(state->extra);
+                state->back += state->extra;
+            }
+#ifdef INFLATE_STRICT
+            if (state->offset > state->dmax) {
+                strm->msg = (char *)"invalid distance too far back";
+                state->mode = BAD;
+                break;
+            }
+#endif
+            Tracevv((stderr, "inflate:         distance %u\n", state->offset));
+            state->mode = MATCH;
+        case MATCH:
+            if (left == 0) goto inf_leave;
+            copy = out - left;
+            if (state->offset > copy) {         /* copy from window */
+                copy = state->offset - copy;
+                if (copy > state->whave) {
+                    if (state->sane) {
+                        strm->msg = (char *)"invalid distance too far back";
+                        state->mode = BAD;
+                        break;
+                    }
+#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
+                    Trace((stderr, "inflate.c too far\n"));
+                    copy -= state->whave;
+                    if (copy > state->length) copy = state->length;
+                    if (copy > left) copy = left;
+                    left -= copy;
+                    state->length -= copy;
+                    do {
+                        *put++ = 0;
+                    } while (--copy);
+                    if (state->length == 0) state->mode = LEN;
+                    break;
+#endif
+                }
+                if (copy > state->wnext) {
+                    copy -= state->wnext;
+                    from = state->window + (state->wsize - copy);
+                }
+                else
+                    from = state->window + (state->wnext - copy);
+                if (copy > state->length) copy = state->length;
+            }
+            else {                              /* copy from output */
+                from = put - state->offset;
+                copy = state->length;
+            }
+            if (copy > left) copy = left;
+            left -= copy;
+            state->length -= copy;
+            do {
+                *put++ = *from++;
+            } while (--copy);
+            if (state->length == 0) state->mode = LEN;
+            break;
+        case LIT:
+            if (left == 0) goto inf_leave;
+            *put++ = (unsigned char)(state->length);
+            left--;
+            state->mode = LEN;
+            break;
+        case CHECK:
+            if (state->wrap) {
+                NEEDBITS(32);
+                out -= left;
+                strm->total_out += out;
+                state->total += out;
+                if (out)
+                    strm->adler = state->check =
+                        UPDATE(state->check, put - out, out);
+                out = left;
+                if ((
+#ifdef GUNZIP
+                     state->flags ? hold :
+#endif
+                     REVERSE(hold)) != state->check) {
+                    strm->msg = (char *)"incorrect data check";
+                    state->mode = BAD;
+                    break;
+                }
+                INITBITS();
+                Tracev((stderr, "inflate:   check matches trailer\n"));
+            }
+#ifdef GUNZIP
+            state->mode = LENGTH;
+        case LENGTH:
+            if (state->wrap && state->flags) {
+                NEEDBITS(32);
+                if (hold != (state->total & 0xffffffffUL)) {
+                    strm->msg = (char *)"incorrect length check";
+                    state->mode = BAD;
+                    break;
+                }
+                INITBITS();
+                Tracev((stderr, "inflate:   length matches trailer\n"));
+            }
+#endif
+            state->mode = DONE;
+        case DONE:
+            ret = Z_STREAM_END;
+            goto inf_leave;
+        case BAD:
+            ret = Z_DATA_ERROR;
+            goto inf_leave;
+        case MEM:
+            return Z_MEM_ERROR;
+        case SYNC:
+        default:
+            return Z_STREAM_ERROR;
+        }
+
+    /*
+       Return from inflate(), updating the total counts and the check value.
+       If there was no progress during the inflate() call, return a buffer
+       error.  Call updatewindow() to create and/or update the window state.
+       Note: a memory error from inflate() is non-recoverable.
+     */
+  inf_leave:
+    RESTORE();
+    if (state->wsize || (state->mode < CHECK && out != strm->avail_out))
+        if (updatewindow(strm, out)) {
+            state->mode = MEM;
+            return Z_MEM_ERROR;
+        }
+    in -= strm->avail_in;
+    out -= strm->avail_out;
+    strm->total_in += in;
+    strm->total_out += out;
+    state->total += out;
+    if (state->wrap && out)
+        strm->adler = state->check =
+            UPDATE(state->check, strm->next_out - out, out);
+    strm->data_type = state->bits + (state->last ? 64 : 0) +
+                      (state->mode == TYPE ? 128 : 0) +
+                      (state->mode == LEN_ || state->mode == COPY_ ? 256 : 0);
+    if (((in == 0 && out == 0) || flush == Z_FINISH) && ret == Z_OK)
+        ret = Z_BUF_ERROR;
+    return ret;
+}
+
+int ZEXPORT inflateEnd(strm)
+z_streamp strm;
+{
+    struct inflate_state FAR *state;
+    if (strm == Z_NULL || strm->state == Z_NULL || strm->zfree == (free_func)0)
+        return Z_STREAM_ERROR;
+    state = (struct inflate_state FAR *)strm->state;
+    if (state->window != Z_NULL) ZFREE(strm, state->window);
+    ZFREE(strm, strm->state);
+    strm->state = Z_NULL;
+    Tracev((stderr, "inflate: end\n"));
+    return Z_OK;
+}
+
+int ZEXPORT inflateSetDictionary(strm, dictionary, dictLength)
+z_streamp strm;
+const Bytef *dictionary;
+uInt dictLength;
+{
+    struct inflate_state FAR *state;
+    unsigned long id;
+
+    /* check state */
+    if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+    state = (struct inflate_state FAR *)strm->state;
+    if (state->wrap != 0 && state->mode != DICT)
+        return Z_STREAM_ERROR;
+
+    /* check for correct dictionary id */
+    if (state->mode == DICT) {
+        id = adler32(0L, Z_NULL, 0);
+        id = adler32(id, dictionary, dictLength);
+        if (id != state->check)
+            return Z_DATA_ERROR;
+    }
+
+    /* copy dictionary to window */
+    if (updatewindow(strm, strm->avail_out)) {
+        state->mode = MEM;
+        return Z_MEM_ERROR;
+    }
+    if (dictLength > state->wsize) {
+        zmemcpy(state->window, dictionary + dictLength - state->wsize,
+                state->wsize);
+        state->whave = state->wsize;
+    }
+    else {
+        zmemcpy(state->window + state->wsize - dictLength, dictionary,
+                dictLength);
+        state->whave = dictLength;
+    }
+    state->havedict = 1;
+    Tracev((stderr, "inflate:   dictionary set\n"));
+    return Z_OK;
+}
+
+int ZEXPORT inflateGetHeader(strm, head)
+z_streamp strm;
+gz_headerp head;
+{
+    struct inflate_state FAR *state;
+
+    /* check state */
+    if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+    state = (struct inflate_state FAR *)strm->state;
+    if ((state->wrap & 2) == 0) return Z_STREAM_ERROR;
+
+    /* save header structure */
+    state->head = head;
+    head->done = 0;
+    return Z_OK;
+}
+
+/*
+   Search buf[0..len-1] for the pattern: 0, 0, 0xff, 0xff.  Return when found
+   or when out of input.  When called, *have is the number of pattern bytes
+   found in order so far, in 0..3.  On return *have is updated to the new
+   state.  If on return *have equals four, then the pattern was found and the
+   return value is how many bytes were read including the last byte of the
+   pattern.  If *have is less than four, then the pattern has not been found
+   yet and the return value is len.  In the latter case, syncsearch() can be
+   called again with more data and the *have state.  *have is initialized to
+   zero for the first call.
+ */
+local unsigned syncsearch(have, buf, len)
+unsigned FAR *have;
+unsigned char FAR *buf;
+unsigned len;
+{
+    unsigned got;
+    unsigned next;
+
+    got = *have;
+    next = 0;
+    while (next < len && got < 4) {
+        if ((int)(buf[next]) == (got < 2 ? 0 : 0xff))
+            got++;
+        else if (buf[next])
+            got = 0;
+        else
+            got = 4 - got;
+        next++;
+    }
+    *have = got;
+    return next;
+}
+
+int ZEXPORT inflateSync(strm)
+z_streamp strm;
+{
+    unsigned len;               /* number of bytes to look at or looked at */
+    unsigned long in, out;      /* temporary to save total_in and total_out */
+    unsigned char buf[4];       /* to restore bit buffer to byte string */
+    struct inflate_state FAR *state;
+
+    /* check parameters */
+    if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+    state = (struct inflate_state FAR *)strm->state;
+    if (strm->avail_in == 0 && state->bits < 8) return Z_BUF_ERROR;
+
+    /* if first time, start search in bit buffer */
+    if (state->mode != SYNC) {
+        state->mode = SYNC;
+        state->hold <<= state->bits & 7;
+        state->bits -= state->bits & 7;
+        len = 0;
+        while (state->bits >= 8) {
+            buf[len++] = (unsigned char)(state->hold);
+            state->hold >>= 8;
+            state->bits -= 8;
+        }
+        state->have = 0;
+        syncsearch(&(state->have), buf, len);
+    }
+
+    /* search available input */
+    len = syncsearch(&(state->have), strm->next_in, strm->avail_in);
+    strm->avail_in -= len;
+    strm->next_in += len;
+    strm->total_in += len;
+
+    /* return no joy or set up to restart inflate() on a new block */
+    if (state->have != 4) return Z_DATA_ERROR;
+    in = strm->total_in;  out = strm->total_out;
+    inflateReset(strm);
+    strm->total_in = in;  strm->total_out = out;
+    state->mode = TYPE;
+    return Z_OK;
+}
+
+/*
+   Returns true if inflate is currently at the end of a block generated by
+   Z_SYNC_FLUSH or Z_FULL_FLUSH. This function is used by one PPP
+   implementation to provide an additional safety check. PPP uses
+   Z_SYNC_FLUSH but removes the length bytes of the resulting empty stored
+   block. When decompressing, PPP checks that at the end of input packet,
+   inflate is waiting for these length bytes.
+ */
+int ZEXPORT inflateSyncPoint(strm)
+z_streamp strm;
+{
+    struct inflate_state FAR *state;
+
+    if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+    state = (struct inflate_state FAR *)strm->state;
+    return state->mode == STORED && state->bits == 0;
+}
+
+int ZEXPORT inflateCopy(dest, source)
+z_streamp dest;
+z_streamp source;
+{
+    struct inflate_state FAR *state;
+    struct inflate_state FAR *copy;
+    unsigned char FAR *window;
+    unsigned wsize;
+
+    /* check input */
+    if (dest == Z_NULL || source == Z_NULL || source->state == Z_NULL ||
+        source->zalloc == (alloc_func)0 || source->zfree == (free_func)0)
+        return Z_STREAM_ERROR;
+    state = (struct inflate_state FAR *)source->state;
+
+    /* allocate space */
+    copy = (struct inflate_state FAR *)
+           ZALLOC(source, 1, sizeof(struct inflate_state));
+    if (copy == Z_NULL) return Z_MEM_ERROR;
+    window = Z_NULL;
+    if (state->window != Z_NULL) {
+        window = (unsigned char FAR *)
+                 ZALLOC(source, 1U << state->wbits, sizeof(unsigned char));
+        if (window == Z_NULL) {
+            ZFREE(source, copy);
+            return Z_MEM_ERROR;
+        }
+    }
+
+    /* copy state */
+    zmemcpy(dest, source, sizeof(z_stream));
+    zmemcpy(copy, state, sizeof(struct inflate_state));
+    if (state->lencode >= state->codes &&
+        state->lencode <= state->codes + ENOUGH - 1) {
+        copy->lencode = copy->codes + (state->lencode - state->codes);
+        copy->distcode = copy->codes + (state->distcode - state->codes);
+    }
+    copy->next = copy->codes + (state->next - state->codes);
+    if (window != Z_NULL) {
+        wsize = 1U << state->wbits;
+        zmemcpy(window, state->window, wsize);
+    }
+    copy->window = window;
+    dest->state = (struct internal_state FAR *)copy;
+    return Z_OK;
+}
+
+int ZEXPORT inflateUndermine(strm, subvert)
+z_streamp strm;
+int subvert;
+{
+    struct inflate_state FAR *state;
+
+    if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR;
+    state = (struct inflate_state FAR *)strm->state;
+    state->sane = !subvert;
+#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
+    return Z_OK;
+#else
+    state->sane = 1;
+    return Z_DATA_ERROR;
+#endif
+}
+
+long ZEXPORT inflateMark(strm)
+z_streamp strm;
+{
+    struct inflate_state FAR *state;
+
+    if (strm == Z_NULL || strm->state == Z_NULL) return -1L << 16;
+    state = (struct inflate_state FAR *)strm->state;
+    return ((long)(state->back) << 16) +
+        (state->mode == COPY ? state->length :
+            (state->mode == MATCH ? state->was - state->length : 0));
+}
diff --git a/src/plugins/cfitsio/inflate.h b/src/plugins/cfitsio/inflate.h
new file mode 100644
index 0000000..95f4986
--- /dev/null
+++ b/src/plugins/cfitsio/inflate.h
@@ -0,0 +1,122 @@
+/* inflate.h -- internal inflate state definition
+ * Copyright (C) 1995-2009 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* WARNING: this file should *not* be used by applications. It is
+   part of the implementation of the compression library and is
+   subject to change. Applications should only use zlib.h.
+ */
+
+/* define NO_GZIP when compiling if you want to disable gzip header and
+   trailer decoding by inflate().  NO_GZIP would be used to avoid linking in
+   the crc code when it is not needed.  For shared libraries, gzip decoding
+   should be left enabled. */
+#ifndef NO_GZIP
+#  define GUNZIP
+#endif
+
+/* Possible inflate modes between inflate() calls */
+typedef enum {
+    HEAD,       /* i: waiting for magic header */
+    FLAGS,      /* i: waiting for method and flags (gzip) */
+    TIME,       /* i: waiting for modification time (gzip) */
+    OS,         /* i: waiting for extra flags and operating system (gzip) */
+    EXLEN,      /* i: waiting for extra length (gzip) */
+    EXTRA,      /* i: waiting for extra bytes (gzip) */
+    NAME,       /* i: waiting for end of file name (gzip) */
+    COMMENT,    /* i: waiting for end of comment (gzip) */
+    HCRC,       /* i: waiting for header crc (gzip) */
+    DICTID,     /* i: waiting for dictionary check value */
+    DICT,       /* waiting for inflateSetDictionary() call */
+        TYPE,       /* i: waiting for type bits, including last-flag bit */
+        TYPEDO,     /* i: same, but skip check to exit inflate on new block */
+        STORED,     /* i: waiting for stored size (length and complement) */
+        COPY_,      /* i/o: same as COPY below, but only first time in */
+        COPY,       /* i/o: waiting for input or output to copy stored block */
+        TABLE,      /* i: waiting for dynamic block table lengths */
+        LENLENS,    /* i: waiting for code length code lengths */
+        CODELENS,   /* i: waiting for length/lit and distance code lengths */
+            LEN_,       /* i: same as LEN below, but only first time in */
+            LEN,        /* i: waiting for length/lit/eob code */
+            LENEXT,     /* i: waiting for length extra bits */
+            DIST,       /* i: waiting for distance code */
+            DISTEXT,    /* i: waiting for distance extra bits */
+            MATCH,      /* o: waiting for output space to copy string */
+            LIT,        /* o: waiting for output space to write literal */
+    CHECK,      /* i: waiting for 32-bit check value */
+    LENGTH,     /* i: waiting for 32-bit length (gzip) */
+    DONE,       /* finished check, done -- remain here until reset */
+    BAD,        /* got a data error -- remain here until reset */
+    MEM,        /* got an inflate() memory error -- remain here until reset */
+    SYNC        /* looking for synchronization bytes to restart inflate() */
+} inflate_mode;
+
+/*
+    State transitions between above modes -
+
+    (most modes can go to BAD or MEM on error -- not shown for clarity)
+
+    Process header:
+        HEAD -> (gzip) or (zlib) or (raw)
+        (gzip) -> FLAGS -> TIME -> OS -> EXLEN -> EXTRA -> NAME -> COMMENT ->
+                  HCRC -> TYPE
+        (zlib) -> DICTID or TYPE
+        DICTID -> DICT -> TYPE
+        (raw) -> TYPEDO
+    Read deflate blocks:
+            TYPE -> TYPEDO -> STORED or TABLE or LEN_ or CHECK
+            STORED -> COPY_ -> COPY -> TYPE
+            TABLE -> LENLENS -> CODELENS -> LEN_
+            LEN_ -> LEN
+    Read deflate codes in fixed or dynamic block:
+                LEN -> LENEXT or LIT or TYPE
+                LENEXT -> DIST -> DISTEXT -> MATCH -> LEN
+                LIT -> LEN
+    Process trailer:
+        CHECK -> LENGTH -> DONE
+ */
+
+/* state maintained between inflate() calls.  Approximately 10K bytes. */
+struct inflate_state {
+    inflate_mode mode;          /* current inflate mode */
+    int last;                   /* true if processing last block */
+    int wrap;                   /* bit 0 true for zlib, bit 1 true for gzip */
+    int havedict;               /* true if dictionary provided */
+    int flags;                  /* gzip header method and flags (0 if zlib) */
+    unsigned dmax;              /* zlib header max distance (INFLATE_STRICT) */
+    unsigned long check;        /* protected copy of check value */
+    unsigned long total;        /* protected copy of output count */
+    gz_headerp head;            /* where to save gzip header information */
+        /* sliding window */
+    unsigned wbits;             /* log base 2 of requested window size */
+    unsigned wsize;             /* window size or zero if not using window */
+    unsigned whave;             /* valid bytes in the window */
+    unsigned wnext;             /* window write index */
+    unsigned char FAR *window;  /* allocated sliding window, if needed */
+        /* bit accumulator */
+    unsigned long hold;         /* input bit accumulator */
+    unsigned bits;              /* number of bits in "in" */
+        /* for string and stored block copying */
+    unsigned length;            /* literal or length of data to copy */
+    unsigned offset;            /* distance back to copy string from */
+        /* for table and code decoding */
+    unsigned extra;             /* extra bits needed */
+        /* fixed and dynamic code tables */
+    code const FAR *lencode;    /* starting table for length/literal codes */
+    code const FAR *distcode;   /* starting table for distance codes */
+    unsigned lenbits;           /* index bits for lencode */
+    unsigned distbits;          /* index bits for distcode */
+        /* dynamic table building */
+    unsigned ncode;             /* number of code length code lengths */
+    unsigned nlen;              /* number of length code lengths */
+    unsigned ndist;             /* number of distance code lengths */
+    unsigned have;              /* number of code lengths in lens[] */
+    code FAR *next;             /* next available space in codes[] */
+    unsigned short lens[320];   /* temporary storage for code lengths */
+    unsigned short work[288];   /* work area for code table building */
+    code codes[ENOUGH];         /* space for code tables */
+    int sane;                   /* if false, allow invalid distance too far */
+    int back;                   /* bits back of last unprocessed length/lit */
+    unsigned was;               /* initial length of match */
+};
diff --git a/src/plugins/cfitsio/inftrees.c b/src/plugins/cfitsio/inftrees.c
new file mode 100644
index 0000000..11e9c52
--- /dev/null
+++ b/src/plugins/cfitsio/inftrees.c
@@ -0,0 +1,330 @@
+/* inftrees.c -- generate Huffman trees for efficient decoding
+ * Copyright (C) 1995-2010 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+#include "zutil.h"
+#include "inftrees.h"
+
+#define MAXBITS 15
+
+const char inflate_copyright[] =
+   " inflate 1.2.5 Copyright 1995-2010 Mark Adler ";
+/*
+  If you use the zlib library in a product, an acknowledgment is welcome
+  in the documentation of your product. If for some reason you cannot
+  include such an acknowledgment, I would appreciate that you keep this
+  copyright string in the executable of your product.
+ */
+
+/*
+   Build a set of tables to decode the provided canonical Huffman code.
+   The code lengths are lens[0..codes-1].  The result starts at *table,
+   whose indices are 0..2^bits-1.  work is a writable array of at least
+   lens shorts, which is used as a work area.  type is the type of code
+   to be generated, CODES, LENS, or DISTS.  On return, zero is success,
+   -1 is an invalid code, and +1 means that ENOUGH isn't enough.  table
+   on return points to the next available entry's address.  bits is the
+   requested root table index bits, and on return it is the actual root
+   table index bits.  It will differ if the request is greater than the
+   longest code or if it is less than the shortest code.
+ */
+int ZLIB_INTERNAL inflate_table(type, lens, codes, table, bits, work)
+codetype type;
+unsigned short FAR *lens;
+unsigned codes;
+code FAR * FAR *table;
+unsigned FAR *bits;
+unsigned short FAR *work;
+{
+    unsigned len;               /* a code's length in bits */
+    unsigned sym;               /* index of code symbols */
+    unsigned min, max;          /* minimum and maximum code lengths */
+    unsigned root;              /* number of index bits for root table */
+    unsigned curr;              /* number of index bits for current table */
+    unsigned drop;              /* code bits to drop for sub-table */
+    int left;                   /* number of prefix codes available */
+    unsigned used;              /* code entries in table used */
+    unsigned huff;              /* Huffman code */
+    unsigned incr;              /* for incrementing code, index */
+    unsigned fill;              /* index for replicating entries */
+    unsigned low;               /* low bits for current root entry */
+    unsigned mask;              /* mask for low root bits */
+    code here;                  /* table entry for duplication */
+    code FAR *next;             /* next available space in table */
+    const unsigned short FAR *base;     /* base value table to use */
+    const unsigned short FAR *extra;    /* extra bits table to use */
+    int end;                    /* use base and extra for symbol > end */
+    unsigned short count[MAXBITS+1];    /* number of codes of each length */
+    unsigned short offs[MAXBITS+1];     /* offsets in table for each length */
+    static const unsigned short lbase[31] = { /* Length codes 257..285 base */
+        3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
+        35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0};
+    static const unsigned short lext[31] = { /* Length codes 257..285 extra */
+        16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18,
+        19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 73, 195};
+    static const unsigned short dbase[32] = { /* Distance codes 0..29 base */
+        1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
+        257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
+        8193, 12289, 16385, 24577, 0, 0};
+    static const unsigned short dext[32] = { /* Distance codes 0..29 extra */
+        16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22,
+        23, 23, 24, 24, 25, 25, 26, 26, 27, 27,
+        28, 28, 29, 29, 64, 64};
+
+    /*
+       Process a set of code lengths to create a canonical Huffman code.  The
+       code lengths are lens[0..codes-1].  Each length corresponds to the
+       symbols 0..codes-1.  The Huffman code is generated by first sorting the
+       symbols by length from short to long, and retaining the symbol order
+       for codes with equal lengths.  Then the code starts with all zero bits
+       for the first code of the shortest length, and the codes are integer
+       increments for the same length, and zeros are appended as the length
+       increases.  For the deflate format, these bits are stored backwards
+       from their more natural integer increment ordering, and so when the
+       decoding tables are built in the large loop below, the integer codes
+       are incremented backwards.
+
+       This routine assumes, but does not check, that all of the entries in
+       lens[] are in the range 0..MAXBITS.  The caller must assure this.
+       1..MAXBITS is interpreted as that code length.  zero means that that
+       symbol does not occur in this code.
+
+       The codes are sorted by computing a count of codes for each length,
+       creating from that a table of starting indices for each length in the
+       sorted table, and then entering the symbols in order in the sorted
+       table.  The sorted table is work[], with that space being provided by
+       the caller.
+
+       The length counts are used for other purposes as well, i.e. finding
+       the minimum and maximum length codes, determining if there are any
+       codes at all, checking for a valid set of lengths, and looking ahead
+       at length counts to determine sub-table sizes when building the
+       decoding tables.
+     */
+
+    /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */
+    for (len = 0; len <= MAXBITS; len++)
+        count[len] = 0;
+    for (sym = 0; sym < codes; sym++)
+        count[lens[sym]]++;
+
+    /* bound code lengths, force root to be within code lengths */
+    root = *bits;
+    for (max = MAXBITS; max >= 1; max--)
+        if (count[max] != 0) break;
+    if (root > max) root = max;
+    if (max == 0) {                     /* no symbols to code at all */
+        here.op = (unsigned char)64;    /* invalid code marker */
+        here.bits = (unsigned char)1;
+        here.val = (unsigned short)0;
+        *(*table)++ = here;             /* make a table to force an error */
+        *(*table)++ = here;
+        *bits = 1;
+        return 0;     /* no symbols, but wait for decoding to report error */
+    }
+    for (min = 1; min < max; min++)
+        if (count[min] != 0) break;
+    if (root < min) root = min;
+
+    /* check for an over-subscribed or incomplete set of lengths */
+    left = 1;
+    for (len = 1; len <= MAXBITS; len++) {
+        left <<= 1;
+        left -= count[len];
+        if (left < 0) return -1;        /* over-subscribed */
+    }
+    if (left > 0 && (type == CODES || max != 1))
+        return -1;                      /* incomplete set */
+
+    /* generate offsets into symbol table for each length for sorting */
+    offs[1] = 0;
+    for (len = 1; len < MAXBITS; len++)
+        offs[len + 1] = offs[len] + count[len];
+
+    /* sort symbols by length, by symbol order within each length */
+    for (sym = 0; sym < codes; sym++)
+        if (lens[sym] != 0) work[offs[lens[sym]]++] = (unsigned short)sym;
+
+    /*
+       Create and fill in decoding tables.  In this loop, the table being
+       filled is at next and has curr index bits.  The code being used is huff
+       with length len.  That code is converted to an index by dropping drop
+       bits off of the bottom.  For codes where len is less than drop + curr,
+       those top drop + curr - len bits are incremented through all values to
+       fill the table with replicated entries.
+
+       root is the number of index bits for the root table.  When len exceeds
+       root, sub-tables are created pointed to by the root entry with an index
+       of the low root bits of huff.  This is saved in low to check for when a
+       new sub-table should be started.  drop is zero when the root table is
+       being filled, and drop is root when sub-tables are being filled.
+
+       When a new sub-table is needed, it is necessary to look ahead in the
+       code lengths to determine what size sub-table is needed.  The length
+       counts are used for this, and so count[] is decremented as codes are
+       entered in the tables.
+
+       used keeps track of how many table entries have been allocated from the
+       provided *table space.  It is checked for LENS and DIST tables against
+       the constants ENOUGH_LENS and ENOUGH_DISTS to guard against changes in
+       the initial root table size constants.  See the comments in inftrees.h
+       for more information.
+
+       sym increments through all symbols, and the loop terminates when
+       all codes of length max, i.e. all codes, have been processed.  This
+       routine permits incomplete codes, so another loop after this one fills
+       in the rest of the decoding tables with invalid code markers.
+     */
+
+    /* set up for code type */
+    switch (type) {
+    case CODES:
+        base = extra = work;    /* dummy value--not used */
+        end = 19;
+        break;
+    case LENS:
+        base = lbase;
+        base -= 257;
+        extra = lext;
+        extra -= 257;
+        end = 256;
+        break;
+    default:            /* DISTS */
+        base = dbase;
+        extra = dext;
+        end = -1;
+    }
+
+    /* initialize state for loop */
+    huff = 0;                   /* starting code */
+    sym = 0;                    /* starting code symbol */
+    len = min;                  /* starting code length */
+    next = *table;              /* current table to fill in */
+    curr = root;                /* current table index bits */
+    drop = 0;                   /* current bits to drop from code for index */
+    low = (unsigned)(-1);       /* trigger new sub-table when len > root */
+    used = 1U << root;          /* use root table entries */
+    mask = used - 1;            /* mask for comparing low */
+
+    /* check available table space */
+    if ((type == LENS && used >= ENOUGH_LENS) ||
+        (type == DISTS && used >= ENOUGH_DISTS))
+        return 1;
+
+    /* process all codes and make table entries */
+    for (;;) {
+        /* create table entry */
+        here.bits = (unsigned char)(len - drop);
+        if ((int)(work[sym]) < end) {
+            here.op = (unsigned char)0;
+            here.val = work[sym];
+        }
+        else if ((int)(work[sym]) > end) {
+            here.op = (unsigned char)(extra[work[sym]]);
+            here.val = base[work[sym]];
+        }
+        else {
+            here.op = (unsigned char)(32 + 64);         /* end of block */
+            here.val = 0;
+        }
+
+        /* replicate for those indices with low len bits equal to huff */
+        incr = 1U << (len - drop);
+        fill = 1U << curr;
+        min = fill;                 /* save offset to next table */
+        do {
+            fill -= incr;
+            next[(huff >> drop) + fill] = here;
+        } while (fill != 0);
+
+        /* backwards increment the len-bit code huff */
+        incr = 1U << (len - 1);
+        while (huff & incr)
+            incr >>= 1;
+        if (incr != 0) {
+            huff &= incr - 1;
+            huff += incr;
+        }
+        else
+            huff = 0;
+
+        /* go to next symbol, update count, len */
+        sym++;
+        if (--(count[len]) == 0) {
+            if (len == max) break;
+            len = lens[work[sym]];
+        }
+
+        /* create new sub-table if needed */
+        if (len > root && (huff & mask) != low) {
+            /* if first time, transition to sub-tables */
+            if (drop == 0)
+                drop = root;
+
+            /* increment past last table */
+            next += min;            /* here min is 1 << curr */
+
+            /* determine length of next table */
+            curr = len - drop;
+            left = (int)(1 << curr);
+            while (curr + drop < max) {
+                left -= count[curr + drop];
+                if (left <= 0) break;
+                curr++;
+                left <<= 1;
+            }
+
+            /* check for enough space */
+            used += 1U << curr;
+            if ((type == LENS && used >= ENOUGH_LENS) ||
+                (type == DISTS && used >= ENOUGH_DISTS))
+                return 1;
+
+            /* point entry in root table to sub-table */
+            low = huff & mask;
+            (*table)[low].op = (unsigned char)curr;
+            (*table)[low].bits = (unsigned char)root;
+            (*table)[low].val = (unsigned short)(next - *table);
+        }
+    }
+
+    /*
+       Fill in rest of table for incomplete codes.  This loop is similar to the
+       loop above in incrementing huff for table indices.  It is assumed that
+       len is equal to curr + drop, so there is no loop needed to increment
+       through high index bits.  When the current sub-table is filled, the loop
+       drops back to the root table to fill in any remaining entries there.
+     */
+    here.op = (unsigned char)64;                /* invalid code marker */
+    here.bits = (unsigned char)(len - drop);
+    here.val = (unsigned short)0;
+    while (huff != 0) {
+        /* when done with sub-table, drop back to root table */
+        if (drop != 0 && (huff & mask) != low) {
+            drop = 0;
+            len = root;
+            next = *table;
+            here.bits = (unsigned char)len;
+        }
+
+        /* put invalid code marker in table */
+        next[huff >> drop] = here;
+
+        /* backwards increment the len-bit code huff */
+        incr = 1U << (len - 1);
+        while (huff & incr)
+            incr >>= 1;
+        if (incr != 0) {
+            huff &= incr - 1;
+            huff += incr;
+        }
+        else
+            huff = 0;
+    }
+
+    /* set return parameters */
+    *table += used;
+    *bits = root;
+    return 0;
+}
diff --git a/src/plugins/cfitsio/inftrees.h b/src/plugins/cfitsio/inftrees.h
new file mode 100644
index 0000000..baa53a0
--- /dev/null
+++ b/src/plugins/cfitsio/inftrees.h
@@ -0,0 +1,62 @@
+/* inftrees.h -- header to use inftrees.c
+ * Copyright (C) 1995-2005, 2010 Mark Adler
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* WARNING: this file should *not* be used by applications. It is
+   part of the implementation of the compression library and is
+   subject to change. Applications should only use zlib.h.
+ */
+
+/* Structure for decoding tables.  Each entry provides either the
+   information needed to do the operation requested by the code that
+   indexed that table entry, or it provides a pointer to another
+   table that indexes more bits of the code.  op indicates whether
+   the entry is a pointer to another table, a literal, a length or
+   distance, an end-of-block, or an invalid code.  For a table
+   pointer, the low four bits of op is the number of index bits of
+   that table.  For a length or distance, the low four bits of op
+   is the number of extra bits to get after the code.  bits is
+   the number of bits in this code or part of the code to drop off
+   of the bit buffer.  val is the actual byte to output in the case
+   of a literal, the base length or distance, or the offset from
+   the current table to the next table.  Each entry is four bytes. */
+typedef struct {
+    unsigned char op;           /* operation, extra bits, table bits */
+    unsigned char bits;         /* bits in this part of the code */
+    unsigned short val;         /* offset in table or code value */
+} code;
+
+/* op values as set by inflate_table():
+    00000000 - literal
+    0000tttt - table link, tttt != 0 is the number of table index bits
+    0001eeee - length or distance, eeee is the number of extra bits
+    01100000 - end of block
+    01000000 - invalid code
+ */
+
+/* Maximum size of the dynamic table.  The maximum number of code structures is
+   1444, which is the sum of 852 for literal/length codes and 592 for distance
+   codes.  These values were found by exhaustive searches using the program
+   examples/enough.c found in the zlib distribtution.  The arguments to that
+   program are the number of symbols, the initial root table size, and the
+   maximum bit length of a code.  "enough 286 9 15" for literal/length codes
+   returns returns 852, and "enough 30 6 15" for distance codes returns 592.
+   The initial root table size (9 or 6) is found in the fifth argument of the
+   inflate_table() calls in inflate.c and infback.c.  If the root table size is
+   changed, then these maximum sizes would be need to be recalculated and
+   updated. */
+#define ENOUGH_LENS 852
+#define ENOUGH_DISTS 592
+#define ENOUGH (ENOUGH_LENS+ENOUGH_DISTS)
+
+/* Type of code to build for inflate_table() */
+typedef enum {
+    CODES,
+    LENS,
+    DISTS
+} codetype;
+
+int ZLIB_INTERNAL inflate_table OF((codetype type, unsigned short FAR *lens,
+                             unsigned codes, code FAR * FAR *table,
+                             unsigned FAR *bits, unsigned short FAR *work));
diff --git a/src/plugins/cfitsio/iraffits.c b/src/plugins/cfitsio/iraffits.c
new file mode 100644
index 0000000..d8fc06b
--- /dev/null
+++ b/src/plugins/cfitsio/iraffits.c
@@ -0,0 +1,2073 @@
+/*------------------------------------------------------------------------*/
+/*                                                                        */
+/*  These routines have been modified by William Pence for use by CFITSIO */
+/*        The original files were provided by Doug Mink                   */
+/*------------------------------------------------------------------------*/
+
+/* File imhfile.c
+ * August 6, 1998
+ * By Doug Mink, based on Mike VanHilst's readiraf.c
+
+ * Module:      imhfile.c (IRAF .imh image file reading and writing)
+ * Purpose:     Read and write IRAF image files (and translate headers)
+ * Subroutine:  irafrhead (filename, lfhead, fitsheader, lihead)
+ *              Read IRAF image header
+ * Subroutine:  irafrimage (fitsheader)
+ *              Read IRAF image pixels (call after irafrhead)
+ * Subroutine:	same_path (pixname, hdrname)
+ *		Put filename and header path together
+ * Subroutine:	iraf2fits (hdrname, irafheader, nbiraf, nbfits)
+ *		Convert IRAF image header to FITS image header
+ * Subroutine:  irafgeti4 (irafheader, offset)
+ *		Get 4-byte integer from arbitrary part of IRAF header
+ * Subroutine:  irafgetc2 (irafheader, offset)
+ *		Get character string from arbitrary part of IRAF v.1 header
+ * Subroutine:  irafgetc (irafheader, offset)
+ *		Get character string from arbitrary part of IRAF header
+ * Subroutine:  iraf2str (irafstring, nchar)
+ * 		Convert 2-byte/char IRAF string to 1-byte/char string
+ * Subroutine:	irafswap (bitpix,string,nbytes)
+ *		Swap bytes in string in place, with FITS bits/pixel code
+ * Subroutine:	irafswap2 (string,nbytes)
+ *		Swap bytes in string in place
+ * Subroutine	irafswap4 (string,nbytes)
+ *		Reverse bytes of Integer*4 or Real*4 vector in place
+ * Subroutine	irafswap8 (string,nbytes)
+ *		Reverse bytes of Real*8 vector in place
+
+
+ * Copyright:   2000 Smithsonian Astrophysical Observatory
+ *              You may do anything you like with this file except remove
+ *              this copyright.  The Smithsonian Astrophysical Observatory
+ *              makes no representations about the suitability of this
+ *              software for any purpose.  It is provided "as is" without
+ *              express or implied warranty.
+ */
+
+#include <stdio.h>		/* define stderr, FD, and NULL */
+#include <stdlib.h>
+#include <stddef.h>  /* stddef.h is apparently needed to define size_t */
+#include <string.h>
+
+#define FILE_NOT_OPENED 104
+
+/* Parameters from iraf/lib/imhdr.h for IRAF version 1 images */
+#define SZ_IMPIXFILE	 79		/* name of pixel storage file */
+#define SZ_IMHDRFILE	 79   		/* length of header storage file */
+#define SZ_IMTITLE	 79		/* image title string */
+#define LEN_IMHDR	2052		/* length of std header */
+
+/* Parameters from iraf/lib/imhdr.h for IRAF version 2 images */
+#define	SZ_IM2PIXFILE	255		/* name of pixel storage file */
+#define	SZ_IM2HDRFILE	255		/* name of header storage file */
+#define	SZ_IM2TITLE	383		/* image title string */
+#define LEN_IM2HDR	2046		/* length of std header */
+
+/* Offsets into header in bytes for parameters in IRAF version 1 images */
+#define IM_HDRLEN	 12		/* Length of header in 4-byte ints */
+#define IM_PIXTYPE       16             /* Datatype of the pixels */
+#define IM_NDIM          20             /* Number of dimensions */
+#define IM_LEN           24             /* Length (as stored) */
+#define IM_PHYSLEN       52             /* Physical length (as stored) */
+#define IM_PIXOFF        88             /* Offset of the pixels */
+#define IM_CTIME        108             /* Time of image creation */
+#define IM_MTIME        112             /* Time of last modification */
+#define IM_LIMTIME      116             /* Time of min,max computation */
+#define IM_MAX          120             /* Maximum pixel value */
+#define IM_MIN          124             /* Maximum pixel value */
+#define IM_PIXFILE      412             /* Name of pixel storage file */
+#define IM_HDRFILE      572             /* Name of header storage file */
+#define IM_TITLE        732             /* Image name string */
+
+/* Offsets into header in bytes for parameters in IRAF version 2 images */
+#define IM2_HDRLEN	  6		/* Length of header in 4-byte ints */
+#define IM2_PIXTYPE      10             /* Datatype of the pixels */
+#define IM2_SWAPPED      14             /* Pixels are byte swapped */
+#define IM2_NDIM         18             /* Number of dimensions */
+#define IM2_LEN          22             /* Length (as stored) */
+#define IM2_PHYSLEN      50             /* Physical length (as stored) */
+#define IM2_PIXOFF       86             /* Offset of the pixels */
+#define IM2_CTIME       106             /* Time of image creation */
+#define IM2_MTIME       110             /* Time of last modification */
+#define IM2_LIMTIME     114             /* Time of min,max computation */
+#define IM2_MAX         118             /* Maximum pixel value */
+#define IM2_MIN         122             /* Maximum pixel value */
+#define IM2_PIXFILE     126             /* Name of pixel storage file */
+#define IM2_HDRFILE     382             /* Name of header storage file */
+#define IM2_TITLE       638             /* Image name string */
+
+/* Codes from iraf/unix/hlib/iraf.h */
+#define	TY_CHAR		2
+#define	TY_SHORT	3
+#define	TY_INT		4
+#define	TY_LONG		5
+#define	TY_REAL		6
+#define	TY_DOUBLE	7
+#define	TY_COMPLEX	8
+#define TY_POINTER      9
+#define TY_STRUCT       10
+#define TY_USHORT       11
+#define TY_UBYTE        12
+
+#define LEN_PIXHDR	1024
+#define MAXINT  2147483647 /* Biggest number that can fit in long */
+
+static int isirafswapped(char *irafheader, int offset);
+static int irafgeti4(char *irafheader, int offset);
+static char *irafgetc2(char *irafheader, int offset, int nc);
+static char *irafgetc(char *irafheader,	int offset, int	nc);
+static char *iraf2str(char *irafstring, int nchar);
+static char *irafrdhead(char *filename, int *lihead);
+static int irafrdimage (char **buffptr, size_t *buffsize,
+    size_t *filesize, int *status);
+static int iraftofits (char *hdrname, char *irafheader, int nbiraf,
+    char **buffptr, size_t *nbfits, size_t *fitssize, int *status);
+static char *same_path(char *pixname, char *hdrname);
+
+static int swaphead=0;	/* =1 to swap data bytes of IRAF header values */
+static int swapdata=0;  /* =1 to swap bytes in IRAF data pixels */
+
+static void irafswap(int bitpix, char *string, int nbytes);
+static void irafswap2(char *string, int nbytes);
+static void irafswap4(char *string, int nbytes);
+static void irafswap8(char *string, int nbytes);
+static int pix_version (char *irafheader);
+static int irafncmp (char *irafheader, char *teststring, int nc);
+static int machswap(void);
+static int head_version (char *irafheader);
+static int hgeti4(char* hstring, char* keyword, int* val);
+static int hgets(char* hstring, char* keyword, int lstr, char* string);
+static char* hgetc(char* hstring, char* keyword);
+static char* ksearch(char* hstring, char* keyword);
+static char *blsearch (char* hstring, char* keyword);	
+static char *strsrch (char* s1,	char* s2);
+static char *strnsrch (	char* s1,char* s2,int ls1);
+static void hputi4(char* hstring,char* keyword,	int ival);
+static void hputs(char* hstring,char* keyword,char* cval);
+static void hputcom(char* hstring,char* keyword,char* comment);
+static void hputl(char* hstring,char* keyword,int lval);
+static void hputc(char* hstring,char* keyword,char* cval);
+static int getirafpixname (char *hdrname, char *irafheader, char *pixfilename, int *status);
+int iraf2mem(char *filename, char **buffptr, size_t *buffsize, 
+      size_t *filesize, int *status);
+
+void ffpmsg(const char *err_message);
+
+/*--------------------------------------------------------------------------*/
+int fits_delete_iraf_file(char *filename,     /* name of input file                 */
+             int *status)        /* IO - error status                       */
+
+/*
+   Delete the iraf .imh header file and the associated .pix data file
+*/
+{
+    char *irafheader;
+    int lenirafhead;
+
+    char pixfilename[SZ_IM2PIXFILE+1];
+
+    /* read IRAF header into dynamically created char array (free it later!) */
+    irafheader = irafrdhead(filename, &lenirafhead);
+
+    if (!irafheader)
+    {
+	return(*status = FILE_NOT_OPENED);
+    }
+
+    getirafpixname (filename, irafheader, pixfilename, status);
+
+    /* don't need the IRAF header any more */
+    free(irafheader);
+
+    if (*status > 0)
+       return(*status);
+
+    remove(filename);
+    remove(pixfilename);
+    
+    return(*status);
+}
+
+/*--------------------------------------------------------------------------*/
+int iraf2mem(char *filename,     /* name of input file                 */
+             char **buffptr,     /* O - memory pointer (initially NULL)    */
+             size_t *buffsize,   /* O - size of mem buffer, in bytes        */
+             size_t *filesize,   /* O - size of FITS file, in bytes         */
+             int *status)        /* IO - error status                       */
+
+/*
+   Driver routine that reads an IRAF image into memory, also converting
+   it into FITS format.
+*/
+{
+    char *irafheader;
+    int lenirafhead;
+
+    *buffptr = NULL;
+    *buffsize = 0;
+    *filesize = 0;
+
+    /* read IRAF header into dynamically created char array (free it later!) */
+    irafheader = irafrdhead(filename, &lenirafhead);
+
+    if (!irafheader)
+    {
+	return(*status = FILE_NOT_OPENED);
+    }
+
+    /* convert IRAF header to FITS header in memory */
+    iraftofits(filename, irafheader, lenirafhead, buffptr, buffsize, filesize,
+               status);
+
+    /* don't need the IRAF header any more */
+    free(irafheader);
+
+    if (*status > 0)
+       return(*status);
+
+    *filesize = (((*filesize - 1) / 2880 ) + 1 ) * 2880; /* multiple of 2880 */
+
+    /* append the image data onto the FITS header */
+    irafrdimage(buffptr, buffsize, filesize, status);
+
+    return(*status);
+}
+
+/*--------------------------------------------------------------------------*/
+/* Subroutine:	irafrdhead  (was irafrhead in D. Mink's original code)
+ * Purpose:	Open and read the iraf .imh file.
+ * Returns:	NULL if failure, else pointer to IRAF .imh image header
+ * Notes:	The imhdr format is defined in iraf/lib/imhdr.h, some of
+ *		which defines or mimicked, above.
+ */
+
+static char *irafrdhead (
+    char *filename,   /* Name of IRAF header file */
+    int *lihead)     /* Length of IRAF image header in bytes (returned) */
+{
+    FILE *fd;
+    int nbr;
+    char *irafheader;
+    char errmsg[81];
+    long nbhead;
+    int nihead;
+
+    *lihead = 0;
+
+    /* open the image header file */
+    fd = fopen (filename, "rb");
+    if (fd == NULL) {
+        ffpmsg("unable to open IRAF header file:");
+        ffpmsg(filename);
+	return (NULL);
+	}
+
+    /* Find size of image header file */
+    if (fseek(fd, 0, 2) != 0)  /* move to end of the file */
+    {
+        ffpmsg("IRAFRHEAD: cannot seek in file:");
+        ffpmsg(filename);
+        return(NULL);
+    }
+
+    nbhead = ftell(fd);     /* position = size of file */
+    if (nbhead < 0)
+    {
+        ffpmsg("IRAFRHEAD: cannot get pos. in file:");
+        ffpmsg(filename);
+        return(NULL);
+    }
+
+    if (fseek(fd, 0, 0) != 0) /* move back to beginning */
+    {
+        ffpmsg("IRAFRHEAD: cannot seek to beginning of file:");
+        ffpmsg(filename);
+        return(NULL);
+    }
+
+    /* allocate initial sized buffer */
+    nihead = nbhead + 5000;
+    irafheader = (char *) calloc (1, nihead);
+    if (irafheader == NULL) {
+	sprintf(errmsg, "IRAFRHEAD Cannot allocate %d-byte header",
+		      nihead);
+        ffpmsg(errmsg);
+        ffpmsg(filename);
+	return (NULL);
+	}
+    *lihead = nihead;
+
+    /* Read IRAF header */
+    nbr = fread (irafheader, 1, nbhead, fd);
+    fclose (fd);
+
+    /* Reject if header less than minimum length */
+    if (nbr < LEN_PIXHDR) {
+	sprintf(errmsg, "IRAFRHEAD header file: %d / %d bytes read.",
+		      nbr,LEN_PIXHDR);
+        ffpmsg(errmsg);
+        ffpmsg(filename);
+	free (irafheader);
+	return (NULL);
+	}
+
+    return (irafheader);
+}
+/*--------------------------------------------------------------------------*/
+static int irafrdimage (
+    char **buffptr,	/* FITS image header (filled) */
+    size_t *buffsize,      /* allocated size of the buffer */
+    size_t *filesize,      /* actual size of the FITS file */
+    int *status)
+{
+    FILE *fd;
+    char *bang;
+    int nax = 1, naxis1 = 1, naxis2 = 1, naxis3 = 1, naxis4 = 1, npaxis1 = 1, npaxis2;
+    int bitpix, bytepix, i;
+    char *fitsheader, *image;
+    int nbr, nbimage, nbaxis, nbl, nbdiff;
+    char *pixheader;
+    char *linebuff;
+    int imhver, lpixhead = 0;
+    char pixname[SZ_IM2PIXFILE+1];
+    char errmsg[81];
+    size_t newfilesize;
+ 
+    fitsheader = *buffptr;           /* pointer to start of header */
+
+    /* Convert pixel file name to character string */
+    hgets (fitsheader, "PIXFILE", SZ_IM2PIXFILE, pixname);
+    hgeti4 (fitsheader, "PIXOFF", &lpixhead);
+
+    /* Open pixel file, ignoring machine name if present */
+    if ((bang = strchr (pixname, '!')) != NULL )
+	fd = fopen (bang + 1, "rb");
+    else
+	fd = fopen (pixname, "rb");
+
+    /* Print error message and exit if pixel file is not found */
+    if (!fd) {
+        ffpmsg("IRAFRIMAGE: Cannot open IRAF pixel file:");
+        ffpmsg(pixname);
+	return (*status = FILE_NOT_OPENED);
+	}
+
+    /* Read pixel header */
+    pixheader = (char *) calloc (lpixhead, 1);
+    if (pixheader == NULL) {
+            ffpmsg("IRAFRIMAGE: Cannot alloc memory for pixel header");
+            ffpmsg(pixname);
+            fclose (fd);
+	    return (*status = FILE_NOT_OPENED);
+	}
+    nbr = fread (pixheader, 1, lpixhead, fd);
+
+    /* Check size of pixel header */
+    if (nbr < lpixhead) {
+	sprintf(errmsg, "IRAF pixel file: %d / %d bytes read.",
+		      nbr,LEN_PIXHDR);
+        ffpmsg(errmsg);
+	free (pixheader);
+	fclose (fd);
+	return (*status = FILE_NOT_OPENED);
+	}
+
+    /* check pixel header magic word */
+    imhver = pix_version (pixheader);
+    if (imhver < 1) {
+        ffpmsg("File not valid IRAF pixel file:");
+        ffpmsg(pixname);
+	free (pixheader);
+	fclose (fd);
+	return (*status = FILE_NOT_OPENED);
+	}
+    free (pixheader);
+
+    /* Find number of bytes to read */
+    hgeti4 (fitsheader,"NAXIS",&nax);
+    hgeti4 (fitsheader,"NAXIS1",&naxis1);
+    hgeti4 (fitsheader,"NPAXIS1",&npaxis1);
+    if (nax > 1) {
+        hgeti4 (fitsheader,"NAXIS2",&naxis2);
+        hgeti4 (fitsheader,"NPAXIS2",&npaxis2);
+	}
+    if (nax > 2)
+        hgeti4 (fitsheader,"NAXIS3",&naxis3);
+    if (nax > 3)
+        hgeti4 (fitsheader,"NAXIS4",&naxis4);
+
+    hgeti4 (fitsheader,"BITPIX",&bitpix);
+    if (bitpix < 0)
+	bytepix = -bitpix / 8;
+    else
+	bytepix = bitpix / 8;
+
+    nbimage = naxis1 * naxis2 * naxis3 * naxis4 * bytepix;
+    
+    newfilesize = *filesize + nbimage;  /* header + data */
+    newfilesize = (((newfilesize - 1) / 2880 ) + 1 ) * 2880;
+
+    if (newfilesize > *buffsize)   /* need to allocate more memory? */
+    {
+      fitsheader =  (char *) realloc (*buffptr, newfilesize);
+      if (fitsheader == NULL) {
+	sprintf(errmsg, "IRAFRIMAGE Cannot allocate %d-byte image buffer",
+		(int) (*filesize));
+        ffpmsg(errmsg);
+        ffpmsg(pixname);
+	fclose (fd);
+	return (*status = FILE_NOT_OPENED);
+	}
+    }
+
+    *buffptr = fitsheader;
+    *buffsize = newfilesize;
+
+    image = fitsheader + *filesize;
+    *filesize = newfilesize;
+
+    /* Read IRAF image all at once if physical and image dimensions are the same */
+    if (npaxis1 == naxis1)
+	nbr = fread (image, 1, nbimage, fd);
+
+    /* Read IRAF image one line at a time if physical and image dimensions differ */
+    else {
+	nbdiff = (npaxis1 - naxis1) * bytepix;
+	nbaxis = naxis1 * bytepix;
+	linebuff = image;
+	nbr = 0;
+	if (naxis2 == 1 && naxis3 > 1)
+	    naxis2 = naxis3;
+	for (i = 0; i < naxis2; i++) {
+	    nbl = fread (linebuff, 1, nbaxis, fd);
+	    nbr = nbr + nbl;
+	    fseek (fd, nbdiff, 1);
+	    linebuff = linebuff + nbaxis;
+	    }
+	}
+    fclose (fd);
+
+    /* Check size of image */
+    if (nbr < nbimage) {
+	sprintf(errmsg, "IRAF pixel file: %d / %d bytes read.",
+		      nbr,nbimage);
+        ffpmsg(errmsg);
+        ffpmsg(pixname);
+	return (*status = FILE_NOT_OPENED);
+	}
+
+    /* Byte-reverse image, if necessary */
+    if (swapdata)
+	irafswap (bitpix, image, nbimage);
+
+    return (*status);
+}
+/*--------------------------------------------------------------------------*/
+/* Return IRAF image format version number from magic word in IRAF header*/
+
+static int head_version (
+    char *irafheader)	/* IRAF image header from file */
+
+{
+
+    /* Check header file magic word */
+    if (irafncmp (irafheader, "imhdr", 5) != 0 ) {
+	if (strncmp (irafheader, "imhv2", 5) != 0)
+	    return (0);
+	else
+	    return (2);
+	}
+    else
+	return (1);
+}
+
+/*--------------------------------------------------------------------------*/
+/* Return IRAF image format version number from magic word in IRAF pixel file */
+
+static int pix_version (
+    char *irafheader)   /* IRAF image header from file */
+{
+
+    /* Check pixel file header magic word */
+    if (irafncmp (irafheader, "impix", 5) != 0) {
+	if (strncmp (irafheader, "impv2", 5) != 0)
+	    return (0);
+	else
+	    return (2);
+	}
+    else
+	return (1);
+}
+
+/*--------------------------------------------------------------------------*/
+/* Verify that file is valid IRAF imhdr or impix by checking first 5 chars
+ * Returns:	0 on success, 1 on failure */
+
+static int irafncmp (
+
+char	*irafheader,	/* IRAF image header from file */
+char	*teststring,	/* C character string to compare */
+int	nc)		/* Number of characters to compate */
+
+{
+    char *line;
+
+    if ((line = iraf2str (irafheader, nc)) == NULL)
+	return (1);
+    if (strncmp (line, teststring, nc) == 0) {
+	free (line);
+	return (0);
+	}
+    else {
+	free (line);
+	return (1);
+	}
+}
+/*--------------------------------------------------------------------------*/
+
+/* Convert IRAF image header to FITS image header, returning FITS header */
+
+static int iraftofits (
+    char    *hdrname,  /* IRAF header file name (may be path) */
+    char    *irafheader,  /* IRAF image header */
+    int	    nbiraf,	  /* Number of bytes in IRAF header */
+    char    **buffptr,    /* pointer to the FITS header  */
+    size_t  *nbfits,      /* allocated size of the FITS header buffer */
+    size_t  *fitssize,  /* Number of bytes in FITS header (returned) */
+                        /*  = number of bytes to the end of the END keyword */
+    int     *status)
+{
+    char *objname;	/* object name from FITS file */
+    int lstr, i, j, k, ib, nax, nbits;
+    char *pixname, *newpixname, *bang, *chead;
+    char *fitsheader;
+    int nblock, nlines;
+    char *fhead, *fhead1, *fp, endline[81];
+    char irafchar;
+    char fitsline[81];
+    int pixtype;
+    int imhver, n, imu, pixoff, impixoff;
+/*    int immax, immin, imtime;  */
+    int imndim, imlen, imphyslen, impixtype;
+    char errmsg[81];
+
+    /* Set up last line of FITS header */
+    (void)strncpy (endline,"END", 3);
+    for (i = 3; i < 80; i++)
+	endline[i] = ' ';
+    endline[80] = 0;
+
+    /* Check header magic word */
+    imhver = head_version (irafheader);
+    if (imhver < 1) {
+	ffpmsg("File not valid IRAF image header");
+        ffpmsg(hdrname);
+	return(*status = FILE_NOT_OPENED);
+	}
+    if (imhver == 2) {
+	nlines = 24 + ((nbiraf - LEN_IM2HDR) / 81);
+	imndim = IM2_NDIM;
+	imlen = IM2_LEN;
+	imphyslen = IM2_PHYSLEN;
+	impixtype = IM2_PIXTYPE;
+	impixoff = IM2_PIXOFF;
+/*	imtime = IM2_MTIME; */
+/*	immax = IM2_MAX;  */
+/*	immin = IM2_MIN; */
+	}
+    else {
+	nlines = 24 + ((nbiraf - LEN_IMHDR) / 162);
+	imndim = IM_NDIM;
+	imlen = IM_LEN;
+	imphyslen = IM_PHYSLEN;
+	impixtype = IM_PIXTYPE;
+	impixoff = IM_PIXOFF;
+/*	imtime = IM_MTIME; */
+/*	immax = IM_MAX; */
+/*	immin = IM_MIN; */
+	}
+
+    /*  Initialize FITS header */
+    nblock = (nlines * 80) / 2880;
+    *nbfits = (nblock + 5) * 2880 + 4;
+    fitsheader = (char *) calloc (*nbfits, 1);
+    if (fitsheader == NULL) {
+	sprintf(errmsg, "IRAF2FITS Cannot allocate %d-byte FITS header",
+		(int) (*nbfits));
+        ffpmsg(hdrname);
+	return (*status = FILE_NOT_OPENED);
+	}
+
+    fhead = fitsheader;
+    *buffptr = fitsheader;
+    (void)strncpy (fitsheader, endline, 80);
+    hputl (fitsheader, "SIMPLE", 1);
+    fhead = fhead + 80;
+
+    /*  check if the IRAF file is in big endian (sun) format (= 0) or not. */
+    /*  This is done by checking the 4 byte integer in the header that     */
+    /*  represents the iraf pixel type.  This 4-byte word is guaranteed to */
+    /*  have the least sig byte != 0 and the most sig byte = 0,  so if the */
+    /*  first byte of the word != 0, then the file in little endian format */
+    /*  like on an Alpha machine.                                          */
+
+    swaphead = isirafswapped(irafheader, impixtype);
+    if (imhver == 1)
+        swapdata = swaphead; /* vers 1 data has same swapness as header */
+    else
+        swapdata = irafgeti4 (irafheader, IM2_SWAPPED); 
+
+    /*  Set pixel size in FITS header */
+    pixtype = irafgeti4 (irafheader, impixtype);
+    switch (pixtype) {
+	case TY_CHAR:
+	    nbits = 8;
+	    break;
+	case TY_UBYTE:
+	    nbits = 8;
+	    break;
+	case TY_SHORT:
+	    nbits = 16;
+	    break;
+	case TY_USHORT:
+	    nbits = -16;
+	    break;
+	case TY_INT:
+	case TY_LONG:
+	    nbits = 32;
+	    break;
+	case TY_REAL:
+	    nbits = -32;
+	    break;
+	case TY_DOUBLE:
+	    nbits = -64;
+	    break;
+	default:
+	    sprintf(errmsg,"Unsupported IRAF data type: %d", pixtype);
+            ffpmsg(errmsg);
+            ffpmsg(hdrname);
+	    return (*status = FILE_NOT_OPENED);
+	}
+    hputi4 (fitsheader,"BITPIX",nbits);
+    hputcom (fitsheader,"BITPIX", "IRAF .imh pixel type");
+    fhead = fhead + 80;
+
+    /*  Set image dimensions in FITS header */
+    nax = irafgeti4 (irafheader, imndim);
+    hputi4 (fitsheader,"NAXIS",nax);
+    hputcom (fitsheader,"NAXIS", "IRAF .imh naxis");
+    fhead = fhead + 80;
+
+    n = irafgeti4 (irafheader, imlen);
+    hputi4 (fitsheader, "NAXIS1", n);
+    hputcom (fitsheader,"NAXIS1", "IRAF .imh image naxis[1]");
+    fhead = fhead + 80;
+
+    if (nax > 1) {
+	n = irafgeti4 (irafheader, imlen+4);
+	hputi4 (fitsheader, "NAXIS2", n);
+	hputcom (fitsheader,"NAXIS2", "IRAF .imh image naxis[2]");
+        fhead = fhead + 80;
+	}
+    if (nax > 2) {
+	n = irafgeti4 (irafheader, imlen+8);
+	hputi4 (fitsheader, "NAXIS3", n);
+	hputcom (fitsheader,"NAXIS3", "IRAF .imh image naxis[3]");
+	fhead = fhead + 80;
+	}
+    if (nax > 3) {
+	n = irafgeti4 (irafheader, imlen+12);
+	hputi4 (fitsheader, "NAXIS4", n);
+	hputcom (fitsheader,"NAXIS4", "IRAF .imh image naxis[4]");
+	fhead = fhead + 80;
+	}
+
+    /* Set object name in FITS header */
+    if (imhver == 2)
+	objname = irafgetc (irafheader, IM2_TITLE, SZ_IM2TITLE);
+    else
+	objname = irafgetc2 (irafheader, IM_TITLE, SZ_IMTITLE);
+    if ((lstr = strlen (objname)) < 8) {
+	for (i = lstr; i < 8; i++)
+	    objname[i] = ' ';
+	objname[8] = 0;
+	}
+    hputs (fitsheader,"OBJECT",objname);
+    hputcom (fitsheader,"OBJECT", "IRAF .imh title");
+    free (objname);
+    fhead = fhead + 80;
+
+    /* Save physical axis lengths so image file can be read */
+    n = irafgeti4 (irafheader, imphyslen);
+    hputi4 (fitsheader, "NPAXIS1", n);
+    hputcom (fitsheader,"NPAXIS1", "IRAF .imh physical naxis[1]");
+    fhead = fhead + 80;
+    if (nax > 1) {
+	n = irafgeti4 (irafheader, imphyslen+4);
+	hputi4 (fitsheader, "NPAXIS2", n);
+	hputcom (fitsheader,"NPAXIS2", "IRAF .imh physical naxis[2]");
+	fhead = fhead + 80;
+	}
+    if (nax > 2) {
+	n = irafgeti4 (irafheader, imphyslen+8);
+	hputi4 (fitsheader, "NPAXIS3", n);
+	hputcom (fitsheader,"NPAXIS3", "IRAF .imh physical naxis[3]");
+	fhead = fhead + 80;
+	}
+    if (nax > 3) {
+	n = irafgeti4 (irafheader, imphyslen+12);
+	hputi4 (fitsheader, "NPAXIS4", n);
+	hputcom (fitsheader,"NPAXIS4", "IRAF .imh physical naxis[4]");
+	fhead = fhead + 80;
+	}
+
+    /* Save image header filename in header */
+    hputs (fitsheader,"IMHFILE",hdrname);
+    hputcom (fitsheader,"IMHFILE", "IRAF header file name");
+    fhead = fhead + 80;
+
+    /* Save image pixel file pathname in header */
+    if (imhver == 2)
+	pixname = irafgetc (irafheader, IM2_PIXFILE, SZ_IM2PIXFILE);
+    else
+	pixname = irafgetc2 (irafheader, IM_PIXFILE, SZ_IMPIXFILE);
+    if (strncmp(pixname, "HDR", 3) == 0 ) {
+	newpixname = same_path (pixname, hdrname);
+        if (newpixname) {
+          free (pixname);
+          pixname = newpixname;
+	  }
+	}
+    if (strchr (pixname, '/') == NULL && strchr (pixname, '$') == NULL) {
+	newpixname = same_path (pixname, hdrname);
+        if (newpixname) {
+          free (pixname);
+          pixname = newpixname;
+	  }
+	}
+	
+    if ((bang = strchr (pixname, '!')) != NULL )
+	hputs (fitsheader,"PIXFILE",bang+1);
+    else
+	hputs (fitsheader,"PIXFILE",pixname);
+    free (pixname);
+    hputcom (fitsheader,"PIXFILE", "IRAF .pix pixel file");
+    fhead = fhead + 80;
+
+    /* Save image offset from star of pixel file */
+    pixoff = irafgeti4 (irafheader, impixoff);
+    pixoff = (pixoff - 1) * 2;
+    hputi4 (fitsheader, "PIXOFF", pixoff);
+    hputcom (fitsheader,"PIXOFF", "IRAF .pix pixel offset (Do not change!)");
+    fhead = fhead + 80;
+
+    /* Save IRAF file format version in header */
+    hputi4 (fitsheader,"IMHVER",imhver);
+    hputcom (fitsheader,"IMHVER", "IRAF .imh format version (1 or 2)");
+    fhead = fhead + 80;
+
+    /* Save flag as to whether to swap IRAF data for this file and machine */
+    if (swapdata)
+	hputl (fitsheader, "PIXSWAP", 1);
+    else
+	hputl (fitsheader, "PIXSWAP", 0);
+    hputcom (fitsheader,"PIXSWAP", "IRAF pixels, FITS byte orders differ if T");
+    fhead = fhead + 80;
+
+    /* Add user portion of IRAF header to FITS header */
+    fitsline[80] = 0;
+    if (imhver == 2) {
+	imu = LEN_IM2HDR;
+	chead = irafheader;
+	j = 0;
+	for (k = 0; k < 80; k++)
+	    fitsline[k] = ' ';
+	for (i = imu; i < nbiraf; i++) {
+	    irafchar = chead[i];
+	    if (irafchar == 0)
+		break;
+	    else if (irafchar == 10) {
+		(void)strncpy (fhead, fitsline, 80);
+		/* fprintf (stderr,"%80s\n",fitsline); */
+		if (strncmp (fitsline, "OBJECT ", 7) != 0) {
+		    fhead = fhead + 80;
+		    }
+		for (k = 0; k < 80; k++)
+		    fitsline[k] = ' ';
+		j = 0;
+		}
+	    else {
+		if (j > 80) {
+		    if (strncmp (fitsline, "OBJECT ", 7) != 0) {
+			(void)strncpy (fhead, fitsline, 80);
+			/* fprintf (stderr,"%80s\n",fitsline); */
+			j = 9;
+			fhead = fhead + 80;
+			}
+		    for (k = 0; k < 80; k++)
+			fitsline[k] = ' ';
+		    }
+		if (irafchar > 32 && irafchar < 127)
+		    fitsline[j] = irafchar;
+		j++;
+		}
+	    }
+	}
+    else {
+	imu = LEN_IMHDR;
+	chead = irafheader;
+	if (swaphead == 1)
+	    ib = 0;
+	else
+	    ib = 1;
+	for (k = 0; k < 80; k++)
+	    fitsline[k] = ' ';
+	j = 0;
+	for (i = imu; i < nbiraf; i=i+2) {
+	    irafchar = chead[i+ib];
+	    if (irafchar == 0)
+		break;
+	    else if (irafchar == 10) {
+		if (strncmp (fitsline, "OBJECT ", 7) != 0) {
+		    (void)strncpy (fhead, fitsline, 80);
+		    fhead = fhead + 80;
+		    }
+		/* fprintf (stderr,"%80s\n",fitsline); */
+		j = 0;
+		for (k = 0; k < 80; k++)
+		    fitsline[k] = ' ';
+		}
+	    else {
+		if (j > 80) {
+		    if (strncmp (fitsline, "OBJECT ", 7) != 0) {
+			(void)strncpy (fhead, fitsline, 80);
+			j = 9;
+			fhead = fhead + 80;
+			}
+		    /* fprintf (stderr,"%80s\n",fitsline); */
+		    for (k = 0; k < 80; k++)
+			fitsline[k] = ' ';
+		    }
+		if (irafchar > 32 && irafchar < 127)
+		    fitsline[j] = irafchar;
+		j++;
+		}
+	    }
+	}
+
+    /* Add END to last line */
+    (void)strncpy (fhead, endline, 80);
+
+    /* Find end of last 2880-byte block of header */
+    fhead = ksearch (fitsheader, "END") + 80;
+    nblock = *nbfits / 2880;
+    fhead1 = fitsheader + (nblock * 2880);
+    *fitssize = fhead - fitsheader;  /* no. of bytes to end of END keyword */
+
+    /* Pad rest of header with spaces */
+    strncpy (endline,"   ",3);
+    for (fp = fhead; fp < fhead1; fp = fp + 80) {
+	(void)strncpy (fp, endline,80);
+	}
+
+    return (*status);
+}
+/*--------------------------------------------------------------------------*/
+
+/* get the IRAF pixel file name */
+
+static int getirafpixname (
+    char    *hdrname,  /* IRAF header file name (may be path) */
+    char    *irafheader,  /* IRAF image header */
+    char    *pixfilename,     /* IRAF pixel file name */
+    int     *status)
+{
+    int imhver;
+    char *pixname, *newpixname, *bang;
+
+    /* Check header magic word */
+    imhver = head_version (irafheader);
+    if (imhver < 1) {
+	ffpmsg("File not valid IRAF image header");
+        ffpmsg(hdrname);
+	return(*status = FILE_NOT_OPENED);
+	}
+
+    /* get image pixel file pathname in header */
+    if (imhver == 2)
+	pixname = irafgetc (irafheader, IM2_PIXFILE, SZ_IM2PIXFILE);
+    else
+	pixname = irafgetc2 (irafheader, IM_PIXFILE, SZ_IMPIXFILE);
+
+    if (strncmp(pixname, "HDR", 3) == 0 ) {
+	newpixname = same_path (pixname, hdrname);
+        if (newpixname) {
+          free (pixname);
+          pixname = newpixname;
+	  }
+	}
+
+    if (strchr (pixname, '/') == NULL && strchr (pixname, '$') == NULL) {
+	newpixname = same_path (pixname, hdrname);
+        if (newpixname) {
+          free (pixname);
+          pixname = newpixname;
+	  }
+	}
+	
+    if ((bang = strchr (pixname, '!')) != NULL )
+	strcpy(pixfilename,bang+1);
+    else
+	strcpy(pixfilename,pixname);
+
+    free (pixname);
+
+    return (*status);
+}
+
+/*--------------------------------------------------------------------------*/
+/* Put filename and header path together */
+
+static char *same_path (
+
+char	*pixname,	/* IRAF pixel file pathname */
+char	*hdrname)	/* IRAF image header file pathname */
+
+{
+    int len;
+    char *newpixname;
+
+/*  WDP - 10/16/2007 - increased allocation to avoid possible overflow */
+/*    newpixname = (char *) calloc (SZ_IM2PIXFILE, sizeof (char)); */
+
+    newpixname = (char *) calloc (2*SZ_IM2PIXFILE+1, sizeof (char));
+    if (newpixname == NULL) {
+            ffpmsg("iraffits same_path: Cannot alloc memory for newpixname");
+	    return (NULL);
+	}
+
+    /* Pixel file is in same directory as header */
+    if (strncmp(pixname, "HDR$", 4) == 0 ) {
+	(void)strncpy (newpixname, hdrname, SZ_IM2PIXFILE);
+
+	/* find the end of the pathname */
+	len = strlen (newpixname);
+#ifndef VMS
+	while( (len > 0) && (newpixname[len-1] != '/') )
+#else
+	while( (len > 0) && (newpixname[len-1] != ']') && (newpixname[len-1] != ':') )
+#endif
+	    len--;
+
+	/* add name */
+	newpixname[len] = '\0';
+	(void)strncat (newpixname, &pixname[4], SZ_IM2PIXFILE);
+	}
+
+    /* Bare pixel file with no path is assumed to be same as HDR$filename */
+    else if (strchr (pixname, '/') == NULL && strchr (pixname, '$') == NULL) {
+	(void)strncpy (newpixname, hdrname, SZ_IM2PIXFILE);
+
+	/* find the end of the pathname */
+	len = strlen (newpixname);
+#ifndef VMS
+	while( (len > 0) && (newpixname[len-1] != '/') )
+#else
+	while( (len > 0) && (newpixname[len-1] != ']') && (newpixname[len-1] != ':') )
+#endif
+	    len--;
+
+	/* add name */
+	newpixname[len] = '\0';
+	(void)strncat (newpixname, pixname, SZ_IM2PIXFILE);
+	}
+
+    /* Pixel file has same name as header file, but with .pix extension */
+    else if (strncmp (pixname, "HDR", 3) == 0) {
+
+	/* load entire header name string into name buffer */
+	(void)strncpy (newpixname, hdrname, SZ_IM2PIXFILE);
+	len = strlen (newpixname);
+	newpixname[len-3] = 'p';
+	newpixname[len-2] = 'i';
+	newpixname[len-1] = 'x';
+	}
+
+    return (newpixname);
+}
+
+/*--------------------------------------------------------------------------*/
+static int isirafswapped (
+
+char	*irafheader,	/* IRAF image header */
+int	offset)		/* Number of bytes to skip before number */
+
+    /*  check if the IRAF file is in big endian (sun) format (= 0) or not */
+    /*  This is done by checking the 4 byte integer in the header that */
+    /*  represents the iraf pixel type.  This 4-byte word is guaranteed to */
+    /*  have the least sig byte != 0 and the most sig byte = 0,  so if the */
+    /*  first byte of the word != 0, then the file in little endian format */
+    /*  like on an Alpha machine.                                          */
+
+{
+    int  swapped;
+
+    if (irafheader[offset] != 0)
+	swapped = 1;
+    else
+	swapped = 0;
+
+    return (swapped);
+}
+/*--------------------------------------------------------------------------*/
+static int irafgeti4 (
+
+char	*irafheader,	/* IRAF image header */
+int	offset)		/* Number of bytes to skip before number */
+
+{
+    char *ctemp, *cheader;
+    int  temp;
+
+    cheader = irafheader;
+    ctemp = (char *) &temp;
+
+    if (machswap() != swaphead) {
+	ctemp[3] = cheader[offset];
+	ctemp[2] = cheader[offset+1];
+	ctemp[1] = cheader[offset+2];
+	ctemp[0] = cheader[offset+3];
+	}
+    else {
+	ctemp[0] = cheader[offset];
+	ctemp[1] = cheader[offset+1];
+	ctemp[2] = cheader[offset+2];
+	ctemp[3] = cheader[offset+3];
+	}
+    return (temp);
+}
+
+/*--------------------------------------------------------------------------*/
+/* IRAFGETC2 -- Get character string from arbitrary part of v.1 IRAF header */
+
+static char *irafgetc2 (
+
+char	*irafheader,	/* IRAF image header */
+int	offset,		/* Number of bytes to skip before string */
+int	nc)		/* Maximum number of characters in string */
+
+{
+    char *irafstring, *string;
+
+    irafstring = irafgetc (irafheader, offset, 2*(nc+1));
+    string = iraf2str (irafstring, nc);
+    free (irafstring);
+
+    return (string);
+}
+
+/*--------------------------------------------------------------------------*/
+/* IRAFGETC -- Get character string from arbitrary part of IRAF header */
+
+static char *irafgetc (
+
+char	*irafheader,	/* IRAF image header */
+int	offset,		/* Number of bytes to skip before string */
+int	nc)		/* Maximum number of characters in string */
+
+{
+    char *ctemp, *cheader;
+    int i;
+
+    cheader = irafheader;
+    ctemp = (char *) calloc (nc+1, 1);
+    if (ctemp == NULL) {
+	ffpmsg("IRAFGETC Cannot allocate memory for string variable");
+	return (NULL);
+	}
+    for (i = 0; i < nc; i++) {
+	ctemp[i] = cheader[offset+i];
+	if (ctemp[i] > 0 && ctemp[i] < 32)
+	    ctemp[i] = ' ';
+	}
+
+    return (ctemp);
+}
+
+/*--------------------------------------------------------------------------*/
+/* Convert IRAF 2-byte/char string to 1-byte/char string */
+
+static char *iraf2str (
+
+char	*irafstring,	/* IRAF 2-byte/character string */
+int	nchar)		/* Number of characters in string */
+{
+    char *string;
+    int i, j;
+
+    string = (char *) calloc (nchar+1, 1);
+    if (string == NULL) {
+	ffpmsg("IRAF2STR Cannot allocate memory for string variable");
+	return (NULL);
+	}
+
+    /* the chars are in bytes 1, 3, 5, ... if bigendian format (SUN) */
+    /* else in bytes 0, 2, 4, ... if little endian format (Alpha)    */
+
+    if (irafstring[0] != 0)
+	j = 0;
+    else
+	j = 1;
+
+    /* Convert appropriate byte of input to output character */
+    for (i = 0; i < nchar; i++) {
+	string[i] = irafstring[j];
+	j = j + 2;
+	}
+
+    return (string);
+}
+
+/*--------------------------------------------------------------------------*/
+/* IRAFSWAP -- Reverse bytes of any type of vector in place */
+
+static void irafswap (
+
+int	bitpix,		/* Number of bits per pixel */
+			/*  16 = short, -16 = unsigned short, 32 = int */
+			/* -32 = float, -64 = double */
+char	*string,	/* Address of starting point of bytes to swap */
+int	nbytes)		/* Number of bytes to swap */
+
+{
+    switch (bitpix) {
+
+	case 16:
+	    if (nbytes < 2) return;
+	    irafswap2 (string,nbytes);
+	    break;
+
+	case 32:
+	    if (nbytes < 4) return;
+	    irafswap4 (string,nbytes);
+	    break;
+
+	case -16:
+	    if (nbytes < 2) return;
+	    irafswap2 (string,nbytes);
+	    break;
+
+	case -32:
+	    if (nbytes < 4) return;
+	    irafswap4 (string,nbytes);
+	    break;
+
+	case -64:
+	    if (nbytes < 8) return;
+	    irafswap8 (string,nbytes);
+	    break;
+
+	}
+    return;
+}
+
+/*--------------------------------------------------------------------------*/
+/* IRAFSWAP2 -- Swap bytes in string in place */
+
+static void irafswap2 (
+
+char *string,	/* Address of starting point of bytes to swap */
+int nbytes)	/* Number of bytes to swap */
+
+{
+    char *sbyte, temp, *slast;
+
+    slast = string + nbytes;
+    sbyte = string;
+    while (sbyte < slast) {
+	temp = sbyte[0];
+	sbyte[0] = sbyte[1];
+	sbyte[1] = temp;
+	sbyte= sbyte + 2;
+	}
+    return;
+}
+
+/*--------------------------------------------------------------------------*/
+/* IRAFSWAP4 -- Reverse bytes of Integer*4 or Real*4 vector in place */
+
+static void irafswap4 (
+
+char *string,	/* Address of Integer*4 or Real*4 vector */
+int nbytes)	/* Number of bytes to reverse */
+
+{
+    char *sbyte, *slast;
+    char temp0, temp1, temp2, temp3;
+
+    slast = string + nbytes;
+    sbyte = string;
+    while (sbyte < slast) {
+	temp3 = sbyte[0];
+	temp2 = sbyte[1];
+	temp1 = sbyte[2];
+	temp0 = sbyte[3];
+	sbyte[0] = temp0;
+	sbyte[1] = temp1;
+	sbyte[2] = temp2;
+	sbyte[3] = temp3;
+	sbyte = sbyte + 4;
+	}
+
+    return;
+}
+
+/*--------------------------------------------------------------------------*/
+/* IRAFSWAP8 -- Reverse bytes of Real*8 vector in place */
+
+static void irafswap8 (
+
+char *string,	/* Address of Real*8 vector */
+int nbytes)	/* Number of bytes to reverse */
+
+{
+    char *sbyte, *slast;
+    char temp[8];
+
+    slast = string + nbytes;
+    sbyte = string;
+    while (sbyte < slast) {
+	temp[7] = sbyte[0];
+	temp[6] = sbyte[1];
+	temp[5] = sbyte[2];
+	temp[4] = sbyte[3];
+	temp[3] = sbyte[4];
+	temp[2] = sbyte[5];
+	temp[1] = sbyte[6];
+	temp[0] = sbyte[7];
+	sbyte[0] = temp[0];
+	sbyte[1] = temp[1];
+	sbyte[2] = temp[2];
+	sbyte[3] = temp[3];
+	sbyte[4] = temp[4];
+	sbyte[5] = temp[5];
+	sbyte[6] = temp[6];
+	sbyte[7] = temp[7];
+	sbyte = sbyte + 8;
+	}
+    return;
+}
+
+/*--------------------------------------------------------------------------*/
+static int
+machswap (void)
+
+{
+    char *ctest;
+    int itest;
+
+    itest = 1;
+    ctest = (char *)&itest;
+    if (*ctest)
+	return (1);
+    else
+	return (0);
+}
+
+/*--------------------------------------------------------------------------*/
+/*             the following routines were originally in hget.c             */
+/*--------------------------------------------------------------------------*/
+
+
+static int lhead0 = 0;
+
+/*--------------------------------------------------------------------------*/
+
+/* Extract long value for variable from FITS header string */
+
+static int
+hgeti4 (hstring,keyword,ival)
+
+char *hstring;	/* character string containing FITS header information
+		   in the format <keyword>= <value> {/ <comment>} */
+char *keyword;	/* character string containing the name of the keyword
+		   the value of which is returned.  hget searches for a
+		   line beginning with this string.  if "[n]" is present,
+		   the n'th token in the value is returned.
+		   (the first 8 characters must be unique) */
+int *ival;
+{
+char *value;
+double dval;
+int minint;
+char val[30]; 
+
+/* Get value and comment from header string */
+	value = hgetc (hstring,keyword);
+
+/* Translate value from ASCII to binary */
+	if (value != NULL) {
+	    minint = -MAXINT - 1;
+	    strcpy (val, value);
+	    dval = atof (val);
+	    if (dval+0.001 > MAXINT)
+		*ival = MAXINT;
+	    else if (dval >= 0)
+		*ival = (int) (dval + 0.001);
+	    else if (dval-0.001 < minint)
+		*ival = minint;
+	    else
+		*ival = (int) (dval - 0.001);
+	    return (1);
+	    }
+	else {
+	    return (0);
+	    }
+}
+
+/*-------------------------------------------------------------------*/
+/* Extract string value for variable from FITS header string */
+
+static int
+hgets (hstring, keyword, lstr, str)
+
+char *hstring;	/* character string containing FITS header information
+		   in the format <keyword>= <value> {/ <comment>} */
+char *keyword;	/* character string containing the name of the keyword
+		   the value of which is returned.  hget searches for a
+		   line beginning with this string.  if "[n]" is present,
+		   the n'th token in the value is returned.
+		   (the first 8 characters must be unique) */
+int lstr;	/* Size of str in characters */
+char *str;	/* String (returned) */
+{
+	char *value;
+	int lval;
+
+/* Get value and comment from header string */
+	value = hgetc (hstring,keyword);
+
+	if (value != NULL) {
+	    lval = strlen (value);
+	    if (lval < lstr)
+		strcpy (str, value);
+	    else if (lstr > 1)
+		strncpy (str, value, lstr-1);
+	    else
+		str[0] = value[0];
+	    return (1);
+	    }
+	else
+	    return (0);
+}
+
+/*-------------------------------------------------------------------*/
+/* Extract character value for variable from FITS header string */
+
+static char *
+hgetc (hstring,keyword0)
+
+char *hstring;	/* character string containing FITS header information
+		   in the format <keyword>= <value> {/ <comment>} */
+char *keyword0;	/* character string containing the name of the keyword
+		   the value of which is returned.  hget searches for a
+		   line beginning with this string.  if "[n]" is present,
+		   the n'th token in the value is returned.
+		   (the first 8 characters must be unique) */
+{
+	static char cval[80];
+	char *value;
+	char cwhite[2];
+	char squot[2], dquot[2], lbracket[2], rbracket[2], slash[2], comma[2];
+	char keyword[81]; /* large for ESO hierarchical keywords */
+	char line[100];
+	char *vpos, *cpar = NULL;
+	char *q1, *q2 = NULL, *v1, *v2, *c1, *brack1, *brack2;
+	int ipar, i;
+
+	squot[0] = 39;
+	squot[1] = 0;
+	dquot[0] = 34;
+	dquot[1] = 0;
+	lbracket[0] = 91;
+	lbracket[1] = 0;
+	comma[0] = 44;
+	comma[1] = 0;
+	rbracket[0] = 93;
+	rbracket[1] = 0;
+	slash[0] = 47;
+	slash[1] = 0;
+
+/* Find length of variable name */
+	strncpy (keyword,keyword0, sizeof(keyword)-1);
+	brack1 = strsrch (keyword,lbracket);
+	if (brack1 == NULL)
+	    brack1 = strsrch (keyword,comma);
+	if (brack1 != NULL) {
+	    *brack1 = '\0';
+	    brack1++;
+	    }
+
+/* Search header string for variable name */
+	vpos = ksearch (hstring,keyword);
+
+/* Exit if not found */
+	if (vpos == NULL) {
+	    return (NULL);
+	    }
+
+/* Initialize line to nulls */
+	 for (i = 0; i < 100; i++)
+	    line[i] = 0;
+
+/* In standard FITS, data lasts until 80th character */
+
+/* Extract entry for this variable from the header */
+	strncpy (line,vpos,80);
+
+/* check for quoted value */
+	q1 = strsrch (line,squot);
+	c1 = strsrch (line,slash);
+	if (q1 != NULL) {
+	    if (c1 != NULL && q1 < c1)
+		q2 = strsrch (q1+1,squot);
+	    else if (c1 == NULL)
+		q2 = strsrch (q1+1,squot);
+	    else
+		q1 = NULL;
+	    }
+	else {
+	    q1 = strsrch (line,dquot);
+	    if (q1 != NULL) {
+		if (c1 != NULL && q1 < c1)
+		    q2 = strsrch (q1+1,dquot);
+		else if (c1 == NULL)
+		    q2 = strsrch (q1+1,dquot);
+		else
+		    q1 = NULL;
+		}
+	    else {
+		q1 = NULL;
+		q2 = line + 10;
+		}
+	    }
+
+/* Extract value and remove excess spaces */
+	if (q1 != NULL) {
+	    v1 = q1 + 1;
+	    v2 = q2;
+	    c1 = strsrch (q2,"/");
+	    }
+	else {
+	    v1 = strsrch (line,"=") + 1;
+	    c1 = strsrch (line,"/");
+	    if (c1 != NULL)
+		v2 = c1;
+	    else
+		v2 = line + 79;
+	    }
+
+/* Ignore leading spaces */
+	while (*v1 == ' ' && v1 < v2) {
+	    v1++;
+	    }
+
+/* Drop trailing spaces */
+	*v2 = '\0';
+	v2--;
+	while (*v2 == ' ' && v2 > v1) {
+	    *v2 = '\0';
+	    v2--;
+	    }
+
+	if (!strcmp (v1, "-0"))
+	    v1++;
+	strcpy (cval,v1);
+	value = cval;
+
+/* If keyword has brackets, extract appropriate token from value */
+	if (brack1 != NULL) {
+	    brack2 = strsrch (brack1,rbracket);
+	    if (brack2 != NULL)
+		*brack2 = '\0';
+	    ipar = atoi (brack1);
+	    if (ipar > 0) {
+		cwhite[0] = ' ';
+		cwhite[1] = '\0';
+		for (i = 1; i <= ipar; i++) {
+		    cpar = strtok (v1,cwhite);
+		    v1 = NULL;
+		    }
+		if (cpar != NULL) {
+		    strcpy (cval,cpar);
+		    }
+		else
+		    value = NULL;
+		}
+	    }
+
+	return (value);
+}
+
+
+/*-------------------------------------------------------------------*/
+/* Find beginning of fillable blank line before FITS header keyword line */
+
+static char *
+blsearch (hstring,keyword)
+
+/* Find entry for keyword keyword in FITS header string hstring.
+   (the keyword may have a maximum of eight letters)
+   NULL is returned if the keyword is not found */
+
+char *hstring;	/* character string containing fits-style header
+		information in the format <keyword>= <value> {/ <comment>}
+		the default is that each entry is 80 characters long;
+		however, lines may be of arbitrary length terminated by
+		nulls, carriage returns or linefeeds, if packed is true.  */
+char *keyword;	/* character string containing the name of the variable
+		to be returned.  ksearch searches for a line beginning
+		with this string.  The string may be a character
+		literal or a character variable terminated by a null
+		or '$'.  it is truncated to 8 characters. */
+{
+    char *loc, *headnext, *headlast, *pval, *lc, *line;
+    char *bval;
+    int icol, nextchar, lkey, nleft, lhstr;
+
+    pval = 0;
+
+    /* Search header string for variable name */
+    if (lhead0)
+	lhstr = lhead0;
+    else {
+	lhstr = 0;
+	while (lhstr < 57600 && hstring[lhstr] != 0)
+	    lhstr++;
+	}
+    headlast = hstring + lhstr;
+    headnext = hstring;
+    pval = NULL;
+    while (headnext < headlast) {
+	nleft = headlast - headnext;
+	loc = strnsrch (headnext, keyword, nleft);
+
+	/* Exit if keyword is not found */
+	if (loc == NULL) {
+	    break;
+	    }
+
+	icol = (loc - hstring) % 80;
+	lkey = strlen (keyword);
+	nextchar = (int) *(loc + lkey);
+
+	/* If this is not in the first 8 characters of a line, keep searching */
+	if (icol > 7)
+	    headnext = loc + 1;
+
+	/* If parameter name in header is longer, keep searching */
+	else if (nextchar != 61 && nextchar > 32 && nextchar < 127)
+	    headnext = loc + 1;
+
+	/* If preceeding characters in line are not blanks, keep searching */
+	else {
+	    line = loc - icol;
+	    for (lc = line; lc < loc; lc++) {
+		if (*lc != ' ')
+		    headnext = loc + 1;
+		}
+
+	/* Return pointer to start of line if match */
+	    if (loc >= headnext) {
+		pval = line;
+		break;
+		}
+	    }
+	}
+
+    /* Return NULL if keyword is found at start of FITS header string */
+    if (pval == NULL)
+	return (pval);
+
+    /* Return NULL if  found the first keyword in the header */
+    if (pval == hstring)
+        return (NULL);
+
+    /* Find last nonblank line before requested keyword */
+    bval = pval - 80;
+    while (!strncmp (bval,"        ",8))
+	bval = bval - 80;
+    bval = bval + 80;
+
+    /* Return pointer to calling program if blank lines found */
+    if (bval < pval)
+	return (bval);
+    else
+	return (NULL);
+}
+
+
+/*-------------------------------------------------------------------*/
+/* Find FITS header line containing specified keyword */
+
+static char *ksearch (hstring,keyword)
+
+/* Find entry for keyword keyword in FITS header string hstring.
+   (the keyword may have a maximum of eight letters)
+   NULL is returned if the keyword is not found */
+
+char *hstring;	/* character string containing fits-style header
+		information in the format <keyword>= <value> {/ <comment>}
+		the default is that each entry is 80 characters long;
+		however, lines may be of arbitrary length terminated by
+		nulls, carriage returns or linefeeds, if packed is true.  */
+char *keyword;	/* character string containing the name of the variable
+		to be returned.  ksearch searches for a line beginning
+		with this string.  The string may be a character
+		literal or a character variable terminated by a null
+		or '$'.  it is truncated to 8 characters. */
+{
+    char *loc, *headnext, *headlast, *pval, *lc, *line;
+    int icol, nextchar, lkey, nleft, lhstr;
+
+    pval = 0;
+
+/* Search header string for variable name */
+    if (lhead0)
+	lhstr = lhead0;
+    else {
+	lhstr = 0;
+	while (lhstr < 57600 && hstring[lhstr] != 0)
+	    lhstr++;
+	}
+    headlast = hstring + lhstr;
+    headnext = hstring;
+    pval = NULL;
+    while (headnext < headlast) {
+	nleft = headlast - headnext;
+	loc = strnsrch (headnext, keyword, nleft);
+
+	/* Exit if keyword is not found */
+	if (loc == NULL) {
+	    break;
+	    }
+
+	icol = (loc - hstring) % 80;
+	lkey = strlen (keyword);
+	nextchar = (int) *(loc + lkey);
+
+	/* If this is not in the first 8 characters of a line, keep searching */
+	if (icol > 7)
+	    headnext = loc + 1;
+
+	/* If parameter name in header is longer, keep searching */
+	else if (nextchar != 61 && nextchar > 32 && nextchar < 127)
+	    headnext = loc + 1;
+
+	/* If preceeding characters in line are not blanks, keep searching */
+	else {
+	    line = loc - icol;
+	    for (lc = line; lc < loc; lc++) {
+		if (*lc != ' ')
+		    headnext = loc + 1;
+		}
+
+	/* Return pointer to start of line if match */
+	    if (loc >= headnext) {
+		pval = line;
+		break;
+		}
+	    }
+	}
+
+/* Return pointer to calling program */
+	return (pval);
+
+}
+
+/*-------------------------------------------------------------------*/
+/* Find string s2 within null-terminated string s1 */
+
+static char *
+strsrch (s1, s2)
+
+char *s1;	/* String to search */
+char *s2;	/* String to look for */
+
+{
+    int ls1;
+    ls1 = strlen (s1);
+    return (strnsrch (s1, s2, ls1));
+}
+
+/*-------------------------------------------------------------------*/
+/* Find string s2 within string s1 */
+
+static char *
+strnsrch (s1, s2, ls1)
+
+char	*s1;	/* String to search */
+char	*s2;	/* String to look for */
+int	ls1;	/* Length of string being searched */
+
+{
+    char *s,*s1e;
+    char cfirst,clast;
+    int i,ls2;
+
+    /* Return null string if either pointer is NULL */
+    if (s1 == NULL || s2 == NULL)
+	return (NULL);
+
+    /* A zero-length pattern is found in any string */
+    ls2 = strlen (s2);
+    if (ls2 ==0)
+	return (s1);
+
+    /* Only a zero-length string can be found in a zero-length string */
+    if (ls1 ==0)
+	return (NULL);
+
+    cfirst = s2[0];
+    clast = s2[ls2-1];
+    s1e = s1 + ls1 - ls2 + 1;
+    s = s1;
+    while (s < s1e) { 
+
+	/* Search for first character in pattern string */
+	if (*s == cfirst) {
+
+	    /* If single character search, return */
+	    if (ls2 == 1)
+		return (s);
+
+	    /* Search for last character in pattern string if first found */
+	    if (s[ls2-1] == clast) {
+
+		/* If two-character search, return */
+		if (ls2 == 2)
+		    return (s);
+
+		/* If 3 or more characters, check for rest of search string */
+		i = 1;
+		while (i < ls2 && s[i] == s2[i])
+		    i++;
+
+		/* If entire string matches, return */
+		if (i >= ls2)
+		    return (s);
+		}
+	    }
+	s++;
+	}
+    return (NULL);
+}
+
+/*-------------------------------------------------------------------*/
+/*             the following routines were originally in hget.c      */
+/*-------------------------------------------------------------------*/
+/*  HPUTI4 - Set int keyword = ival in FITS header string */
+
+static void
+hputi4 (hstring,keyword,ival)
+
+  char *hstring;	/* character string containing FITS-style header
+			   information in the format
+			   <keyword>= <value> {/ <comment>}
+			   each entry is padded with spaces to 80 characters */
+
+  char *keyword;		/* character string containing the name of the variable
+			   to be returned.  hput searches for a line beginning
+			   with this string, and if there isn't one, creates one.
+		   	   The first 8 characters of keyword must be unique. */
+  int ival;		/* int number */
+{
+    char value[30];
+
+    /* Translate value from binary to ASCII */
+    sprintf (value,"%d",ival);
+
+    /* Put value into header string */
+    hputc (hstring,keyword,value);
+
+    /* Return to calling program */
+    return;
+}
+
+/*-------------------------------------------------------------------*/
+
+/*  HPUTL - Set keyword = F if lval=0, else T, in FITS header string */
+
+static void
+hputl (hstring, keyword,lval)
+
+char *hstring;		/* FITS header */
+char *keyword;		/* Keyword name */
+int lval;		/* logical variable (0=false, else true) */
+{
+    char value[8];
+
+    /* Translate value from binary to ASCII */
+    if (lval)
+	strcpy (value, "T");
+    else
+	strcpy (value, "F");
+
+    /* Put value into header string */
+    hputc (hstring,keyword,value);
+
+    /* Return to calling program */
+    return;
+}
+
+/*-------------------------------------------------------------------*/
+
+/*  HPUTS - Set character string keyword = 'cval' in FITS header string */
+
+static void
+hputs (hstring,keyword,cval)
+
+char *hstring;	/* FITS header */
+char *keyword;	/* Keyword name */
+char *cval;	/* character string containing the value for variable
+		   keyword.  trailing and leading blanks are removed.  */
+{
+    char squot = 39;
+    char value[70];
+    int lcval;
+
+    /*  find length of variable string */
+
+    lcval = strlen (cval);
+    if (lcval > 67)
+	lcval = 67;
+
+    /* Put quotes around string */
+    value[0] = squot;
+    strncpy (&value[1],cval,lcval);
+    value[lcval+1] = squot;
+    value[lcval+2] = 0;
+
+    /* Put value into header string */
+    hputc (hstring,keyword,value);
+
+    /* Return to calling program */
+    return;
+}
+
+/*---------------------------------------------------------------------*/
+/*  HPUTC - Set character string keyword = value in FITS header string */
+
+static void
+hputc (hstring,keyword,value)
+
+char *hstring;
+char *keyword;
+char *value;	/* character string containing the value for variable
+		   keyword.  trailing and leading blanks are removed.  */
+{
+    char squot = 39;
+    char line[100];
+    char newcom[50];
+    char blank[80];
+    char *v, *vp, *v1, *v2, *q1, *q2, *c1, *ve;
+    int lkeyword, lcom, lval, lc, i;
+
+    for (i = 0; i < 80; i++)
+	blank[i] = ' ';
+
+    /*  find length of keyword and value */
+    lkeyword = strlen (keyword);
+    lval = strlen (value);
+
+    /*  If COMMENT or HISTORY, always add it just before the END */
+    if (lkeyword == 7 && (strncmp (keyword,"COMMENT",7) == 0 ||
+	strncmp (keyword,"HISTORY",7) == 0)) {
+
+	/* Find end of header */
+	v1 = ksearch (hstring,"END");
+	v2 = v1 + 80;
+
+	/* Move END down one line */
+	strncpy (v2, v1, 80);
+
+	/* Insert keyword */
+	strncpy (v1,keyword,7);
+
+	/* Pad with spaces */
+	for (vp = v1+lkeyword; vp < v2; vp++)
+	    *vp = ' ';
+
+	/* Insert comment */
+	strncpy (v1+9,value,lval);
+	return;
+	}
+
+    /* Otherwise search for keyword */
+    else
+	v1 = ksearch (hstring,keyword);
+
+    /*  If parameter is not found, find a place to put it */
+    if (v1 == NULL) {
+	
+	/* First look for blank lines before END */
+        v1 = blsearch (hstring, "END");
+    
+	/*  Otherwise, create a space for it at the end of the header */
+	if (v1 == NULL) {
+	    ve = ksearch (hstring,"END");
+	    v1 = ve;
+	    v2 = v1 + 80;
+	    strncpy (v2, ve, 80);
+	    }
+	else
+	    v2 = v1 + 80;
+	lcom = 0;
+	newcom[0] = 0;
+	}
+
+    /*  Otherwise, extract the entry for this keyword from the header */
+    else {
+	strncpy (line, v1, 80);
+	line[80] = 0;
+	v2 = v1 + 80;
+
+	/*  check for quoted value */
+	q1 = strchr (line, squot);
+	if (q1 != NULL)
+	    q2 = strchr (q1+1,squot);
+	else
+	    q2 = line;
+
+	/*  extract comment and remove trailing spaces */
+
+	c1 = strchr (q2,'/');
+	if (c1 != NULL) {
+	    lcom = 80 - (c1 - line);
+	    strncpy (newcom, c1+1, lcom);
+	    vp = newcom + lcom - 1;
+	    while (vp-- > newcom && *vp == ' ')
+		*vp = 0;
+	    lcom = strlen (newcom);
+	    }
+	else {
+	    newcom[0] = 0;
+	    lcom = 0;
+	    }
+	}
+
+    /* Fill new entry with spaces */
+    for (vp = v1; vp < v2; vp++)
+	*vp = ' ';
+
+    /*  Copy keyword to new entry */
+    strncpy (v1, keyword, lkeyword);
+
+    /*  Add parameter value in the appropriate place */
+    vp = v1 + 8;
+    *vp = '=';
+    vp = v1 + 9;
+    *vp = ' ';
+    vp = vp + 1;
+    if (*value == squot) {
+	strncpy (vp, value, lval);
+	if (lval+12 > 31)
+	    lc = lval + 12;
+	else
+	    lc = 30;
+	}
+    else {
+	vp = v1 + 30 - lval;
+	strncpy (vp, value, lval);
+	lc = 30;
+	}
+
+    /* Add comment in the appropriate place */
+	if (lcom > 0) {
+	    if (lc+2+lcom > 80)
+		lcom = 78 - lc;
+	    vp = v1 + lc + 2;     /* Jul 16 1997: was vp = v1 + lc * 2 */
+	    *vp = '/';
+	    vp = vp + 1;
+	    strncpy (vp, newcom, lcom);
+	    for (v = vp + lcom; v < v2; v++)
+		*v = ' ';
+	    }
+
+	return;
+}
+
+/*-------------------------------------------------------------------*/
+/*  HPUTCOM - Set comment for keyword or on line in FITS header string */
+
+static void
+hputcom (hstring,keyword,comment)
+
+  char *hstring;
+  char *keyword;
+  char *comment;
+{
+	char squot;
+	char line[100];
+	int lkeyword, lcom;
+	char *vp, *v1, *v2, *c0 = NULL, *c1, *q1, *q2;
+
+	squot = 39;
+
+/*  Find length of variable name */
+	lkeyword = strlen (keyword);
+
+/*  If COMMENT or HISTORY, always add it just before the END */
+	if (lkeyword == 7 && (strncmp (keyword,"COMMENT",7) == 0 ||
+	    strncmp (keyword,"HISTORY",7) == 0)) {
+
+	/* Find end of header */
+	    v1 = ksearch (hstring,"END");
+	    v2 = v1 + 80;
+	    strncpy (v2, v1, 80);
+
+	/*  blank out new line and insert keyword */
+	    for (vp = v1; vp < v2; vp++)
+		*vp = ' ';
+	    strncpy (v1, keyword, lkeyword);
+	    }
+
+/* search header string for variable name */
+	else {
+	    v1 = ksearch (hstring,keyword);
+	    v2 = v1 + 80;
+
+	/* if parameter is not found, return without doing anything */
+	    if (v1 == NULL) {
+		return;
+		}
+
+	/* otherwise, extract entry for this variable from the header */
+	    strncpy (line, v1, 80);
+
+	/* check for quoted value */
+	    q1 = strchr (line,squot);
+	    if (q1 != NULL)
+		q2 = strchr (q1+1,squot);
+	    else
+		q2 = NULL;
+
+	    if (q2 == NULL || q2-line < 31)
+		c0 = v1 + 31;
+	    else
+		c0 = v1 + (q2-line) + 2; /* allan: 1997-09-30, was c0=q2+2 */
+
+	    strncpy (c0, "/ ",2);
+	    }
+
+/* create new entry */
+	lcom = strlen (comment);
+
+	if (lcom > 0) {
+	    c1 = c0 + 2;
+	    if (c1+lcom > v2)
+		lcom = v2 - c1;
+	    strncpy (c1, comment, lcom);
+	    }
+
+}
diff --git a/src/plugins/cfitsio/iter_a.c b/src/plugins/cfitsio/iter_a.c
new file mode 100644
index 0000000..19ea1d1
--- /dev/null
+++ b/src/plugins/cfitsio/iter_a.c
@@ -0,0 +1,147 @@
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "fitsio.h"
+
+/*
+  This program illustrates how to use the CFITSIO iterator function.
+  It reads and modifies the input 'iter_a.fit' file by computing a
+  value for the 'rate' column as a function of the values in the other
+  'counts' and 'time' columns.
+*/
+main()
+{
+    extern flux_rate(); /* external work function is passed to the iterator */
+    fitsfile *fptr;
+    iteratorCol cols[3];  /* structure used by the iterator function */
+    int n_cols;
+    long rows_per_loop, offset;
+
+    int status, nkeys, keypos, hdutype, ii, jj;
+    char filename[]  = "iter_a.fit";     /* name of rate FITS file */
+
+    status = 0; 
+
+    fits_open_file(&fptr, filename, READWRITE, &status); /* open file */
+
+    /* move to the desired binary table extension */
+    if (fits_movnam_hdu(fptr, BINARY_TBL, "RATE", 0, &status) )
+        fits_report_error(stderr, status);    /* print out error messages */
+
+    n_cols  = 3;   /* number of columns */
+
+    /* define input column structure members for the iterator function */
+    fits_iter_set_by_name(&cols[0], fptr, "COUNTS", TLONG,  InputCol);
+    fits_iter_set_by_name(&cols[1], fptr, "TIME",   TFLOAT, InputCol);
+    fits_iter_set_by_name(&cols[2], fptr, "RATE",   TFLOAT, OutputCol);
+
+    rows_per_loop = 0;  /* use default optimum number of rows */
+    offset = 0;         /* process all the rows */
+
+    /* apply the rate function to each row of the table */
+    printf("Calling iterator function...%d\n", status);
+
+    fits_iterate_data(n_cols, cols, offset, rows_per_loop,
+                      flux_rate, 0L, &status);
+
+    fits_close_file(fptr, &status);      /* all done */
+
+    if (status)
+        fits_report_error(stderr, status);  /* print out error messages */
+
+    return(status);
+}
+/*--------------------------------------------------------------------------*/
+int flux_rate(long totalrows, long offset, long firstrow, long nrows,
+             int ncols, iteratorCol *cols, void *user_strct ) 
+
+/*
+   Sample iterator function that calculates the output flux 'rate' column
+   by dividing the input 'counts' by the 'time' column.
+   It also applies a constant deadtime correction factor if the 'deadtime'
+   keyword exists.  Finally, this creates or updates the 'LIVETIME'
+   keyword with the sum of all the individual integration times.
+*/
+{
+    int ii, status = 0;
+
+    /* declare variables static to preserve their values between calls */
+    static long *counts;
+    static float *interval;
+    static float *rate;
+    static float deadtime, livetime; /* must preserve values between calls */
+
+    /*--------------------------------------------------------*/
+    /*  Initialization procedures: execute on the first call  */
+    /*--------------------------------------------------------*/
+    if (firstrow == 1)
+    {
+       if (ncols != 3)
+           return(-1);  /* number of columns incorrect */
+
+       if (fits_iter_get_datatype(&cols[0]) != TLONG  ||
+           fits_iter_get_datatype(&cols[1]) != TFLOAT ||
+           fits_iter_get_datatype(&cols[2]) != TFLOAT )
+           return(-2);  /* bad data type */
+
+       /* assign the input pointers to the appropriate arrays and null ptrs*/
+       counts       = (long *)  fits_iter_get_array(&cols[0]);
+       interval     = (float *) fits_iter_get_array(&cols[1]);
+       rate         = (float *) fits_iter_get_array(&cols[2]);
+
+       livetime = 0;  /* initialize the total integration time */
+
+       /* try to get the deadtime keyword value */
+       fits_read_key(cols[0].fptr, TFLOAT, "DEADTIME", &deadtime, '\0',
+                     &status);
+       if (status)
+       {
+           deadtime = 1.0;  /* default deadtime if keyword doesn't exist */
+       }
+       else if (deadtime < 0. || deadtime > 1.0)
+       {
+           return(-1);    /* bad deadtime value */
+       }
+
+       printf("deadtime = %f\n", deadtime);
+    }
+
+    /*--------------------------------------------*/
+    /*  Main loop: process all the rows of data */
+    /*--------------------------------------------*/
+
+    /*  NOTE: 1st element of array is the null pixel value!  */
+    /*  Loop from 1 to nrows, not 0 to nrows - 1.  */
+
+    /* this version tests for null values */
+    rate[0] = DOUBLENULLVALUE;   /* define the value that represents null */
+
+    for (ii = 1; ii <= nrows; ii++)
+    {
+       if (counts[ii] == counts[0])   /*  undefined counts value? */
+       {
+           rate[ii] = DOUBLENULLVALUE;
+       }
+       else if (interval[ii] > 0.)
+       {
+           rate[ii] = counts[ii] / interval[ii] / deadtime;
+           livetime += interval[ii];  /* accumulate total integration time */
+       }
+       else
+           return(-2);  /* bad integration time */
+    }
+
+    /*-------------------------------------------------------*/
+    /*  Clean up procedures:  after processing all the rows  */
+    /*-------------------------------------------------------*/
+
+    if (firstrow + nrows - 1 == totalrows)
+    {
+        /*  update the LIVETIME keyword value */
+
+        fits_update_key(cols[0].fptr, TFLOAT, "LIVETIME", &livetime, 
+                 "total integration time", &status);
+        printf("livetime = %f\n", livetime);
+   }
+    return(0);  /* return successful status */
+}
diff --git a/src/plugins/cfitsio/iter_b.c b/src/plugins/cfitsio/iter_b.c
new file mode 100644
index 0000000..296f4e1
--- /dev/null
+++ b/src/plugins/cfitsio/iter_b.c
@@ -0,0 +1,114 @@
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "fitsio.h"
+
+/*
+  This program illustrates how to use the CFITSIO iterator function.
+  It simply prints out the values in a character string and a logical
+  type column in a table, and toggles the value in the logical column
+  so that T -> F and F -> T.
+*/
+main()
+{
+    extern str_iter(); /* external work function is passed to the iterator */
+    fitsfile *fptr;
+    iteratorCol cols[2];
+    int n_cols;
+    long rows_per_loop, offset;
+    int status = 0;
+    char filename[]  = "iter_b.fit";     /* name of rate FITS file */
+
+    /* open the file and move to the correct extension */
+    fits_open_file(&fptr, filename, READWRITE, &status);
+    fits_movnam_hdu(fptr, BINARY_TBL, "iter_test", 0, &status);
+
+    /* define input column structure members for the iterator function */
+    n_cols  = 2;   /* number of columns */
+
+    /* define input column structure members for the iterator function */
+    fits_iter_set_by_name(&cols[0], fptr, "Avalue", TSTRING,  InputOutputCol);
+    fits_iter_set_by_name(&cols[1], fptr, "Lvalue", TLOGICAL, InputOutputCol);
+
+    rows_per_loop = 0;  /* use default optimum number of rows */
+    offset = 0;         /* process all the rows */
+
+    /* apply the  function to each row of the table */
+    printf("Calling iterator function...%d\n", status);
+
+    fits_iterate_data(n_cols, cols, offset, rows_per_loop,
+                      str_iter, 0L, &status);
+
+    fits_close_file(fptr, &status);      /* all done */
+
+    if (status)
+       fits_report_error(stderr, status); /* print out error messages */
+
+    return(status);
+}
+/*--------------------------------------------------------------------------*/
+int str_iter(long totalrows, long offset, long firstrow, long nrows,
+             int ncols, iteratorCol *cols, void *user_strct )
+
+/*
+   Sample iterator function.
+*/
+{
+    int ii;
+
+    /* declare variables static to preserve their values between calls */
+    static char **stringvals;
+    static char *logicalvals;
+
+    /*--------------------------------------------------------*/
+    /*  Initialization procedures: execute on the first call  */
+    /*--------------------------------------------------------*/
+    if (firstrow == 1)
+    {
+       if (ncols != 2)
+           return(-1);  /* number of columns incorrect */
+
+       if (fits_iter_get_datatype(&cols[0]) != TSTRING ||
+           fits_iter_get_datatype(&cols[1]) != TLOGICAL )
+           return(-2);  /* bad data type */
+
+       /* assign the input pointers to the appropriate arrays */
+       stringvals       = (char **) fits_iter_get_array(&cols[0]);
+       logicalvals      = (char *)  fits_iter_get_array(&cols[1]);
+
+       printf("Total rows, No. rows = %d %d\n",totalrows, nrows);
+    }
+
+    /*------------------------------------------*/
+    /*  Main loop: process all the rows of data */
+    /*------------------------------------------*/
+
+    /*  NOTE: 1st element of array is the null pixel value!  */
+    /*  Loop from 1 to nrows, not 0 to nrows - 1.  */
+   
+    for (ii = 1; ii <= nrows; ii++)
+    {
+      printf("%s %d\n", stringvals[ii], logicalvals[ii]);
+      if (logicalvals[ii])
+      {
+         logicalvals[ii] = FALSE;
+         strcpy(stringvals[ii], "changed to false");
+      }
+      else
+      {
+         logicalvals[ii] = TRUE;
+         strcpy(stringvals[ii], "changed to true");
+      }
+    }
+
+    /*-------------------------------------------------------*/
+    /*  Clean up procedures:  after processing all the rows  */
+    /*-------------------------------------------------------*/
+
+    if (firstrow + nrows - 1 == totalrows)
+    {
+      /* no action required in this case */
+    }
+ 
+    return(0);
+}
diff --git a/src/plugins/cfitsio/iter_c.c b/src/plugins/cfitsio/iter_c.c
new file mode 100644
index 0000000..bbf9774
--- /dev/null
+++ b/src/plugins/cfitsio/iter_c.c
@@ -0,0 +1,171 @@
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "fitsio.h"
+
+/*
+    This example program illustrates how to use the CFITSIO iterator function.
+
+    This program creates a 2D histogram of the X and Y columns of an event
+    list.  The 'main' routine just creates the empty new image, then executes
+    the 'writehisto' work function by calling the CFITSIO iterator function.
+
+    'writehisto' opens the FITS event list that contains the X and Y columns.
+    It then calls a second work function, calchisto, (by recursively calling
+    the CFITSIO iterator function) which actually computes the 2D histogram.
+*/
+
+/*   Globally defined parameters */
+
+long xsize = 480; /* size of the histogram image */
+long ysize = 480;
+long xbinsize = 32;
+long ybinsize = 32;
+
+main()
+{
+    extern writehisto();  /* external work function passed to the iterator */
+    extern long xsize, ysize;  /* size of image */
+
+    fitsfile *fptr;
+    iteratorCol cols[1];
+    int n_cols, status = 0;
+    long n_per_loop, offset, naxes[2];
+    char filename[]  = "histoimg.fit";     /* name of FITS image */
+
+    remove(filename);   /* delete previous version of the file if it exists */
+    fits_create_file(&fptr, filename, &status);  /* create new output image */
+
+    naxes[0] = xsize;
+    naxes[1] = ysize;
+    fits_create_img(fptr, LONG_IMG, 2, naxes, &status); /* create primary HDU */
+
+    n_cols  = 1;   /* number of columns */
+
+    /* define input column structure members for the iterator function */
+    fits_iter_set_by_name(&cols[0], fptr, " ", TLONG, OutputCol);
+
+    n_per_loop = -1;  /* force whole array to be passed at one time */
+    offset = 0;       /* don't skip over any pixels */
+
+    /* execute the function to create and write the 2D histogram */
+    printf("Calling writehisto iterator work function... %d\n", status);
+
+    fits_iterate_data(n_cols, cols, offset, n_per_loop,
+                      writehisto, 0L, &status);
+
+    fits_close_file(fptr, &status);      /* all done; close the file */
+
+    if (status)
+        fits_report_error(stderr, status);  /* print out error messages */
+    else
+        printf("Program completed successfully.\n");
+
+    return(status);
+}
+/*--------------------------------------------------------------------------*/
+int writehisto(long totaln, long offset, long firstn, long nvalues,
+             int narrays, iteratorCol *histo, void *userPointer)
+/*
+   Iterator work function that writes out the 2D histogram.
+   The histogram values are calculated by another work function, calchisto.
+
+   This routine is executed only once since nvalues was forced to = totaln.
+*/
+{
+    extern calchisto();  /* external function called by the iterator */
+    long *histogram;
+    fitsfile *tblptr;
+    iteratorCol cols[2];
+    int n_cols, status = 0;
+    long rows_per_loop, rowoffset;
+    char filename[]  = "iter_c.fit";     /* name of FITS table */
+
+    /* do sanity checking of input values */
+    if (totaln != nvalues)
+        return(-1);  /* whole image must be passed at one time */
+
+    if (narrays != 1)
+        return(-2);  /* number of images is incorrect */
+
+    if (fits_iter_get_datatype(&histo[0]) != TLONG)
+        return(-3);  /* input array has wrong data type */
+
+    /* assign the FITS array pointer to the global histogram pointer */
+    histogram = (long *) fits_iter_get_array(&histo[0]);
+
+    /* open the file and move to the table containing the X and Y columns */
+    fits_open_file(&tblptr, filename, READONLY, &status);
+    fits_movnam_hdu(tblptr, BINARY_TBL, "EVENTS", 0, &status);
+    if (status)
+       return(status);
+   
+    n_cols = 2; /* number of columns */
+
+    /* define input column structure members for the iterator function */
+    fits_iter_set_by_name(&cols[0], tblptr, "X", TLONG,  InputCol);
+    fits_iter_set_by_name(&cols[1], tblptr, "Y", TLONG, InputCol);
+
+    rows_per_loop = 0;  /* take default number of rows per interation */
+    rowoffset = 0;     
+
+    /* calculate the histogram */
+    printf("Calling calchisto iterator work function... %d\n", status);
+
+    fits_iterate_data(n_cols, cols, rowoffset, rows_per_loop,
+                      calchisto, histogram, &status);
+
+    fits_close_file(tblptr, &status);      /* all done */
+    return(status);
+}
+/*--------------------------------------------------------------------------*/
+int calchisto(long totalrows, long offset, long firstrow, long nrows,
+             int ncols, iteratorCol *cols, void *userPointer)
+
+/*
+   Interator work function that calculates values for the 2D histogram.
+*/
+{
+    extern long xsize, ysize, xbinsize, ybinsize;
+    long ii, ihisto, xbin, ybin;
+    static long *xcol, *ycol, *histogram;  /* static to preserve values */
+
+    /*--------------------------------------------------------*/
+    /*  Initialization procedures: execute on the first call  */
+    /*--------------------------------------------------------*/
+    if (firstrow == 1)
+    {
+        /* do sanity checking of input values */
+       if (ncols != 2)
+         return(-3);  /* number of arrays is incorrect */
+
+       if (fits_iter_get_datatype(&cols[0]) != TLONG ||
+           fits_iter_get_datatype(&cols[1]) != TLONG)
+         return(-4);  /* wrong datatypes */
+
+       /* assign the input array points to the X and Y arrays */
+       xcol = (long *) fits_iter_get_array(&cols[0]);
+       ycol = (long *) fits_iter_get_array(&cols[1]);
+       histogram = (long *) userPointer;
+
+       /* initialize the histogram image pixels = 0 */
+       for (ii = 0; ii <= xsize * ysize; ii++)
+           histogram[ii] = 0L;
+    }
+
+    /*------------------------------------------------------------------*/
+    /*  Main loop: increment the 2D histogram at position of each event */
+    /*------------------------------------------------------------------*/
+
+    for (ii = 1; ii <= nrows; ii++) 
+    {
+        xbin = xcol[ii] / xbinsize;
+        ybin = ycol[ii] / ybinsize;
+
+        ihisto = ( ybin * xsize ) + xbin + 1;
+        histogram[ihisto]++;
+    }
+
+    return(0);
+}
+
diff --git a/src/plugins/cfitsio/iter_image.c b/src/plugins/cfitsio/iter_image.c
new file mode 100644
index 0000000..43f263c
--- /dev/null
+++ b/src/plugins/cfitsio/iter_image.c
@@ -0,0 +1,93 @@
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "fitsio.h"
+
+/*
+  This program illustrates how to use the CFITSIO iterator function.
+  It reads and modifies the input 'iter_image.fit' image file by setting
+  all the pixel values to zero (DESTROYING THE ORIGINAL IMAGE!!!)
+*/
+main()
+{
+    extern zero_image(); /* external work function is passed to the iterator */
+    fitsfile *fptr;
+    iteratorCol cols[3];  /* structure used by the iterator function */
+    int n_cols;
+    long rows_per_loop, offset;
+
+    int status, nkeys, keypos, hdutype, ii, jj;
+    char filename[]  = "iter_image.fit";     /* name of rate FITS file */
+
+    status = 0; 
+
+    fits_open_file(&fptr, filename, READWRITE, &status); /* open file */
+
+
+    n_cols = 1;
+
+    /* define input column structure members for the iterator function */
+    fits_iter_set_file(&cols[0], fptr);
+    fits_iter_set_iotype(&cols[0], InputOutputCol);
+    fits_iter_set_datatype(&cols[0], 0);
+
+    rows_per_loop = 0;  /* use default optimum number of rows */
+    offset = 0;         /* process all the rows */
+
+    /* apply the rate function to each row of the table */
+    printf("Calling iterator function...%d\n", status);
+
+    fits_iterate_data(n_cols, cols, offset, rows_per_loop,
+                      zero_image, 0L, &status);
+
+    fits_close_file(fptr, &status);      /* all done */
+
+    if (status)
+        fits_report_error(stderr, status);  /* print out error messages */
+
+    return(status);
+}
+/*--------------------------------------------------------------------------*/
+int zero_image(long totalrows, long offset, long firstrow, long nrows,
+             int ncols, iteratorCol *cols, void *user_strct ) 
+
+/*
+   Sample iterator function that calculates the output flux 'rate' column
+   by dividing the input 'counts' by the 'time' column.
+   It also applies a constant deadtime correction factor if the 'deadtime'
+   keyword exists.  Finally, this creates or updates the 'LIVETIME'
+   keyword with the sum of all the individual integration times.
+*/
+{
+    int ii, status = 0;
+
+    /* declare variables static to preserve their values between calls */
+    static int *counts;
+
+    /*--------------------------------------------------------*/
+    /*  Initialization procedures: execute on the first call  */
+    /*--------------------------------------------------------*/
+    if (firstrow == 1)
+    {
+       if (ncols != 1)
+           return(-1);  /* number of columns incorrect */
+
+       /* assign the input pointers to the appropriate arrays and null ptrs*/
+       counts       = (int *)  fits_iter_get_array(&cols[0]);
+    }
+
+    /*--------------------------------------------*/
+    /*  Main loop: process all the rows of data */
+    /*--------------------------------------------*/
+
+    /*  NOTE: 1st element of array is the null pixel value!  */
+    /*  Loop from 1 to nrows, not 0 to nrows - 1.  */
+
+    for (ii = 1; ii <= nrows; ii++)
+    {
+       counts[ii] = 1.;
+    }
+    printf("firstrows, nrows = %d %d\n", firstrow, nrows);
+    
+    return(0);  /* return successful status */
+}
diff --git a/src/plugins/cfitsio/iter_var.c b/src/plugins/cfitsio/iter_var.c
new file mode 100644
index 0000000..50d0132
--- /dev/null
+++ b/src/plugins/cfitsio/iter_var.c
@@ -0,0 +1,100 @@
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "fitsio.h"
+
+/*
+  This program illustrates how to use the CFITSIO iterator function.
+  It reads and modifies the input 'iter_a.fit' file by computing a
+  value for the 'rate' column as a function of the values in the other
+  'counts' and 'time' columns.
+*/
+main()
+{
+    extern flux_rate(); /* external work function is passed to the iterator */
+    fitsfile *fptr;
+    iteratorCol cols[3];  /* structure used by the iterator function */
+    int n_cols;
+    long rows_per_loop, offset;
+
+    int status, nkeys, keypos, hdutype, ii, jj;
+    char filename[]  = "vari.fits";     /* name of rate FITS file */
+
+    status = 0; 
+
+    fits_open_file(&fptr, filename, READWRITE, &status); /* open file */
+
+    /* move to the desired binary table extension */
+    if (fits_movnam_hdu(fptr, BINARY_TBL, "COMPRESSED_IMAGE", 0, &status) )
+        fits_report_error(stderr, status);    /* print out error messages */
+
+    n_cols  = 1;   /* number of columns */
+
+    /* define input column structure members for the iterator function */
+    fits_iter_set_by_name(&cols[0], fptr, "COMPRESSED_DATA", 0,  InputCol);
+
+    rows_per_loop = 0;  /* use default optimum number of rows */
+    offset = 0;         /* process all the rows */
+
+    /* apply the rate function to each row of the table */
+    printf("Calling iterator function...%d\n", status);
+
+    fits_iterate_data(n_cols, cols, offset, rows_per_loop,
+                      flux_rate, 0L, &status);
+
+    fits_close_file(fptr, &status);      /* all done */
+
+    if (status)
+        fits_report_error(stderr, status);  /* print out error messages */
+
+    return(status);
+}
+/*--------------------------------------------------------------------------*/
+int flux_rate(long totalrows, long offset, long firstrow, long nrows,
+             int ncols, iteratorCol *cols, void *user_strct ) 
+
+/*
+   Sample iterator function that calculates the output flux 'rate' column
+   by dividing the input 'counts' by the 'time' column.
+   It also applies a constant deadtime correction factor if the 'deadtime'
+   keyword exists.  Finally, this creates or updates the 'LIVETIME'
+   keyword with the sum of all the individual integration times.
+*/
+{
+    int ii, status = 0;
+    long repeat;
+
+    /* declare variables static to preserve their values between calls */
+    static unsigned char *counts;
+
+    /*--------------------------------------------------------*/
+    /*  Initialization procedures: execute on the first call  */
+    /*--------------------------------------------------------*/
+    if (firstrow == 1)
+    {
+
+printf("Datatype of column = %d\n",fits_iter_get_datatype(&cols[0]));
+
+       /* assign the input pointers to the appropriate arrays and null ptrs*/
+       counts       = (long *)  fits_iter_get_array(&cols[0]);
+
+    }
+
+    /*--------------------------------------------*/
+    /*  Main loop: process all the rows of data */
+    /*--------------------------------------------*/
+
+    /*  NOTE: 1st element of array is the null pixel value!  */
+    /*  Loop from 1 to nrows, not 0 to nrows - 1.  */
+
+
+    for (ii = 1; ii <= nrows; ii++)
+    {
+       repeat = fits_iter_get_repeat(&cols[0]);
+       printf ("repeat = %d, %d\n",repeat, counts[1]);
+       
+    }
+
+
+    return(0);  /* return successful status */
+}
diff --git a/src/plugins/cfitsio/longnam.h b/src/plugins/cfitsio/longnam.h
new file mode 100644
index 0000000..cac8da4
--- /dev/null
+++ b/src/plugins/cfitsio/longnam.h
@@ -0,0 +1,592 @@
+#ifndef _LONGNAME_H
+#define _LONGNAME_H
+
+#define fits_parse_input_url ffiurl
+#define fits_parse_input_filename ffifile
+#define fits_parse_rootname ffrtnm
+#define fits_file_exists    ffexist
+#define fits_parse_output_url ffourl
+#define fits_parse_extspec  ffexts
+#define fits_parse_extnum   ffextn
+#define fits_parse_binspec  ffbins
+#define fits_parse_binrange ffbinr
+#define fits_parse_range    ffrwrg
+#define fits_parse_rangell    ffrwrgll
+#define fits_open_memfile   ffomem
+
+/* 
+   use the following special macro to test that the fitsio.h include
+   file that was used to build the CFITSIO library is the same version
+   as included when compiling the application program
+*/
+#define fits_open_file(A, B, C, D)  ffopentest( CFITSIO_VERSION, A, B, C, D)
+
+#define fits_open_data      ffdopn
+#define fits_open_table     fftopn
+#define fits_open_image     ffiopn
+#define fits_open_diskfile  ffdkopn
+#define fits_reopen_file    ffreopen
+#define fits_create_file    ffinit
+#define fits_create_diskfile ffdkinit
+#define fits_create_memfile ffimem
+#define fits_create_template fftplt
+#define fits_flush_file     ffflus
+#define fits_flush_buffer   ffflsh
+#define fits_close_file     ffclos
+#define fits_delete_file    ffdelt
+#define fits_file_name      ffflnm
+#define fits_file_mode      ffflmd
+#define fits_url_type       ffurlt
+
+#define fits_get_version    ffvers
+#define fits_uppercase      ffupch
+#define fits_get_errstatus  ffgerr
+#define fits_write_errmsg   ffpmsg
+#define fits_write_errmark  ffpmrk
+#define fits_read_errmsg    ffgmsg
+#define fits_clear_errmsg   ffcmsg
+#define fits_clear_errmark  ffcmrk
+#define fits_report_error   ffrprt
+#define fits_compare_str    ffcmps
+#define fits_test_keyword   fftkey
+#define fits_test_record    fftrec
+#define fits_null_check     ffnchk
+#define fits_make_keyn      ffkeyn
+#define fits_make_nkey      ffnkey
+#define fits_get_keyclass   ffgkcl
+#define fits_get_keytype    ffdtyp
+#define fits_get_inttype    ffinttyp
+#define fits_parse_value    ffpsvc
+#define fits_get_keyname    ffgknm
+#define fits_parse_template ffgthd
+#define fits_ascii_tform    ffasfm
+#define fits_binary_tform   ffbnfm
+#define fits_binary_tformll   ffbnfmll
+#define fits_get_tbcol      ffgabc
+#define fits_get_rowsize    ffgrsz
+#define fits_get_col_display_width    ffgcdw
+
+#define fits_write_record       ffprec
+#define fits_write_key          ffpky
+#define fits_write_key_unit     ffpunt
+#define fits_write_comment      ffpcom
+#define fits_write_history      ffphis 
+#define fits_write_date         ffpdat
+#define fits_get_system_time    ffgstm
+#define fits_get_system_date    ffgsdt
+#define fits_date2str           ffdt2s
+#define fits_time2str           fftm2s
+#define fits_str2date           ffs2dt
+#define fits_str2time           ffs2tm
+#define fits_write_key_longstr  ffpkls
+#define fits_write_key_longwarn ffplsw
+#define fits_write_key_null     ffpkyu
+#define fits_write_key_str      ffpkys
+#define fits_write_key_log      ffpkyl
+#define fits_write_key_lng      ffpkyj
+#define fits_write_key_fixflt   ffpkyf
+#define fits_write_key_flt      ffpkye
+#define fits_write_key_fixdbl   ffpkyg
+#define fits_write_key_dbl      ffpkyd
+#define fits_write_key_fixcmp   ffpkfc
+#define fits_write_key_cmp      ffpkyc
+#define fits_write_key_fixdblcmp ffpkfm
+#define fits_write_key_dblcmp   ffpkym
+#define fits_write_key_triple   ffpkyt
+#define fits_write_tdim         ffptdm
+#define fits_write_tdimll       ffptdmll
+#define fits_write_keys_str     ffpkns
+#define fits_write_keys_log     ffpknl
+#define fits_write_keys_lng     ffpknj
+#define fits_write_keys_fixflt  ffpknf
+#define fits_write_keys_flt     ffpkne
+#define fits_write_keys_fixdbl  ffpkng
+#define fits_write_keys_dbl     ffpknd
+#define fits_copy_key           ffcpky
+#define fits_write_imghdr       ffphps
+#define fits_write_imghdrll     ffphpsll
+#define fits_write_grphdr       ffphpr
+#define fits_write_grphdrll     ffphprll
+#define fits_write_atblhdr      ffphtb
+#define fits_write_btblhdr      ffphbn
+#define fits_write_exthdr       ffphext
+#define fits_write_key_template ffpktp
+
+#define fits_get_hdrspace      ffghsp
+#define fits_get_hdrpos        ffghps
+#define fits_movabs_key        ffmaky
+#define fits_movrel_key        ffmrky
+#define fits_find_nextkey      ffgnxk
+
+#define fits_read_record       ffgrec
+#define fits_read_card         ffgcrd
+#define fits_read_str          ffgstr
+#define fits_read_key_unit     ffgunt
+#define fits_read_keyn         ffgkyn
+#define fits_read_key          ffgky
+#define fits_read_keyword      ffgkey
+#define fits_read_key_str      ffgkys
+#define fits_read_key_log      ffgkyl
+#define fits_read_key_lng      ffgkyj
+#define fits_read_key_lnglng   ffgkyjj
+#define fits_read_key_flt      ffgkye
+#define fits_read_key_dbl      ffgkyd
+#define fits_read_key_cmp      ffgkyc
+#define fits_read_key_dblcmp   ffgkym
+#define fits_read_key_triple   ffgkyt
+#define fits_read_key_longstr  ffgkls
+#define fits_free_memory       fffree
+#define fits_read_tdim         ffgtdm
+#define fits_read_tdimll       ffgtdmll
+#define fits_decode_tdim       ffdtdm
+#define fits_decode_tdimll     ffdtdmll
+#define fits_read_keys_str     ffgkns
+#define fits_read_keys_log     ffgknl
+#define fits_read_keys_lng     ffgknj
+#define fits_read_keys_lnglng  ffgknjj
+#define fits_read_keys_flt     ffgkne
+#define fits_read_keys_dbl     ffgknd
+#define fits_read_imghdr       ffghpr
+#define fits_read_imghdrll     ffghprll
+#define fits_read_atblhdr      ffghtb
+#define fits_read_btblhdr      ffghbn
+#define fits_read_atblhdrll    ffghtbll
+#define fits_read_btblhdrll    ffghbnll
+#define fits_hdr2str           ffhdr2str
+#define fits_convert_hdr2str   ffcnvthdr2str
+
+#define fits_update_card       ffucrd
+#define fits_update_key        ffuky
+#define fits_update_key_null   ffukyu
+#define fits_update_key_str    ffukys
+#define fits_update_key_longstr    ffukls
+#define fits_update_key_log    ffukyl
+#define fits_update_key_lng    ffukyj
+#define fits_update_key_fixflt ffukyf
+#define fits_update_key_flt    ffukye
+#define fits_update_key_fixdbl ffukyg
+#define fits_update_key_dbl    ffukyd
+#define fits_update_key_fixcmp ffukfc
+#define fits_update_key_cmp    ffukyc
+#define fits_update_key_fixdblcmp ffukfm
+#define fits_update_key_dblcmp ffukym
+
+#define fits_modify_record     ffmrec
+#define fits_modify_card       ffmcrd
+#define fits_modify_name       ffmnam
+#define fits_modify_comment    ffmcom
+#define fits_modify_key_null   ffmkyu
+#define fits_modify_key_str    ffmkys
+#define fits_modify_key_longstr    ffmkls
+#define fits_modify_key_log    ffmkyl
+#define fits_modify_key_lng    ffmkyj
+#define fits_modify_key_fixflt ffmkyf
+#define fits_modify_key_flt    ffmkye
+#define fits_modify_key_fixdbl ffmkyg
+#define fits_modify_key_dbl    ffmkyd
+#define fits_modify_key_fixcmp ffmkfc
+#define fits_modify_key_cmp    ffmkyc
+#define fits_modify_key_fixdblcmp ffmkfm
+#define fits_modify_key_dblcmp ffmkym
+
+#define fits_insert_record     ffirec
+#define fits_insert_card       ffikey
+#define fits_insert_key_null   ffikyu
+#define fits_insert_key_str    ffikys
+#define fits_insert_key_longstr    ffikls
+#define fits_insert_key_log    ffikyl
+#define fits_insert_key_lng    ffikyj
+#define fits_insert_key_fixflt ffikyf
+#define fits_insert_key_flt    ffikye
+#define fits_insert_key_fixdbl ffikyg
+#define fits_insert_key_dbl    ffikyd
+#define fits_insert_key_fixcmp ffikfc
+#define fits_insert_key_cmp    ffikyc
+#define fits_insert_key_fixdblcmp ffikfm
+#define fits_insert_key_dblcmp ffikym
+
+#define fits_delete_key     ffdkey
+#define fits_delete_str     ffdstr
+#define fits_delete_record  ffdrec
+#define fits_get_hdu_num    ffghdn
+#define fits_get_hdu_type   ffghdt
+#define fits_get_hduaddr    ffghad
+#define fits_get_hduaddrll    ffghadll
+#define fits_get_hduoff     ffghof
+
+#define fits_get_img_param  ffgipr
+#define fits_get_img_paramll  ffgiprll
+
+#define fits_get_img_type   ffgidt
+#define fits_get_img_equivtype   ffgiet
+#define fits_get_img_dim    ffgidm
+#define fits_get_img_size   ffgisz
+#define fits_get_img_sizell   ffgiszll
+
+#define fits_movabs_hdu     ffmahd
+#define fits_movrel_hdu     ffmrhd
+#define fits_movnam_hdu     ffmnhd
+#define fits_get_num_hdus   ffthdu
+#define fits_create_img     ffcrim
+#define fits_create_imgll   ffcrimll
+#define fits_create_tbl     ffcrtb
+#define fits_create_hdu     ffcrhd
+#define fits_insert_img     ffiimg
+#define fits_insert_imgll   ffiimgll
+#define fits_insert_atbl    ffitab
+#define fits_insert_btbl    ffibin
+#define fits_resize_img     ffrsim
+#define fits_resize_imgll   ffrsimll
+
+#define fits_delete_hdu     ffdhdu
+#define fits_copy_hdu       ffcopy
+#define fits_copy_file      ffcpfl
+#define fits_copy_header    ffcphd
+#define fits_copy_data      ffcpdt
+#define fits_write_hdu      ffwrhdu
+
+#define fits_set_hdustruc   ffrdef
+#define fits_set_hdrsize    ffhdef
+#define fits_write_theap    ffpthp
+
+#define fits_encode_chksum  ffesum
+#define fits_decode_chksum  ffdsum
+#define fits_write_chksum   ffpcks
+#define fits_update_chksum  ffupck
+#define fits_verify_chksum  ffvcks
+#define fits_get_chksum     ffgcks
+
+#define fits_set_bscale     ffpscl
+#define fits_set_tscale     fftscl
+#define fits_set_imgnull    ffpnul
+#define fits_set_btblnull   fftnul
+#define fits_set_atblnull   ffsnul
+
+#define fits_get_colnum     ffgcno
+#define fits_get_colname    ffgcnn
+#define fits_get_coltype    ffgtcl
+#define fits_get_coltypell  ffgtclll
+#define fits_get_eqcoltype  ffeqty
+#define fits_get_eqcoltypell ffeqtyll
+#define fits_get_num_rows   ffgnrw
+#define fits_get_num_rowsll   ffgnrwll
+#define fits_get_num_cols   ffgncl
+#define fits_get_acolparms  ffgacl
+#define fits_get_bcolparms  ffgbcl
+#define fits_get_bcolparmsll  ffgbclll
+
+#define fits_iterate_data   ffiter
+
+#define fits_read_grppar_byt  ffggpb
+#define fits_read_grppar_sbyt  ffggpsb
+#define fits_read_grppar_usht  ffggpui
+#define fits_read_grppar_ulng  ffggpuj
+#define fits_read_grppar_sht  ffggpi
+#define fits_read_grppar_lng  ffggpj
+#define fits_read_grppar_lnglng  ffggpjj
+#define fits_read_grppar_int  ffggpk
+#define fits_read_grppar_uint  ffggpuk
+#define fits_read_grppar_flt  ffggpe
+#define fits_read_grppar_dbl  ffggpd
+
+#define fits_read_pix         ffgpxv
+#define fits_read_pixll       ffgpxvll
+#define fits_read_pixnull     ffgpxf
+#define fits_read_pixnullll   ffgpxfll
+#define fits_read_img         ffgpv
+#define fits_read_imgnull     ffgpf
+#define fits_read_img_byt     ffgpvb
+#define fits_read_img_sbyt     ffgpvsb
+#define fits_read_img_usht     ffgpvui
+#define fits_read_img_ulng     ffgpvuj
+#define fits_read_img_sht     ffgpvi
+#define fits_read_img_lng     ffgpvj
+#define fits_read_img_lnglng     ffgpvjj
+#define fits_read_img_uint     ffgpvuk
+#define fits_read_img_int     ffgpvk
+#define fits_read_img_flt     ffgpve
+#define fits_read_img_dbl     ffgpvd
+
+#define fits_read_imgnull_byt ffgpfb
+#define fits_read_imgnull_sbyt ffgpfsb
+#define fits_read_imgnull_usht ffgpfui
+#define fits_read_imgnull_ulng ffgpfuj
+#define fits_read_imgnull_sht ffgpfi
+#define fits_read_imgnull_lng ffgpfj
+#define fits_read_imgnull_lnglng ffgpfjj
+#define fits_read_imgnull_uint ffgpfuk
+#define fits_read_imgnull_int ffgpfk
+#define fits_read_imgnull_flt ffgpfe
+#define fits_read_imgnull_dbl ffgpfd
+
+#define fits_read_2d_byt      ffg2db
+#define fits_read_2d_sbyt     ffg2dsb
+#define fits_read_2d_usht      ffg2dui
+#define fits_read_2d_ulng      ffg2duj
+#define fits_read_2d_sht      ffg2di
+#define fits_read_2d_lng      ffg2dj
+#define fits_read_2d_lnglng      ffg2djj
+#define fits_read_2d_uint      ffg2duk
+#define fits_read_2d_int      ffg2dk
+#define fits_read_2d_flt      ffg2de
+#define fits_read_2d_dbl      ffg2dd
+
+#define fits_read_3d_byt      ffg3db
+#define fits_read_3d_sbyt      ffg3dsb
+#define fits_read_3d_usht      ffg3dui
+#define fits_read_3d_ulng      ffg3duj
+#define fits_read_3d_sht      ffg3di
+#define fits_read_3d_lng      ffg3dj
+#define fits_read_3d_lnglng      ffg3djj
+#define fits_read_3d_uint      ffg3duk
+#define fits_read_3d_int      ffg3dk
+#define fits_read_3d_flt      ffg3de
+#define fits_read_3d_dbl      ffg3dd
+
+#define fits_read_subset      ffgsv
+#define fits_read_subset_byt  ffgsvb
+#define fits_read_subset_sbyt  ffgsvsb
+#define fits_read_subset_usht  ffgsvui
+#define fits_read_subset_ulng  ffgsvuj
+#define fits_read_subset_sht  ffgsvi
+#define fits_read_subset_lng  ffgsvj
+#define fits_read_subset_lnglng  ffgsvjj
+#define fits_read_subset_uint  ffgsvuk
+#define fits_read_subset_int  ffgsvk
+#define fits_read_subset_flt  ffgsve
+#define fits_read_subset_dbl  ffgsvd
+
+#define fits_read_subsetnull_byt ffgsfb
+#define fits_read_subsetnull_sbyt ffgsfsb
+#define fits_read_subsetnull_usht ffgsfui
+#define fits_read_subsetnull_ulng ffgsfuj
+#define fits_read_subsetnull_sht ffgsfi
+#define fits_read_subsetnull_lng ffgsfj
+#define fits_read_subsetnull_lnglng ffgsfjj
+#define fits_read_subsetnull_uint ffgsfuk
+#define fits_read_subsetnull_int ffgsfk
+#define fits_read_subsetnull_flt ffgsfe
+#define fits_read_subsetnull_dbl ffgsfd
+
+#define ffcpimg fits_copy_image_section
+#define fits_compress_img fits_comp_img
+#define fits_decompress_img fits_decomp_img
+
+#define fits_read_col        ffgcv
+#define fits_read_colnull    ffgcf
+#define fits_read_col_str    ffgcvs
+#define fits_read_col_log    ffgcvl
+#define fits_read_col_byt    ffgcvb
+#define fits_read_col_sbyt    ffgcvsb
+#define fits_read_col_usht    ffgcvui
+#define fits_read_col_ulng    ffgcvuj
+#define fits_read_col_sht    ffgcvi
+#define fits_read_col_lng    ffgcvj
+#define fits_read_col_lnglng    ffgcvjj
+#define fits_read_col_uint    ffgcvuk
+#define fits_read_col_int    ffgcvk
+#define fits_read_col_flt    ffgcve
+#define fits_read_col_dbl    ffgcvd
+#define fits_read_col_cmp    ffgcvc
+#define fits_read_col_dblcmp ffgcvm
+#define fits_read_col_bit    ffgcx
+#define fits_read_col_bit_usht ffgcxui
+#define fits_read_col_bit_uint ffgcxuk
+
+#define fits_read_colnull_str    ffgcfs
+#define fits_read_colnull_log    ffgcfl
+#define fits_read_colnull_byt    ffgcfb
+#define fits_read_colnull_sbyt    ffgcfsb
+#define fits_read_colnull_usht    ffgcfui
+#define fits_read_colnull_ulng    ffgcfuj
+#define fits_read_colnull_sht    ffgcfi
+#define fits_read_colnull_lng    ffgcfj
+#define fits_read_colnull_lnglng    ffgcfjj
+#define fits_read_colnull_uint    ffgcfuk
+#define fits_read_colnull_int    ffgcfk
+#define fits_read_colnull_flt    ffgcfe
+#define fits_read_colnull_dbl    ffgcfd
+#define fits_read_colnull_cmp    ffgcfc
+#define fits_read_colnull_dblcmp ffgcfm
+
+#define fits_read_descript ffgdes
+#define fits_read_descriptll ffgdesll
+#define fits_read_descripts ffgdess
+#define fits_read_descriptsll ffgdessll
+#define fits_read_tblbytes    ffgtbb
+
+#define fits_write_grppar_byt ffpgpb
+#define fits_write_grppar_sbyt ffpgpsb
+#define fits_write_grppar_usht ffpgpui
+#define fits_write_grppar_ulng ffpgpuj
+#define fits_write_grppar_sht ffpgpi
+#define fits_write_grppar_lng ffpgpj
+#define fits_write_grppar_lnglng ffpgpjj
+#define fits_write_grppar_uint ffpgpuk
+#define fits_write_grppar_int ffpgpk
+#define fits_write_grppar_flt ffpgpe
+#define fits_write_grppar_dbl ffpgpd
+
+#define fits_write_pix        ffppx
+#define fits_write_pixll      ffppxll
+#define fits_write_pixnull    ffppxn
+#define fits_write_pixnullll  ffppxnll
+#define fits_write_img        ffppr
+#define fits_write_img_byt    ffpprb
+#define fits_write_img_sbyt    ffpprsb
+#define fits_write_img_usht    ffpprui
+#define fits_write_img_ulng    ffppruj
+#define fits_write_img_sht    ffppri
+#define fits_write_img_lng    ffpprj
+#define fits_write_img_lnglng    ffpprjj
+#define fits_write_img_uint    ffppruk
+#define fits_write_img_int    ffpprk
+#define fits_write_img_flt    ffppre
+#define fits_write_img_dbl    ffpprd
+
+#define fits_write_imgnull     ffppn
+#define fits_write_imgnull_byt ffppnb
+#define fits_write_imgnull_sbyt ffppnsb
+#define fits_write_imgnull_usht ffppnui
+#define fits_write_imgnull_ulng ffppnuj
+#define fits_write_imgnull_sht ffppni
+#define fits_write_imgnull_lng ffppnj
+#define fits_write_imgnull_lnglng ffppnjj
+#define fits_write_imgnull_uint ffppnuk
+#define fits_write_imgnull_int ffppnk
+#define fits_write_imgnull_flt ffppne
+#define fits_write_imgnull_dbl ffppnd
+
+#define fits_write_img_null ffppru
+#define fits_write_null_img ffpprn
+
+#define fits_write_2d_byt   ffp2db
+#define fits_write_2d_sbyt   ffp2dsb
+#define fits_write_2d_usht   ffp2dui
+#define fits_write_2d_ulng   ffp2duj
+#define fits_write_2d_sht   ffp2di
+#define fits_write_2d_lng   ffp2dj
+#define fits_write_2d_lnglng   ffp2djj
+#define fits_write_2d_uint   ffp2duk
+#define fits_write_2d_int   ffp2dk
+#define fits_write_2d_flt   ffp2de
+#define fits_write_2d_dbl   ffp2dd
+
+#define fits_write_3d_byt   ffp3db
+#define fits_write_3d_sbyt   ffp3dsb
+#define fits_write_3d_usht   ffp3dui
+#define fits_write_3d_ulng   ffp3duj
+#define fits_write_3d_sht   ffp3di
+#define fits_write_3d_lng   ffp3dj
+#define fits_write_3d_lnglng   ffp3djj
+#define fits_write_3d_uint   ffp3duk
+#define fits_write_3d_int   ffp3dk
+#define fits_write_3d_flt   ffp3de
+#define fits_write_3d_dbl   ffp3dd
+
+#define fits_write_subset  ffpss
+#define fits_write_subset_byt  ffpssb
+#define fits_write_subset_sbyt  ffpsssb
+#define fits_write_subset_usht  ffpssui
+#define fits_write_subset_ulng  ffpssuj
+#define fits_write_subset_sht  ffpssi
+#define fits_write_subset_lng  ffpssj
+#define fits_write_subset_lnglng  ffpssjj
+#define fits_write_subset_uint  ffpssuk
+#define fits_write_subset_int  ffpssk
+#define fits_write_subset_flt  ffpsse
+#define fits_write_subset_dbl  ffpssd
+
+#define fits_write_col         ffpcl
+#define fits_write_col_str     ffpcls
+#define fits_write_col_log     ffpcll
+#define fits_write_col_byt     ffpclb
+#define fits_write_col_sbyt     ffpclsb
+#define fits_write_col_usht     ffpclui
+#define fits_write_col_ulng     ffpcluj
+#define fits_write_col_sht     ffpcli
+#define fits_write_col_lng     ffpclj
+#define fits_write_col_lnglng     ffpcljj
+#define fits_write_col_uint     ffpcluk
+#define fits_write_col_int     ffpclk
+#define fits_write_col_flt     ffpcle
+#define fits_write_col_dbl     ffpcld
+#define fits_write_col_cmp     ffpclc
+#define fits_write_col_dblcmp  ffpclm
+#define fits_write_col_null    ffpclu
+#define fits_write_col_bit     ffpclx
+#define fits_write_nulrows     ffprwu
+#define fits_write_nullrows    ffprwu
+
+#define fits_write_colnull ffpcn
+#define fits_write_colnull_str ffpcns
+#define fits_write_colnull_log ffpcnl
+#define fits_write_colnull_byt ffpcnb
+#define fits_write_colnull_sbyt ffpcnsb
+#define fits_write_colnull_usht ffpcnui
+#define fits_write_colnull_ulng ffpcnuj
+#define fits_write_colnull_sht ffpcni
+#define fits_write_colnull_lng ffpcnj
+#define fits_write_colnull_lnglng ffpcnjj
+#define fits_write_colnull_uint ffpcnuk
+#define fits_write_colnull_int ffpcnk
+#define fits_write_colnull_flt ffpcne
+#define fits_write_colnull_dbl ffpcnd
+
+#define fits_write_ext ffpextn
+#define fits_read_ext  ffgextn
+
+#define fits_write_descript  ffpdes
+#define fits_compress_heap   ffcmph
+#define fits_test_heap   fftheap
+
+#define fits_write_tblbytes  ffptbb
+#define fits_insert_rows  ffirow
+#define fits_delete_rows  ffdrow
+#define fits_delete_rowrange ffdrrg
+#define fits_delete_rowlist ffdrws
+#define fits_delete_rowlistll ffdrwsll
+#define fits_insert_col   fficol
+#define fits_insert_cols  fficls
+#define fits_delete_col   ffdcol
+#define fits_copy_col     ffcpcl
+#define fits_copy_rows    ffcprw
+#define fits_modify_vector_len  ffmvec
+
+#define fits_read_img_coord ffgics
+#define fits_read_img_coord_version ffgicsa
+#define fits_read_tbl_coord ffgtcs
+#define fits_pix_to_world ffwldp
+#define fits_world_to_pix ffxypx
+
+#define fits_get_image_wcs_keys ffgiwcs
+#define fits_get_table_wcs_keys ffgtwcs
+
+#define fits_find_rows          fffrow
+#define fits_find_first_row     ffffrw
+#define fits_find_rows_cmp      fffrwc
+#define fits_select_rows        ffsrow
+#define fits_calc_rows          ffcrow
+#define fits_calculator         ffcalc
+#define fits_calculator_rng     ffcalc_rng
+#define fits_test_expr          fftexp
+
+#define fits_create_group       ffgtcr 
+#define fits_insert_group       ffgtis 
+#define fits_change_group       ffgtch 
+#define fits_remove_group       ffgtrm 
+#define fits_copy_group         ffgtcp 
+#define fits_merge_groups       ffgtmg 
+#define fits_compact_group      ffgtcm 
+#define fits_verify_group       ffgtvf 
+#define fits_open_group         ffgtop 
+#define fits_add_group_member   ffgtam 
+#define fits_get_num_members    ffgtnm 
+
+#define fits_get_num_groups     ffgmng 
+#define fits_open_member        ffgmop 
+#define fits_copy_member        ffgmcp 
+#define fits_transfer_member    ffgmtf 
+#define fits_remove_member      ffgmrm
+
+#endif
diff --git a/src/plugins/cfitsio/modkey.c b/src/plugins/cfitsio/modkey.c
new file mode 100644
index 0000000..e6fe03a
--- /dev/null
+++ b/src/plugins/cfitsio/modkey.c
@@ -0,0 +1,1706 @@
+/*  This file, modkey.c, contains routines that modify, insert, or update  */
+/*  keywords in a FITS header.                                             */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+
+#include <string.h>
+/* stddef.h is apparently needed to define size_t */
+#include <ctype.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include "fitsio2.h"
+/*--------------------------------------------------------------------------*/
+int ffuky( fitsfile *fptr,     /* I - FITS file pointer        */
+           int  datatype,      /* I - datatype of the value    */
+           const char *keyname,      /* I - name of keyword to write */
+           void *value,        /* I - keyword value            */
+           char *comm,         /* I - keyword comment          */
+           int  *status)       /* IO - error status            */
+/*
+  Update the keyword, value and comment in the FITS header.
+  The datatype is specified by the 2nd argument.
+*/
+{
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (datatype == TSTRING)
+    {
+        ffukys(fptr, keyname, (char *) value, comm, status);
+    }
+    else if (datatype == TBYTE)
+    {
+        ffukyj(fptr, keyname, (LONGLONG) *(unsigned char *) value, comm, status);
+    }
+    else if (datatype == TSBYTE)
+    {
+        ffukyj(fptr, keyname, (LONGLONG) *(signed char *) value, comm, status);
+    }
+    else if (datatype == TUSHORT)
+    {
+        ffukyj(fptr, keyname, (LONGLONG) *(unsigned short *) value, comm, status);
+    }
+    else if (datatype == TSHORT)
+    {
+        ffukyj(fptr, keyname, (LONGLONG) *(short *) value, comm, status);
+    }
+    else if (datatype == TINT)
+    {
+        ffukyj(fptr, keyname, (LONGLONG) *(int *) value, comm, status);
+    }
+    else if (datatype == TUINT)
+    {
+        ffukyg(fptr, keyname, (double) *(unsigned int *) value, 0,
+               comm, status);
+    }
+    else if (datatype == TLOGICAL)
+    {
+        ffukyl(fptr, keyname, *(int *) value, comm, status);
+    }
+    else if (datatype == TULONG)
+    {
+        ffukyg(fptr, keyname, (double) *(unsigned long *) value, 0,
+               comm, status);
+    }
+    else if (datatype == TLONG)
+    {
+        ffukyj(fptr, keyname, (LONGLONG) *(long *) value, comm, status);
+    }
+    else if (datatype == TLONGLONG)
+    {
+        ffukyj(fptr, keyname, *(LONGLONG *) value, comm, status);
+    }
+    else if (datatype == TFLOAT)
+    {
+        ffukye(fptr, keyname, *(float *) value, -7, comm, status);
+    }
+    else if (datatype == TDOUBLE)
+    {
+        ffukyd(fptr, keyname, *(double *) value, -15, comm, status);
+    }
+    else if (datatype == TCOMPLEX)
+    {
+        ffukyc(fptr, keyname, (float *) value, -7, comm, status);
+    }
+    else if (datatype == TDBLCOMPLEX)
+    {
+        ffukym(fptr, keyname, (double *) value, -15, comm, status);
+    }
+    else
+        *status = BAD_DATATYPE;
+
+    return(*status);
+} 
+/*--------------------------------------------------------------------------*/
+int ffukyu(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           char *comm,        /* I - keyword comment    */
+           int *status)       /* IO - error status      */
+{
+    int tstatus;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    tstatus = *status;
+
+    if (ffmkyu(fptr, keyname, comm, status) == KEY_NO_EXIST)
+    {
+        *status = tstatus;
+        ffpkyu(fptr, keyname, comm, status);
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffukys(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           char *value,       /* I - keyword value      */
+           char *comm,        /* I - keyword comment    */
+           int *status)       /* IO - error status      */ 
+{
+    int tstatus;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    tstatus = *status;
+
+    if (ffmkys(fptr, keyname, value, comm, status) == KEY_NO_EXIST)
+    {
+        *status = tstatus;
+        ffpkys(fptr, keyname, value, comm, status);
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffukls(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           char *value,       /* I - keyword value      */
+           char *comm,        /* I - keyword comment    */
+           int *status)       /* IO - error status      */ 
+{
+    /* update a long string keyword */
+
+    int tstatus;
+    char junk[FLEN_ERRMSG];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    tstatus = *status;
+
+    if (ffmkls(fptr, keyname, value, comm, status) == KEY_NO_EXIST)
+    {
+        /* since the ffmkls call failed, it wrote a bogus error message */
+        fits_read_errmsg(junk);  /* clear the error message */
+	
+        *status = tstatus;
+        ffpkls(fptr, keyname, value, comm, status);
+    }
+    return(*status);
+}/*--------------------------------------------------------------------------*/
+int ffukyl(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           int value,         /* I - keyword value      */
+           char *comm,        /* I - keyword comment    */
+           int *status)       /* IO - error status      */
+{
+    int tstatus;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    tstatus = *status;
+
+    if (ffmkyl(fptr, keyname, value, comm, status) == KEY_NO_EXIST)
+    {
+        *status = tstatus;
+        ffpkyl(fptr, keyname, value, comm, status);
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffukyj(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           LONGLONG value,    /* I - keyword value      */
+           char *comm,        /* I - keyword comment    */
+           int *status)       /* IO - error status      */
+{
+    int tstatus;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    tstatus = *status;
+
+    if (ffmkyj(fptr, keyname, value, comm, status) == KEY_NO_EXIST)
+    {
+        *status = tstatus;
+        ffpkyj(fptr, keyname, value, comm, status);
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffukyf(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           float value,       /* I - keyword value      */
+           int decim,         /* I - no of decimals     */         
+           char *comm,        /* I - keyword comment    */
+           int *status)       /* IO - error status      */
+{
+    int tstatus;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    tstatus = *status;
+
+    if (ffmkyf(fptr, keyname, value, decim, comm, status) == KEY_NO_EXIST)
+    {
+        *status = tstatus;
+        ffpkyf(fptr, keyname, value, decim, comm, status);
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffukye(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           float value,       /* I - keyword value      */
+           int decim,         /* I - no of decimals     */
+           char *comm,        /* I - keyword comment    */
+           int *status)       /* IO - error status      */
+{
+    int tstatus;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    tstatus = *status;
+
+    if (ffmkye(fptr, keyname, value, decim, comm, status) == KEY_NO_EXIST)
+    {
+        *status = tstatus;
+        ffpkye(fptr, keyname, value, decim, comm, status);
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffukyg(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           double value,      /* I - keyword value      */
+           int decim,         /* I - no of decimals     */
+           char *comm,        /* I - keyword comment    */
+           int *status)       /* IO - error status      */
+{
+    int tstatus;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    tstatus = *status;
+
+    if (ffmkyg(fptr, keyname, value, decim, comm, status) == KEY_NO_EXIST)
+    {
+        *status = tstatus;
+        ffpkyg(fptr, keyname, value, decim, comm, status);
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffukyd(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           double value,      /* I - keyword value      */
+           int decim,         /* I - no of decimals     */
+           char *comm,        /* I - keyword comment    */
+           int *status)       /* IO - error status      */
+{
+    int tstatus;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    tstatus = *status;
+
+    if (ffmkyd(fptr, keyname, value, decim, comm, status) == KEY_NO_EXIST)
+    {
+        *status = tstatus;
+        ffpkyd(fptr, keyname, value, decim, comm, status);
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffukfc(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           float *value,      /* I - keyword value      */
+           int decim,         /* I - no of decimals     */         
+           char *comm,        /* I - keyword comment    */
+           int *status)       /* IO - error status      */
+{
+    int tstatus;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    tstatus = *status;
+
+    if (ffmkfc(fptr, keyname, value, decim, comm, status) == KEY_NO_EXIST)
+    {
+        *status = tstatus;
+        ffpkfc(fptr, keyname, value, decim, comm, status);
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffukyc(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           float *value,      /* I - keyword value      */
+           int decim,         /* I - no of decimals     */
+           char *comm,        /* I - keyword comment    */
+           int *status)       /* IO - error status      */
+{
+    int tstatus;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    tstatus = *status;
+
+    if (ffmkyc(fptr, keyname, value, decim, comm, status) == KEY_NO_EXIST)
+    {
+        *status = tstatus;
+        ffpkyc(fptr, keyname, value, decim, comm, status);
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffukfm(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           double *value,     /* I - keyword value      */
+           int decim,         /* I - no of decimals     */
+           char *comm,        /* I - keyword comment    */
+           int *status)       /* IO - error status      */
+{
+    int tstatus;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    tstatus = *status;
+
+    if (ffmkfm(fptr, keyname, value, decim, comm, status) == KEY_NO_EXIST)
+    {
+        *status = tstatus;
+        ffpkfm(fptr, keyname, value, decim, comm, status);
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffukym(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           double *value,     /* I - keyword value      */
+           int decim,         /* I - no of decimals     */
+           char *comm,        /* I - keyword comment    */
+           int *status)       /* IO - error status      */
+{
+    int tstatus;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    tstatus = *status;
+
+    if (ffmkym(fptr, keyname, value, decim, comm, status) == KEY_NO_EXIST)
+    {
+        *status = tstatus;
+        ffpkym(fptr, keyname, value, decim, comm, status);
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffucrd(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           char *card,        /* I - card string value  */
+           int *status)       /* IO - error status      */
+{
+    int tstatus;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    tstatus = *status;
+
+    if (ffmcrd(fptr, keyname, card, status) == KEY_NO_EXIST)
+    {
+        *status = tstatus;
+        ffprec(fptr, card, status);
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffmrec(fitsfile *fptr,    /* I - FITS file pointer               */
+           int nkey,          /* I - number of the keyword to modify */
+           char *card,        /* I - card string value               */
+           int *status)       /* IO - error status                   */
+{
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    ffmaky(fptr, nkey+1, status);
+    ffmkey(fptr, card, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffmcrd(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           char *card,        /* I - card string value  */
+           int *status)       /* IO - error status      */
+{
+    char tcard[FLEN_CARD], valstring[FLEN_CARD], comm[FLEN_CARD], value[FLEN_CARD];
+    int keypos, len;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (ffgcrd(fptr, keyname, tcard, status) > 0)
+        return(*status);
+
+    ffmkey(fptr, card, status);
+
+    /* calc position of keyword in header */
+    keypos = (int) ((((fptr->Fptr)->nextkey) - ((fptr->Fptr)->headstart[(fptr->Fptr)->curhdu])) / 80) + 1;
+
+    ffpsvc(tcard, valstring, comm, status);
+
+    /* check for string value which may be continued over multiple keywords */
+    ffc2s(valstring, value, status);   /* remove quotes and trailing spaces */
+    len = strlen(value);
+
+    while (len && value[len - 1] == '&')  /* ampersand used as continuation char */
+    {
+        ffgcnt(fptr, value, status);
+        if (*value)
+        {
+            ffdrec(fptr, keypos, status);  /* delete the keyword */
+            len = strlen(value);
+        }
+        else   /* a null valstring indicates no continuation */
+            len = 0;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffmnam(fitsfile *fptr,    /* I - FITS file pointer     */
+           const char *oldname,     /* I - existing keyword name */
+           const char *newname,     /* I - new name for keyword  */
+           int *status)       /* IO - error status         */
+{
+    char comm[FLEN_COMMENT];
+    char value[FLEN_VALUE];
+    char card[FLEN_CARD];
+ 
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (ffgkey(fptr, oldname, value, comm, status) > 0)
+        return(*status);
+
+    ffmkky(newname, value, comm, card, status);  /* construct the card */
+    ffmkey(fptr, card, status);  /* rewrite with new name */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffmcom(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           char *comm,        /* I - keyword comment    */
+           int *status)       /* IO - error status      */
+{
+    char oldcomm[FLEN_COMMENT];
+    char value[FLEN_VALUE];
+    char card[FLEN_CARD];
+ 
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (ffgkey(fptr, keyname, value, oldcomm, status) > 0)
+        return(*status);
+
+    ffmkky(keyname, value, comm, card, status);  /* construct the card */
+    ffmkey(fptr, card, status);  /* rewrite with new comment */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpunt(fitsfile *fptr,    /* I - FITS file pointer   */
+           const char *keyname,     /* I - keyword name        */
+           char *unit,        /* I - keyword unit string */
+           int *status)       /* IO - error status       */
+/*
+    Write (put) the units string into the comment field of the existing
+    keyword. This routine uses a local FITS convention (not defined in the
+    official FITS standard) in which the units are enclosed in 
+    square brackets following the '/' comment field delimiter, e.g.:
+
+    KEYWORD =                   12 / [kpc] comment string goes here
+*/
+{
+    char oldcomm[FLEN_COMMENT];
+    char newcomm[FLEN_COMMENT];
+    char value[FLEN_VALUE];
+    char card[FLEN_CARD];
+    char *loc;
+    size_t len;
+ 
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (ffgkey(fptr, keyname, value, oldcomm, status) > 0)
+        return(*status);
+
+    /* copy the units string to the new comment string if not null */
+    if (*unit)
+    {
+        strcpy(newcomm, "[");
+        strncat(newcomm, unit, 45);  /* max allowed length is about 45 chars */
+        strcat(newcomm, "] ");
+        len = strlen(newcomm);  
+        len = FLEN_COMMENT - len - 1;  /* amount of space left in the field */
+    }
+    else
+    {
+        newcomm[0] = '\0';
+        len = FLEN_COMMENT - 1;
+    }
+
+    if (oldcomm[0] == '[')  /* check for existing units field */
+    {
+        loc = strchr(oldcomm, ']');  /* look for the closing bracket */
+        if (loc)
+        {
+            loc++;
+            while (*loc == ' ')   /* skip any blank spaces */
+               loc++;
+
+            strncat(newcomm, loc, len);  /* concat remainder of comment */
+        }
+        else
+        {
+            strncat(newcomm, oldcomm, len);  /* append old comment onto new */
+        }
+    }
+    else
+    {
+        strncat(newcomm, oldcomm, len);
+    }
+
+    ffmkky(keyname, value, newcomm, card, status);  /* construct the card */
+    ffmkey(fptr, card, status);  /* rewrite with new units string */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffmkyu(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           char *comm,        /* I - keyword comment    */
+           int *status)       /* IO - error status      */
+{
+    char valstring[FLEN_VALUE];
+    char oldcomm[FLEN_COMMENT];
+    char card[FLEN_CARD];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (ffgkey(fptr, keyname, valstring, oldcomm, status) > 0)
+        return(*status);                               /* get old comment */
+
+    strcpy(valstring," ");  /* create a dummy value string */
+
+    if (!comm || comm[0] == '&')  /* preserve the current comment string */
+        ffmkky(keyname, valstring, oldcomm, card, status);
+    else
+        ffmkky(keyname, valstring, comm, card, status);
+
+    ffmkey(fptr, card, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffmkys(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           char *value,       /* I - keyword value      */
+           char *comm,        /* I - keyword comment    */
+           int *status)       /* IO - error status      */
+{
+  /* NOTE: This routine does not support long continued strings */
+  /*  It will correctly overwrite an existing long continued string, */
+  /*  but it will not write a new long string.  */
+
+    char oldval[FLEN_VALUE], valstring[FLEN_VALUE];
+    char oldcomm[FLEN_COMMENT];
+    char card[FLEN_CARD];
+    int len, keypos;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (ffgkey(fptr, keyname, oldval, oldcomm, status) > 0)
+        return(*status);                               /* get old comment */
+
+    ffs2c(value, valstring, status);   /* convert value to a string */
+
+    if (!comm || comm[0] == '&')  /* preserve the current comment string */
+        ffmkky(keyname, valstring, oldcomm, card, status);
+    else
+        ffmkky(keyname, valstring, comm, card, status);
+
+    ffmkey(fptr, card, status); /* overwrite the previous keyword */
+
+    keypos = (int) (((((fptr->Fptr)->nextkey) - ((fptr->Fptr)->headstart[(fptr->Fptr)->curhdu])) / 80) + 1);
+
+    /* check if old string value was continued over multiple keywords */
+    ffc2s(oldval, valstring, status); /* remove quotes and trailing spaces */
+    len = strlen(valstring);
+
+    while (len && valstring[len - 1] == '&')  /* ampersand is continuation char */
+    {
+        ffgcnt(fptr, valstring, status);
+        if (*valstring)
+        {
+            ffdrec(fptr, keypos, status);  /* delete the continuation */
+            len = strlen(valstring);
+        }
+        else   /* a null valstring indicates no continuation */
+            len = 0;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffmkls( fitsfile *fptr,     /* I - FITS file pointer        */
+            const char *keyname,      /* I - name of keyword to write */
+            char *value,        /* I - keyword value            */
+            char *incomm,       /* I - keyword comment          */
+            int  *status)       /* IO - error status            */
+/*
+  Modify the value and optionally the comment of a long string keyword.
+  This routine supports the
+  HEASARC long string convention and can modify arbitrarily long string
+  keyword values.  The value is continued over multiple keywords that
+  have the name COMTINUE without an equal sign in column 9 of the card.
+  This routine also supports simple string keywords which are less than
+  69 characters in length.
+
+  This routine is not very efficient, so it should be used sparingly.
+*/
+{
+    char valstring[FLEN_VALUE];
+    char card[FLEN_CARD], tmpkeyname[FLEN_CARD];
+    char comm[FLEN_COMMENT];
+    char tstring[FLEN_VALUE], *cptr;
+    char *longval;
+    int next, remain, vlen, nquote, nchar, namelen, contin, tstatus = -1;
+    int nkeys, keypos;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (!incomm || incomm[0] == '&')  /* preserve the old comment string */
+    {
+        ffghps(fptr, &nkeys, &keypos, status); /* save current position */
+
+        if (ffgkls(fptr, keyname, &longval, comm, status) > 0)
+            return(*status);            /* keyword doesn't exist */
+
+        free(longval);  /* don't need the old value */
+
+        /* move back to previous position to ensure that we delete */
+        /* the right keyword in case there are more than one keyword */
+        /* with this same name. */
+        ffgrec(fptr, keypos - 1, card, status); 
+    } else {
+        /* copy the input comment string */
+        strncpy(comm, incomm, FLEN_COMMENT-1);
+        comm[FLEN_COMMENT-1] = '\0';
+    }
+
+    /* delete the old keyword */
+    if (ffdkey(fptr, keyname, status) > 0)
+        return(*status);            /* keyword doesn't exist */
+
+    ffghps(fptr, &nkeys, &keypos, status); /* save current position */
+
+    /* now construct the new keyword, and insert into header */
+    remain = strlen(value);    /* number of characters to write out */
+    next = 0;                  /* pointer to next character to write */
+    
+    /* count the number of single quote characters in the string */
+    nquote = 0;
+    cptr = strchr(value, '\'');   /* search for quote character */
+
+    while (cptr)  /* search for quote character */
+    {
+        nquote++;            /*  increment no. of quote characters  */
+        cptr++;              /*  increment pointer to next character */
+        cptr = strchr(cptr, '\'');  /* search for another quote char */
+    }
+
+    strncpy(tmpkeyname, keyname, 80);
+    tmpkeyname[80] = '\0';
+    
+    cptr = tmpkeyname;
+    while(*cptr == ' ')   /* skip over leading spaces in name */
+        cptr++;
+
+    /* determine the number of characters that will fit on the line */
+    /* Note: each quote character is expanded to 2 quotes */
+
+    namelen = strlen(cptr);
+    if (namelen <= 8 && (fftkey(cptr, &tstatus) <= 0) )
+    {
+        /* This a normal 8-character FITS keyword */
+        nchar = 68 - nquote; /*  max of 68 chars fit in a FITS string value */
+    }
+    else
+    {
+        /* This a HIERARCH keyword */
+        if (FSTRNCMP(cptr, "HIERARCH ", 9) && 
+            FSTRNCMP(cptr, "hierarch ", 9))
+            nchar = 66 - nquote - namelen;
+        else
+            nchar = 75 - nquote - namelen;  /* don't count 'HIERARCH' twice */
+
+    }
+
+    contin = 0;
+    while (remain > 0)
+    {
+        strncpy(tstring, &value[next], nchar); /* copy string to temp buff */
+        tstring[nchar] = '\0';
+        ffs2c(tstring, valstring, status);  /* put quotes around the string */
+
+        if (remain > nchar)   /* if string is continued, put & as last char */
+        {
+            vlen = strlen(valstring);
+            nchar -= 1;        /* outputting one less character now */
+
+            if (valstring[vlen-2] != '\'')
+                valstring[vlen-2] = '&';  /*  over write last char with &  */
+            else
+            { /* last char was a pair of single quotes, so over write both */
+                valstring[vlen-3] = '&';
+                valstring[vlen-1] = '\0';
+            }
+        }
+
+        if (contin)           /* This is a CONTINUEd keyword */
+        {
+           ffmkky("CONTINUE", valstring, comm, card, status); /* make keyword */
+           strncpy(&card[8], "   ",  2);  /* overwrite the '=' */
+        }
+        else
+        {
+           ffmkky(keyname, valstring, comm, card, status);  /* make keyword */
+        }
+
+        ffirec(fptr, keypos, card, status);  /* insert the keyword */
+       
+        keypos++;        /* next insert position */
+        contin = 1;
+        remain -= nchar;
+        next  += nchar;
+        nchar = 68 - nquote;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffmkyl(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           int value,         /* I - keyword value      */
+           char *comm,        /* I - keyword comment    */
+           int *status)       /* IO - error status      */
+{
+    char valstring[FLEN_VALUE];
+    char oldcomm[FLEN_COMMENT];
+    char card[FLEN_CARD];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (ffgkey(fptr, keyname, valstring, oldcomm, status) > 0)
+        return(*status);                               /* get old comment */
+
+    ffl2c(value, valstring, status);   /* convert value to a string */
+
+    if (!comm || comm[0] == '&')  /* preserve the current comment string */
+        ffmkky(keyname, valstring, oldcomm, card, status);
+    else
+        ffmkky(keyname, valstring, comm, card, status);
+
+    ffmkey(fptr, card, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffmkyj(fitsfile *fptr,   /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           LONGLONG value,    /* I - keyword value      */
+           char *comm,        /* I - keyword comment    */
+           int *status)       /* IO - error status      */
+{
+    char valstring[FLEN_VALUE];
+    char oldcomm[FLEN_COMMENT];
+    char card[FLEN_CARD];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (ffgkey(fptr, keyname, valstring, oldcomm, status) > 0)
+        return(*status);                               /* get old comment */
+
+    ffi2c(value, valstring, status);   /* convert value to a string */
+
+    if (!comm || comm[0] == '&')  /* preserve the current comment string */
+        ffmkky(keyname, valstring, oldcomm, card, status);
+    else
+        ffmkky(keyname, valstring, comm, card, status);
+
+    ffmkey(fptr, card, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffmkyf(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           float value,       /* I - keyword value      */
+           int decim,         /* I - no of decimals     */
+           char *comm,        /* I - keyword comment    */
+           int *status)       /* IO - error status      */
+{
+    char valstring[FLEN_VALUE];
+    char oldcomm[FLEN_COMMENT];
+    char card[FLEN_CARD];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (ffgkey(fptr, keyname, valstring, oldcomm, status) > 0)
+        return(*status);                               /* get old comment */
+
+    ffr2f(value, decim, valstring, status);   /* convert value to a string */
+
+    if (!comm || comm[0] == '&')  /* preserve the current comment string */
+        ffmkky(keyname, valstring, oldcomm, card, status);
+    else
+        ffmkky(keyname, valstring, comm, card, status);
+
+    ffmkey(fptr, card, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffmkye(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           float value,       /* I - keyword value      */
+           int decim,         /* I - no of decimals     */
+           char *comm,        /* I - keyword comment    */
+           int *status)       /* IO - error status      */
+{
+    char valstring[FLEN_VALUE];
+    char oldcomm[FLEN_COMMENT];
+    char card[FLEN_CARD];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (ffgkey(fptr, keyname, valstring, oldcomm, status) > 0)
+        return(*status);                               /* get old comment */
+
+    ffr2e(value, decim, valstring, status);   /* convert value to a string */
+
+    if (!comm || comm[0] == '&')  /* preserve the current comment string */
+        ffmkky(keyname, valstring, oldcomm, card, status);
+    else
+        ffmkky(keyname, valstring, comm, card, status);
+
+    ffmkey(fptr, card, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffmkyg(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           double value,      /* I - keyword value      */
+           int decim,         /* I - no of decimals     */
+           char *comm,        /* I - keyword comment    */
+           int *status)       /* IO - error status      */
+{
+    char valstring[FLEN_VALUE];
+    char oldcomm[FLEN_COMMENT];
+    char card[FLEN_CARD];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (ffgkey(fptr, keyname, valstring, oldcomm, status) > 0)
+        return(*status);                               /* get old comment */
+
+    ffd2f(value, decim, valstring, status);   /* convert value to a string */
+
+    if (!comm || comm[0] == '&')  /* preserve the current comment string */
+        ffmkky(keyname, valstring, oldcomm, card, status);
+    else
+        ffmkky(keyname, valstring, comm, card, status);
+
+    ffmkey(fptr, card, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffmkyd(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           double value,      /* I - keyword value      */
+           int decim,         /* I - no of decimals     */
+           char *comm,        /* I - keyword comment    */
+           int *status)       /* IO - error status      */
+{
+    char valstring[FLEN_VALUE];
+    char oldcomm[FLEN_COMMENT];
+    char card[FLEN_CARD];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (ffgkey(fptr, keyname, valstring, oldcomm, status) > 0)
+        return(*status);                               /* get old comment */
+
+    ffd2e(value, decim, valstring, status);   /* convert value to a string */
+
+    if (!comm || comm[0] == '&')  /* preserve the current comment string */
+        ffmkky(keyname, valstring, oldcomm, card, status);
+    else
+        ffmkky(keyname, valstring, comm, card, status);
+
+    ffmkey(fptr, card, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffmkfc(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           float *value,      /* I - keyword value      */
+           int decim,         /* I - no of decimals     */
+           char *comm,        /* I - keyword comment    */
+           int *status)       /* IO - error status      */
+{
+    char valstring[FLEN_VALUE], tmpstring[FLEN_VALUE];
+    char oldcomm[FLEN_COMMENT];
+    char card[FLEN_CARD];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (ffgkey(fptr, keyname, valstring, oldcomm, status) > 0)
+        return(*status);                               /* get old comment */
+
+    strcpy(valstring, "(" );
+    ffr2f(value[0], decim, tmpstring, status); /* convert to string */
+    strcat(valstring, tmpstring);
+    strcat(valstring, ", ");
+    ffr2f(value[1], decim, tmpstring, status); /* convert to string */
+    strcat(valstring, tmpstring);
+    strcat(valstring, ")");
+
+    if (!comm || comm[0] == '&')  /* preserve the current comment string */
+        ffmkky(keyname, valstring, oldcomm, card, status);
+    else
+        ffmkky(keyname, valstring, comm, card, status);
+
+    ffmkey(fptr, card, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffmkyc(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           float *value,      /* I - keyword value      */
+           int decim,         /* I - no of decimals     */
+           char *comm,        /* I - keyword comment    */
+           int *status)       /* IO - error status      */
+{
+    char valstring[FLEN_VALUE], tmpstring[FLEN_VALUE];
+    char oldcomm[FLEN_COMMENT];
+    char card[FLEN_CARD];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (ffgkey(fptr, keyname, valstring, oldcomm, status) > 0)
+        return(*status);                               /* get old comment */
+
+    strcpy(valstring, "(" );
+    ffr2e(value[0], decim, tmpstring, status); /* convert to string */
+    strcat(valstring, tmpstring);
+    strcat(valstring, ", ");
+    ffr2e(value[1], decim, tmpstring, status); /* convert to string */
+    strcat(valstring, tmpstring);
+    strcat(valstring, ")");
+
+    if (!comm || comm[0] == '&')  /* preserve the current comment string */
+        ffmkky(keyname, valstring, oldcomm, card, status);
+    else
+        ffmkky(keyname, valstring, comm, card, status);
+
+    ffmkey(fptr, card, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffmkfm(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           double *value,     /* I - keyword value      */
+           int decim,         /* I - no of decimals     */
+           char *comm,        /* I - keyword comment    */
+           int *status)       /* IO - error status      */
+{
+    char valstring[FLEN_VALUE], tmpstring[FLEN_VALUE];
+    char oldcomm[FLEN_COMMENT];
+    char card[FLEN_CARD];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (ffgkey(fptr, keyname, valstring, oldcomm, status) > 0)
+        return(*status);                               /* get old comment */
+
+    strcpy(valstring, "(" );
+    ffd2f(value[0], decim, tmpstring, status); /* convert to string */
+    strcat(valstring, tmpstring);
+    strcat(valstring, ", ");
+    ffd2f(value[1], decim, tmpstring, status); /* convert to string */
+    strcat(valstring, tmpstring);
+    strcat(valstring, ")");
+
+    if (!comm || comm[0] == '&')  /* preserve the current comment string */
+        ffmkky(keyname, valstring, oldcomm, card, status);
+    else
+        ffmkky(keyname, valstring, comm, card, status);
+
+    ffmkey(fptr, card, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffmkym(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           double *value,     /* I - keyword value      */
+           int decim,         /* I - no of decimals     */
+           char *comm,        /* I - keyword comment    */
+           int *status)       /* IO - error status      */
+{
+    char valstring[FLEN_VALUE], tmpstring[FLEN_VALUE];
+    char oldcomm[FLEN_COMMENT];
+    char card[FLEN_CARD];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (ffgkey(fptr, keyname, valstring, oldcomm, status) > 0)
+        return(*status);                               /* get old comment */
+
+    strcpy(valstring, "(" );
+    ffd2e(value[0], decim, tmpstring, status); /* convert to string */
+    strcat(valstring, tmpstring);
+    strcat(valstring, ", ");
+    ffd2e(value[1], decim, tmpstring, status); /* convert to string */
+    strcat(valstring, tmpstring);
+    strcat(valstring, ")");
+
+    if (!comm || comm[0] == '&')  /* preserve the current comment string */
+        ffmkky(keyname, valstring, oldcomm, card, status);
+    else
+        ffmkky(keyname, valstring, comm, card, status);
+
+    ffmkey(fptr, card, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffikyu(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           char *comm,        /* I - keyword comment    */
+           int *status)       /* IO - error status      */
+/*
+  Insert a null-valued keyword and comment into the FITS header.  
+*/
+{
+    char valstring[FLEN_VALUE];
+    char card[FLEN_CARD];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    strcpy(valstring," ");  /* create a dummy value string */
+    ffmkky(keyname, valstring, comm, card, status);  /* construct the keyword*/
+    ffikey(fptr, card, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffikys(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           char *value,       /* I - keyword value      */
+           char *comm,        /* I - keyword comment    */
+           int *status)       /* IO - error status      */
+{
+    char valstring[FLEN_VALUE];
+    char card[FLEN_CARD];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    ffs2c(value, valstring, status);   /* put quotes around the string */
+    ffmkky(keyname, valstring, comm, card, status);  /* construct the keyword*/
+    ffikey(fptr, card, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffikls( fitsfile *fptr,     /* I - FITS file pointer        */
+            const char *keyname,      /* I - name of keyword to write */
+            char *value,        /* I - keyword value            */
+            char *comm,         /* I - keyword comment          */
+            int  *status)       /* IO - error status            */
+/*
+  Insert a long string keyword.  This routine supports the
+  HEASARC long string convention and can insert arbitrarily long string
+  keyword values.  The value is continued over multiple keywords that
+  have the name COMTINUE without an equal sign in column 9 of the card.
+  This routine also supports simple string keywords which are less than
+  69 characters in length.
+*/
+{
+    char valstring[FLEN_VALUE];
+    char card[FLEN_CARD], tmpkeyname[FLEN_CARD];
+    char tstring[FLEN_VALUE], *cptr;
+    int next, remain, vlen, nquote, nchar, namelen, contin, tstatus = -1;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    /*  construct the new keyword, and insert into header */
+    remain = strlen(value);    /* number of characters to write out */
+    next = 0;                  /* pointer to next character to write */
+    
+    /* count the number of single quote characters in the string */
+    nquote = 0;
+    cptr = strchr(value, '\'');   /* search for quote character */
+
+    while (cptr)  /* search for quote character */
+    {
+        nquote++;            /*  increment no. of quote characters  */
+        cptr++;              /*  increment pointer to next character */
+        cptr = strchr(cptr, '\'');  /* search for another quote char */
+    }
+
+
+    strncpy(tmpkeyname, keyname, 80);
+    tmpkeyname[80] = '\0';
+    
+    cptr = tmpkeyname;
+    while(*cptr == ' ')   /* skip over leading spaces in name */
+        cptr++;
+
+    /* determine the number of characters that will fit on the line */
+    /* Note: each quote character is expanded to 2 quotes */
+
+    namelen = strlen(cptr);
+    if (namelen <= 8 && (fftkey(cptr, &tstatus) <= 0) )
+    {
+        /* This a normal 8-character FITS keyword */
+        nchar = 68 - nquote; /*  max of 68 chars fit in a FITS string value */
+    }
+    else
+    {
+        /* This a HIERARCH keyword */
+        if (FSTRNCMP(cptr, "HIERARCH ", 9) && 
+            FSTRNCMP(cptr, "hierarch ", 9))
+            nchar = 66 - nquote - namelen;
+        else
+            nchar = 75 - nquote - namelen;  /* don't count 'HIERARCH' twice */
+
+    }
+
+    contin = 0;
+    while (remain > 0)
+    {
+        strncpy(tstring, &value[next], nchar); /* copy string to temp buff */
+        tstring[nchar] = '\0';
+        ffs2c(tstring, valstring, status);  /* put quotes around the string */
+
+        if (remain > nchar)   /* if string is continued, put & as last char */
+        {
+            vlen = strlen(valstring);
+            nchar -= 1;        /* outputting one less character now */
+
+            if (valstring[vlen-2] != '\'')
+                valstring[vlen-2] = '&';  /*  over write last char with &  */
+            else
+            { /* last char was a pair of single quotes, so over write both */
+                valstring[vlen-3] = '&';
+                valstring[vlen-1] = '\0';
+            }
+        }
+
+        if (contin)           /* This is a CONTINUEd keyword */
+        {
+           ffmkky("CONTINUE", valstring, comm, card, status); /* make keyword */
+           strncpy(&card[8], "   ",  2);  /* overwrite the '=' */
+        }
+        else
+        {
+           ffmkky(keyname, valstring, comm, card, status);  /* make keyword */
+        }
+
+        ffikey(fptr, card, status);  /* insert the keyword */
+       
+        contin = 1;
+        remain -= nchar;
+        next  += nchar;
+        nchar = 68 - nquote;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffikyl(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           int value,         /* I - keyword value      */
+           char *comm,        /* I - keyword comment    */
+           int *status)       /* IO - error status      */
+{
+    char valstring[FLEN_VALUE];
+    char card[FLEN_CARD];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    ffl2c(value, valstring, status);   /* convert logical to 'T' or 'F' */
+    ffmkky(keyname, valstring, comm, card, status);  /* construct the keyword*/
+    ffikey(fptr, card, status);  /* write the keyword*/
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffikyj(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           LONGLONG value,    /* I - keyword value      */
+           char *comm,        /* I - keyword comment    */
+           int *status)       /* IO - error status      */
+{
+    char valstring[FLEN_VALUE];
+    char card[FLEN_CARD];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    ffi2c(value, valstring, status);   /* convert to formatted string */
+    ffmkky(keyname, valstring, comm, card, status);  /* construct the keyword*/
+    ffikey(fptr, card, status);  /* write the keyword*/
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffikyf(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           float value,       /* I - keyword value      */
+           int decim,         /* I - no of decimals     */
+           char *comm,        /* I - keyword comment    */ 
+           int *status)       /* IO - error status      */
+{
+    char valstring[FLEN_VALUE];
+    char card[FLEN_CARD];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    ffr2f(value, decim, valstring, status);   /* convert to formatted string */
+    ffmkky(keyname, valstring, comm, card, status);  /* construct the keyword*/
+    ffikey(fptr, card, status);  /* write the keyword*/
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffikye(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           float value,       /* I - keyword value      */
+           int decim,         /* I - no of decimals     */
+           char *comm,        /* I - keyword comment    */ 
+           int *status)       /* IO - error status      */
+{
+    char valstring[FLEN_VALUE];
+    char card[FLEN_CARD];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    ffr2e(value, decim, valstring, status);   /* convert to formatted string */
+    ffmkky(keyname, valstring, comm, card, status);  /* construct the keyword*/
+    ffikey(fptr, card, status);  /* write the keyword*/
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffikyg(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           double value,      /* I - keyword value      */
+           int decim,         /* I - no of decimals     */
+           char *comm,        /* I - keyword comment    */ 
+           int *status)       /* IO - error status      */
+{
+    char valstring[FLEN_VALUE];
+    char card[FLEN_CARD];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    ffd2f(value, decim, valstring, status);   /* convert to formatted string */
+    ffmkky(keyname, valstring, comm, card, status);  /* construct the keyword*/
+    ffikey(fptr, card, status);  /* write the keyword*/
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffikyd(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           double value,      /* I - keyword value      */
+           int decim,         /* I - no of decimals     */
+           char *comm,        /* I - keyword comment    */ 
+           int *status)       /* IO - error status      */
+{
+    char valstring[FLEN_VALUE];
+    char card[FLEN_CARD];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    ffd2e(value, decim, valstring, status);   /* convert to formatted string */
+    ffmkky(keyname, valstring, comm, card, status);  /* construct the keyword*/
+    ffikey(fptr, card, status);  /* write the keyword*/
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffikfc(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           float *value,      /* I - keyword value      */
+           int decim,         /* I - no of decimals     */
+           char *comm,        /* I - keyword comment    */ 
+           int *status)       /* IO - error status      */
+{
+    char valstring[FLEN_VALUE], tmpstring[FLEN_VALUE];
+    char card[FLEN_CARD];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    strcpy(valstring, "(" );
+    ffr2f(value[0], decim, tmpstring, status); /* convert to string */
+    strcat(valstring, tmpstring);
+    strcat(valstring, ", ");
+    ffr2f(value[1], decim, tmpstring, status); /* convert to string */
+    strcat(valstring, tmpstring);
+    strcat(valstring, ")");
+
+    ffmkky(keyname, valstring, comm, card, status);  /* construct the keyword*/
+    ffikey(fptr, card, status);  /* write the keyword*/
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffikyc(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           float *value,      /* I - keyword value      */
+           int decim,         /* I - no of decimals     */
+           char *comm,        /* I - keyword comment    */ 
+           int *status)       /* IO - error status      */
+{
+    char valstring[FLEN_VALUE], tmpstring[FLEN_VALUE];
+    char card[FLEN_CARD];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    strcpy(valstring, "(" );
+    ffr2e(value[0], decim, tmpstring, status); /* convert to string */
+    strcat(valstring, tmpstring);
+    strcat(valstring, ", ");
+    ffr2e(value[1], decim, tmpstring, status); /* convert to string */
+    strcat(valstring, tmpstring);
+    strcat(valstring, ")");
+
+    ffmkky(keyname, valstring, comm, card, status);  /* construct the keyword*/
+    ffikey(fptr, card, status);  /* write the keyword*/
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffikfm(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           double *value,     /* I - keyword value      */
+           int decim,         /* I - no of decimals     */
+           char *comm,        /* I - keyword comment    */ 
+           int *status)       /* IO - error status      */
+{
+    char valstring[FLEN_VALUE], tmpstring[FLEN_VALUE];
+    char card[FLEN_CARD];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+
+    strcpy(valstring, "(" );
+    ffd2f(value[0], decim, tmpstring, status); /* convert to string */
+    strcat(valstring, tmpstring);
+    strcat(valstring, ", ");
+    ffd2f(value[1], decim, tmpstring, status); /* convert to string */
+    strcat(valstring, tmpstring);
+    strcat(valstring, ")");
+
+    ffmkky(keyname, valstring, comm, card, status);  /* construct the keyword*/
+    ffikey(fptr, card, status);  /* write the keyword*/
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffikym(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           double *value,     /* I - keyword value      */
+           int decim,         /* I - no of decimals     */
+           char *comm,        /* I - keyword comment    */ 
+           int *status)       /* IO - error status      */
+{
+    char valstring[FLEN_VALUE], tmpstring[FLEN_VALUE];
+    char card[FLEN_CARD];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    strcpy(valstring, "(" );
+    ffd2e(value[0], decim, tmpstring, status); /* convert to string */
+    strcat(valstring, tmpstring);
+    strcat(valstring, ", ");
+    ffd2e(value[1], decim, tmpstring, status); /* convert to string */
+    strcat(valstring, tmpstring);
+    strcat(valstring, ")");
+
+    ffmkky(keyname, valstring, comm, card, status);  /* construct the keyword*/
+    ffikey(fptr, card, status);  /* write the keyword*/
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffirec(fitsfile *fptr,    /* I - FITS file pointer              */
+           int nkey,          /* I - position to insert new keyword */
+           char *card,        /* I - card string value              */
+           int *status)       /* IO - error status                  */
+{
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    ffmaky(fptr, nkey, status);  /* move to insert position */
+    ffikey(fptr, card, status);  /* insert the keyword card */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffikey(fitsfile *fptr,    /* I - FITS file pointer  */
+           char *card,        /* I - card string value  */
+           int *status)       /* IO - error status      */
+/*
+  insert a keyword at the position of (fptr->Fptr)->nextkey
+*/
+{
+    int ii, len, nshift;
+    long nblocks;
+    LONGLONG bytepos;
+    char *inbuff, *outbuff, *tmpbuff, buff1[FLEN_CARD], buff2[FLEN_CARD];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    if ( ((fptr->Fptr)->datastart - (fptr->Fptr)->headend) == 80) /* only room for END card */
+    {
+        nblocks = 1;
+        if (ffiblk(fptr, nblocks, 0, status) > 0) /* add new 2880-byte block*/
+            return(*status);  
+    }
+
+    /* no. keywords to shift */
+    nshift= (int) (( (fptr->Fptr)->headend - (fptr->Fptr)->nextkey ) / 80); 
+
+    strncpy(buff2, card, 80);     /* copy card to output buffer */
+    buff2[80] = '\0';
+
+    len = strlen(buff2);
+
+    /* silently replace any illegal characters with a space */
+    for (ii=0; ii < len; ii++)   
+        if (buff2[ii] < ' ' || buff2[ii] > 126) buff2[ii] = ' ';
+
+    for (ii=len; ii < 80; ii++)   /* fill buffer with spaces if necessary */
+        buff2[ii] = ' ';
+
+    for (ii=0; ii < 8; ii++)       /* make sure keyword name is uppercase */
+        buff2[ii] = toupper(buff2[ii]);
+
+    fftkey(buff2, status);        /* test keyword name contains legal chars */
+
+/*  no need to do this any more, since any illegal characters have been removed
+    fftrec(buff2, status);  */      /* test rest of keyword for legal chars   */
+
+    inbuff = buff1;
+    outbuff = buff2;
+
+    bytepos = (fptr->Fptr)->nextkey;           /* pointer to next keyword in header */
+    ffmbyt(fptr, bytepos, REPORT_EOF, status);
+
+    for (ii = 0; ii < nshift; ii++) /* shift each keyword down one position */
+    {
+        ffgbyt(fptr, 80, inbuff, status);   /* read the current keyword */
+
+        ffmbyt(fptr, bytepos, REPORT_EOF, status); /* move back */
+        ffpbyt(fptr, 80, outbuff, status);  /* overwrite with other buffer */
+
+        tmpbuff = inbuff;   /* swap input and output buffers */
+        inbuff = outbuff;
+        outbuff = tmpbuff;
+
+        bytepos += 80;
+    }
+
+    ffpbyt(fptr, 80, outbuff, status);  /* write the final keyword */
+
+    (fptr->Fptr)->headend += 80; /* increment the position of the END keyword */
+    (fptr->Fptr)->nextkey += 80; /* increment the pointer to next keyword */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffdkey(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *keyname,     /* I - keyword name       */
+           int *status)       /* IO - error status      */
+/*
+  delete a specified header keyword
+*/
+{
+    int keypos, len;
+    char valstring[FLEN_VALUE], comm[FLEN_COMMENT], value[FLEN_VALUE];
+    char message[FLEN_ERRMSG];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (ffgkey(fptr, keyname, valstring, comm, status) > 0) /* read keyword */
+    {
+        sprintf(message, "Could not find the %s keyword to delete (ffdkey)",
+                keyname);
+        ffpmsg(message);
+        return(*status);
+    }
+
+    /* calc position of keyword in header */
+    keypos = (int) ((((fptr->Fptr)->nextkey) - ((fptr->Fptr)->headstart[(fptr->Fptr)->curhdu])) / 80);
+
+    ffdrec(fptr, keypos, status);  /* delete the keyword */
+
+    /* check for string value which may be continued over multiple keywords */
+    ffc2s(valstring, value, status);   /* remove quotes and trailing spaces */
+    len = strlen(value);
+
+    while (len && value[len - 1] == '&')  /* ampersand used as continuation char */
+    {
+        ffgcnt(fptr, value, status);
+        if (*value)
+        {
+            ffdrec(fptr, keypos, status);  /* delete the keyword */
+            len = strlen(value);
+        }
+        else   /* a null valstring indicates no continuation */
+            len = 0;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffdstr(fitsfile *fptr,    /* I - FITS file pointer  */
+           const char *string,     /* I - keyword name       */
+           int *status)       /* IO - error status      */
+/*
+  delete a specified header keyword containing the input string
+*/
+{
+    int keypos, len;
+    char valstring[FLEN_VALUE], comm[FLEN_COMMENT], value[FLEN_VALUE];
+    char card[FLEN_CARD], message[FLEN_ERRMSG];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (ffgstr(fptr, string, card, status) > 0) /* read keyword */
+    {
+        sprintf(message, "Could not find the %s keyword to delete (ffdkey)",
+                string);
+        ffpmsg(message);
+        return(*status);
+    }
+
+    /* calc position of keyword in header */
+    keypos = (int) ((((fptr->Fptr)->nextkey) - ((fptr->Fptr)->headstart[(fptr->Fptr)->curhdu])) / 80);
+
+    ffdrec(fptr, keypos, status);  /* delete the keyword */
+
+        /* check for string value which may be continued over multiple keywords */
+    ffpsvc(card, valstring, comm, status);
+    ffc2s(valstring, value, status);   /* remove quotes and trailing spaces */
+    len = strlen(value);
+
+    while (len && value[len - 1] == '&')  /* ampersand used as continuation char */
+    {
+        ffgcnt(fptr, value, status);
+        if (*value)
+        {
+            ffdrec(fptr, keypos, status);  /* delete the keyword */
+            len = strlen(value);
+        }
+        else   /* a null valstring indicates no continuation */
+            len = 0;
+    }
+    return(*status);
+}/*--------------------------------------------------------------------------*/
+int ffdrec(fitsfile *fptr,   /* I - FITS file pointer  */
+           int keypos,       /* I - position in header of keyword to delete */
+           int *status)      /* IO - error status      */
+/*
+  Delete a header keyword at position keypos. The 1st keyword is at keypos=1.
+*/
+{
+    int ii, nshift;
+    LONGLONG bytepos;
+    char *inbuff, *outbuff, *tmpbuff, buff1[81], buff2[81];
+    char message[FLEN_ERRMSG];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    if (keypos < 1 ||
+        keypos > (fptr->Fptr)->headend - (fptr->Fptr)->headstart[(fptr->Fptr)->curhdu] / 80 )
+        return(*status = KEY_OUT_BOUNDS);
+
+    (fptr->Fptr)->nextkey = (fptr->Fptr)->headstart[(fptr->Fptr)->curhdu] + (keypos - 1) * 80;
+
+    nshift=(int) (( (fptr->Fptr)->headend - (fptr->Fptr)->nextkey ) / 80); /* no. keywords to shift */
+
+    if (nshift <= 0)
+    {
+        sprintf(message, "Cannot delete keyword number %d.  It does not exist.",
+                keypos);
+        ffpmsg(message);
+        return(*status = KEY_OUT_BOUNDS);
+    }
+
+    bytepos = (fptr->Fptr)->headend - 80;  /* last keyword in header */  
+
+    /* construct a blank keyword */
+    strcpy(buff2, "                                        ");
+    strcat(buff2, "                                        ");
+    inbuff  = buff1;
+    outbuff = buff2;
+    for (ii = 0; ii < nshift; ii++) /* shift each keyword up one position */
+    {
+
+        ffmbyt(fptr, bytepos, REPORT_EOF, status);
+        ffgbyt(fptr, 80, inbuff, status);   /* read the current keyword */
+
+        ffmbyt(fptr, bytepos, REPORT_EOF, status);
+        ffpbyt(fptr, 80, outbuff, status);  /* overwrite with next keyword */
+
+        tmpbuff = inbuff;   /* swap input and output buffers */
+        inbuff = outbuff;
+        outbuff = tmpbuff;
+
+        bytepos -= 80;
+    }
+
+    (fptr->Fptr)->headend -= 80; /* decrement the position of the END keyword */
+    return(*status);
+}
+
diff --git a/src/plugins/cfitsio/pliocomp.c b/src/plugins/cfitsio/pliocomp.c
new file mode 100644
index 0000000..682599f
--- /dev/null
+++ b/src/plugins/cfitsio/pliocomp.c
@@ -0,0 +1,331 @@
+/* stdlib is needed for the abs function */
+#include <stdlib.h>
+/*
+   The following prototype code was provided by Doug Tody, NRAO, for
+   performing conversion between pixel arrays and line lists.  The
+   compression technique is used in IRAF.
+*/
+int pl_p2li (int *pxsrc, int xs, short *lldst, int npix);
+int pl_l2pi (short *ll_src, int xs, int *px_dst, int npix);
+
+
+/*
+ * PL_P2L -- Convert a pixel array to a line list.  The length of the list is
+ * returned as the function value.
+ *
+ * Translated from the SPP version using xc -f, f2c.  8Sep99 DCT.
+ */
+
+#ifndef min
+#define min(a,b)        (((a)<(b))?(a):(b))
+#endif
+#ifndef max
+#define max(a,b)        (((a)>(b))?(a):(b))
+#endif
+
+int pl_p2li (int *pxsrc, int xs, short *lldst, int npix)
+/* int *pxsrc;                      input pixel array */
+/* int xs;                          starting index in pxsrc (?) */
+/* short *lldst;                    encoded line list */
+/* int npix;                        number of pixels to convert */
+{
+    /* System generated locals */
+    int ret_val, i__1, i__2, i__3;
+
+    /* Local variables */
+    int zero, v, x1, hi, ip, dv, xe, np, op, iz, nv, pv, nz;
+
+    /* Parameter adjustments */
+    --lldst;
+    --pxsrc;
+
+    /* Function Body */
+    if (! (npix <= 0)) {
+        goto L110;
+    }
+    ret_val = 0;
+    goto L100;
+L110:
+    lldst[3] = -100;
+    lldst[2] = 7;
+    lldst[1] = 0;
+    lldst[6] = 0;
+    lldst[7] = 0;
+    xe = xs + npix - 1;
+    op = 8;
+    zero = 0;
+/* Computing MAX */
+    i__1 = zero, i__2 = pxsrc[xs];
+    pv = max(i__1,i__2);
+    x1 = xs;
+    iz = xs;
+    hi = 1;
+    i__1 = xe;
+    for (ip = xs; ip <= i__1; ++ip) {
+        if (! (ip < xe)) {
+            goto L130;
+        }
+/* Computing MAX */
+        i__2 = zero, i__3 = pxsrc[ip + 1];
+        nv = max(i__2,i__3);
+        if (! (nv == pv)) {
+            goto L140;
+        }
+        goto L120;
+L140:
+        if (! (pv == 0)) {
+            goto L150;
+        }
+        pv = nv;
+        x1 = ip + 1;
+        goto L120;
+L150:
+        goto L131;
+L130:
+        if (! (pv == 0)) {
+            goto L160;
+        }
+        x1 = xe + 1;
+L160:
+L131:
+        np = ip - x1 + 1;
+        nz = x1 - iz;
+        if (! (pv > 0)) {
+            goto L170;
+        }
+        dv = pv - hi;
+        if (! (dv != 0)) {
+            goto L180;
+        }
+        hi = pv;
+        if (! (abs(dv) > 4095)) {
+            goto L190;
+        }
+        lldst[op] = (short) ((pv & 4095) + 4096);
+        ++op;
+        lldst[op] = (short) (pv / 4096);
+        ++op;
+        goto L191;
+L190:
+        if (! (dv < 0)) {
+            goto L200;
+        }
+        lldst[op] = (short) (-dv + 12288);
+        goto L201;
+L200:
+        lldst[op] = (short) (dv + 8192);
+L201:
+        ++op;
+        if (! (np == 1 && nz == 0)) {
+            goto L210;
+        }
+        v = lldst[op - 1];
+        lldst[op - 1] = (short) (v | 16384);
+        goto L91;
+L210:
+L191:
+L180:
+L170:
+        if (! (nz > 0)) {
+            goto L220;
+        }
+L230:
+        if (! (nz > 0)) {
+            goto L232;
+        }
+        lldst[op] = (short) min(4095,nz);
+        ++op;
+/* L231: */
+        nz += -4095;
+        goto L230;
+L232:
+        if (! (np == 1 && pv > 0)) {
+            goto L240;
+        }
+        lldst[op - 1] = (short) (lldst[op - 1] + 20481);
+        goto L91;
+L240:
+L220:
+L250:
+        if (! (np > 0)) {
+            goto L252;
+        }
+        lldst[op] = (short) (min(4095,np) + 16384);
+        ++op;
+/* L251: */
+        np += -4095;
+        goto L250;
+L252:
+L91:
+        x1 = ip + 1;
+        iz = x1;
+        pv = nv;
+L120:
+        ;
+    }
+/* L121: */
+    lldst[4] = (short) ((op - 1) % 32768);
+    lldst[5] = (short) ((op - 1) / 32768);
+    ret_val = op - 1;
+    goto L100;
+L100:
+    return ret_val;
+} /* plp2li_ */
+
+/*
+ * PL_L2PI -- Translate a PLIO line list into an integer pixel array.
+ * The number of pixels output (always npix) is returned as the function
+ * value.
+ *
+ * Translated from the SPP version using xc -f, f2c.  8Sep99 DCT.
+ */
+
+int pl_l2pi (short *ll_src, int xs, int *px_dst, int npix)
+/* short *ll_src;                   encoded line list */
+/* int xs;                          starting index in ll_src */
+/* int *px_dst;                    output pixel array */
+/* int npix;                       number of pixels to convert */
+{
+    /* System generated locals */
+    int ret_val, i__1, i__2;
+
+    /* Local variables */
+    int data, sw0001, otop, i__, lllen, i1, i2, x1, x2, ip, xe, np,
+             op, pv, opcode, llfirt;
+    int skipwd;
+
+    /* Parameter adjustments */
+    --px_dst;
+    --ll_src;
+
+    /* Function Body */
+    if (! (ll_src[3] > 0)) {
+        goto L110;
+    }
+    lllen = ll_src[3];
+    llfirt = 4;
+    goto L111;
+L110:
+    lllen = (ll_src[5] << 15) + ll_src[4];
+    llfirt = ll_src[2] + 1;
+L111:
+    if (! (npix <= 0 || lllen <= 0)) {
+        goto L120;
+    }
+    ret_val = 0;
+    goto L100;
+L120:
+    xe = xs + npix - 1;
+    skipwd = 0;
+    op = 1;
+    x1 = 1;
+    pv = 1;
+    i__1 = lllen;
+    for (ip = llfirt; ip <= i__1; ++ip) {
+        if (! skipwd) {
+            goto L140;
+        }
+        skipwd = 0;
+        goto L130;
+L140:
+        opcode = ll_src[ip] / 4096;
+        data = ll_src[ip] & 4095;
+        sw0001 = opcode;
+        goto L150;
+L160:
+        x2 = x1 + data - 1;
+        i1 = max(x1,xs);
+        i2 = min(x2,xe);
+        np = i2 - i1 + 1;
+        if (! (np > 0)) {
+            goto L170;
+        }
+        otop = op + np - 1;
+        if (! (opcode == 4)) {
+            goto L180;
+        }
+        i__2 = otop;
+        for (i__ = op; i__ <= i__2; ++i__) {
+            px_dst[i__] = pv;
+/* L190: */
+        }
+/* L191: */
+        goto L181;
+L180:
+        i__2 = otop;
+        for (i__ = op; i__ <= i__2; ++i__) {
+            px_dst[i__] = 0;
+/* L200: */
+        }
+/* L201: */
+        if (! (opcode == 5 && i2 == x2)) {
+            goto L210;
+        }
+        px_dst[otop] = pv;
+L210:
+L181:
+        op = otop + 1;
+L170:
+        x1 = x2 + 1;
+        goto L151;
+L220:
+        pv = (ll_src[ip + 1] << 12) + data;
+        skipwd = 1;
+        goto L151;
+L230:
+        pv += data;
+        goto L151;
+L240:
+        pv -= data;
+        goto L151;
+L250:
+        pv += data;
+        goto L91;
+L260:
+        pv -= data;
+L91:
+        if (! (x1 >= xs && x1 <= xe)) {
+            goto L270;
+        }
+        px_dst[op] = pv;
+        ++op;
+L270:
+        ++x1;
+        goto L151;
+L150:
+        ++sw0001;
+        if (sw0001 < 1 || sw0001 > 8) {
+            goto L151;
+        }
+        switch ((int)sw0001) {
+            case 1:  goto L160;
+            case 2:  goto L220;
+            case 3:  goto L230;
+            case 4:  goto L240;
+            case 5:  goto L160;
+            case 6:  goto L160;
+            case 7:  goto L250;
+            case 8:  goto L260;
+        }
+L151:
+        if (! (x1 > xe)) {
+            goto L280;
+        }
+        goto L131;
+L280:
+L130:
+        ;
+    }
+L131:
+    i__1 = npix;
+    for (i__ = op; i__ <= i__1; ++i__) {
+        px_dst[i__] = 0;
+/* L290: */
+    }
+/* L291: */
+    ret_val = npix;
+    goto L100;
+L100:
+    return ret_val;
+} /* pll2pi_ */
+
diff --git a/src/plugins/cfitsio/putcol.c b/src/plugins/cfitsio/putcol.c
new file mode 100644
index 0000000..0bfc927
--- /dev/null
+++ b/src/plugins/cfitsio/putcol.c
@@ -0,0 +1,1929 @@
+/*  This file, putcol.c, contains routines that write data elements to     */
+/*  a FITS image or table. These are the generic routines.                 */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+
+#include <string.h>
+#include <stdlib.h>
+#include <limits.h>
+#include "fitsio2.h"
+
+/*--------------------------------------------------------------------------*/
+int ffppx(  fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  datatype,   /* I - datatype of the value                   */
+            long  *firstpix, /* I - coord of  first pixel to write(1 based) */
+            LONGLONG  nelem,     /* I - number of values to write               */
+            void  *array,    /* I - array of values that are written        */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of pixels to the primary array.  The datatype of the
+  input array is defined by the 2nd argument. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being written). 
+  
+  This routine is simillar to ffppr, except it supports writing to 
+  large images with more than 2**31 pixels.
+*/
+{
+    int naxis, ii;
+    long group = 1;
+    LONGLONG firstelem, dimsize = 1, naxes[9];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    /* get the size of the image */
+    ffgidm(fptr, &naxis, status);
+    ffgiszll(fptr, 9, naxes, status);
+
+    firstelem = 0;
+    for (ii=0; ii < naxis; ii++)
+    {
+        firstelem += ((firstpix[ii] - 1) * dimsize);
+        dimsize *= naxes[ii];
+    }
+    firstelem++;
+
+    if (datatype == TBYTE)
+    {
+      ffpprb(fptr, group, firstelem, nelem, (unsigned char *) array, status);
+    }
+    else if (datatype == TSBYTE)
+    {
+      ffpprsb(fptr, group, firstelem, nelem, (signed char *) array, status);
+    }
+    else if (datatype == TUSHORT)
+    {
+      ffpprui(fptr, group, firstelem, nelem, (unsigned short *) array,
+              status);
+    }
+    else if (datatype == TSHORT)
+    {
+      ffppri(fptr, group, firstelem, nelem, (short *) array, status);
+    }
+    else if (datatype == TUINT)
+    {
+      ffppruk(fptr, group, firstelem, nelem, (unsigned int *) array, status);
+    }
+    else if (datatype == TINT)
+    {
+      ffpprk(fptr, group, firstelem, nelem, (int *) array, status);
+    }
+    else if (datatype == TULONG)
+    {
+      ffppruj(fptr, group, firstelem, nelem, (unsigned long *) array, status);
+    }
+    else if (datatype == TLONG)
+    {
+      ffpprj(fptr, group, firstelem, nelem, (long *) array, status);
+    }
+    else if (datatype == TLONGLONG)
+    {
+      ffpprjj(fptr, group, firstelem, nelem, (LONGLONG *) array, status);
+    }
+    else if (datatype == TFLOAT)
+    {
+      ffppre(fptr, group, firstelem, nelem, (float *) array, status);
+    }
+    else if (datatype == TDOUBLE)
+    {
+      ffpprd(fptr, group, firstelem, nelem, (double *) array, status);
+    }
+    else
+      *status = BAD_DATATYPE;
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffppxll(  fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  datatype,   /* I - datatype of the value                   */
+            LONGLONG  *firstpix, /* I - coord of  first pixel to write(1 based) */
+            LONGLONG  nelem,     /* I - number of values to write               */
+            void  *array,    /* I - array of values that are written        */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of pixels to the primary array.  The datatype of the
+  input array is defined by the 2nd argument. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being written). 
+  
+  This routine is simillar to ffppr, except it supports writing to 
+  large images with more than 2**31 pixels.
+*/
+{
+    int naxis, ii;
+    long group = 1;
+    LONGLONG firstelem, dimsize = 1, naxes[9];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    /* get the size of the image */
+    ffgidm(fptr, &naxis, status);
+    ffgiszll(fptr, 9, naxes, status);
+
+    firstelem = 0;
+    for (ii=0; ii < naxis; ii++)
+    {
+        firstelem += ((firstpix[ii] - 1) * dimsize);
+        dimsize *= naxes[ii];
+    }
+    firstelem++;
+
+    if (datatype == TBYTE)
+    {
+      ffpprb(fptr, group, firstelem, nelem, (unsigned char *) array, status);
+    }
+    else if (datatype == TSBYTE)
+    {
+      ffpprsb(fptr, group, firstelem, nelem, (signed char *) array, status);
+    }
+    else if (datatype == TUSHORT)
+    {
+      ffpprui(fptr, group, firstelem, nelem, (unsigned short *) array,
+              status);
+    }
+    else if (datatype == TSHORT)
+    {
+      ffppri(fptr, group, firstelem, nelem, (short *) array, status);
+    }
+    else if (datatype == TUINT)
+    {
+      ffppruk(fptr, group, firstelem, nelem, (unsigned int *) array, status);
+    }
+    else if (datatype == TINT)
+    {
+      ffpprk(fptr, group, firstelem, nelem, (int *) array, status);
+    }
+    else if (datatype == TULONG)
+    {
+      ffppruj(fptr, group, firstelem, nelem, (unsigned long *) array, status);
+    }
+    else if (datatype == TLONG)
+    {
+      ffpprj(fptr, group, firstelem, nelem, (long *) array, status);
+    }
+    else if (datatype == TLONGLONG)
+    {
+      ffpprjj(fptr, group, firstelem, nelem, (LONGLONG *) array, status);
+    }
+    else if (datatype == TFLOAT)
+    {
+      ffppre(fptr, group, firstelem, nelem, (float *) array, status);
+    }
+    else if (datatype == TDOUBLE)
+    {
+      ffpprd(fptr, group, firstelem, nelem, (double *) array, status);
+    }
+    else
+      *status = BAD_DATATYPE;
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffppxn(  fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  datatype,   /* I - datatype of the value                   */
+            long  *firstpix, /* I - first vector element to write(1 = 1st)  */
+            LONGLONG  nelem,     /* I - number of values to write               */
+            void  *array,    /* I - array of values that are written        */
+            void  *nulval,   /* I - pointer to the null value               */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of values to the primary array.  The datatype of the
+  input array is defined by the 2nd argument. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being written).
+
+  This routine supports writing to large images with
+  more than 2**31 pixels.
+*/
+{
+    int naxis, ii;
+    long group = 1;
+    LONGLONG firstelem, dimsize = 1, naxes[9];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (nulval == NULL)  /* null value not defined? */
+    {
+        ffppx(fptr, datatype, firstpix, nelem, array, status);
+        return(*status);
+    }
+
+    /* get the size of the image */
+    ffgidm(fptr, &naxis, status);
+    ffgiszll(fptr, 9, naxes, status);
+
+    firstelem = 0;
+    for (ii=0; ii < naxis; ii++)
+    {
+        firstelem += ((firstpix[ii] - 1) * dimsize);
+        dimsize *= naxes[ii];
+    }
+    firstelem++;
+
+    if (datatype == TBYTE)
+    {
+      ffppnb(fptr, group, firstelem, nelem, (unsigned char *) array, 
+             *(unsigned char *) nulval, status);
+    }
+    else if (datatype == TSBYTE)
+    {
+      ffppnsb(fptr, group, firstelem, nelem, (signed char *) array, 
+             *(signed char *) nulval, status);
+    }
+    else if (datatype == TUSHORT)
+    {
+      ffppnui(fptr, group, firstelem, nelem, (unsigned short *) array,
+              *(unsigned short *) nulval,status);
+    }
+    else if (datatype == TSHORT)
+    {
+      ffppni(fptr, group, firstelem, nelem, (short *) array,
+             *(short *) nulval, status);
+    }
+    else if (datatype == TUINT)
+    {
+      ffppnuk(fptr, group, firstelem, nelem, (unsigned int *) array,
+             *(unsigned int *) nulval, status);
+    }
+    else if (datatype == TINT)
+    {
+      ffppnk(fptr, group, firstelem, nelem, (int *) array,
+             *(int *) nulval, status);
+    }
+    else if (datatype == TULONG)
+    {
+      ffppnuj(fptr, group, firstelem, nelem, (unsigned long *) array,
+              *(unsigned long *) nulval,status);
+    }
+    else if (datatype == TLONG)
+    {
+      ffppnj(fptr, group, firstelem, nelem, (long *) array,
+             *(long *) nulval, status);
+    }
+    else if (datatype == TLONGLONG)
+    {
+      ffppnjj(fptr, group, firstelem, nelem, (LONGLONG *) array,
+             *(LONGLONG *) nulval, status);
+    }
+    else if (datatype == TFLOAT)
+    {
+      ffppne(fptr, group, firstelem, nelem, (float *) array,
+             *(float *) nulval, status);
+    }
+    else if (datatype == TDOUBLE)
+    {
+      ffppnd(fptr, group, firstelem, nelem, (double *) array,
+             *(double *) nulval, status);
+    }
+    else
+      *status = BAD_DATATYPE;
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffppxnll(  fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  datatype,   /* I - datatype of the value                   */
+            LONGLONG  *firstpix, /* I - first vector element to write(1 = 1st)  */
+            LONGLONG  nelem,     /* I - number of values to write               */
+            void  *array,    /* I - array of values that are written        */
+            void  *nulval,   /* I - pointer to the null value               */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of values to the primary array.  The datatype of the
+  input array is defined by the 2nd argument. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being written).
+
+  This routine supports writing to large images with
+  more than 2**31 pixels.
+*/
+{
+    int naxis, ii;
+    long  group = 1;
+    LONGLONG firstelem, dimsize = 1, naxes[9];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (nulval == NULL)  /* null value not defined? */
+    {
+        ffppxll(fptr, datatype, firstpix, nelem, array, status);
+        return(*status);
+    }
+
+    /* get the size of the image */
+    ffgidm(fptr, &naxis, status);
+    ffgiszll(fptr, 9, naxes, status);
+
+    firstelem = 0;
+    for (ii=0; ii < naxis; ii++)
+    {
+        firstelem += ((firstpix[ii] - 1) * dimsize);
+        dimsize *= naxes[ii];
+    }
+    firstelem++;
+
+    if (datatype == TBYTE)
+    {
+      ffppnb(fptr, group, firstelem, nelem, (unsigned char *) array, 
+             *(unsigned char *) nulval, status);
+    }
+    else if (datatype == TSBYTE)
+    {
+      ffppnsb(fptr, group, firstelem, nelem, (signed char *) array, 
+             *(signed char *) nulval, status);
+    }
+    else if (datatype == TUSHORT)
+    {
+      ffppnui(fptr, group, firstelem, nelem, (unsigned short *) array,
+              *(unsigned short *) nulval,status);
+    }
+    else if (datatype == TSHORT)
+    {
+      ffppni(fptr, group, firstelem, nelem, (short *) array,
+             *(short *) nulval, status);
+    }
+    else if (datatype == TUINT)
+    {
+      ffppnuk(fptr, group, firstelem, nelem, (unsigned int *) array,
+             *(unsigned int *) nulval, status);
+    }
+    else if (datatype == TINT)
+    {
+      ffppnk(fptr, group, firstelem, nelem, (int *) array,
+             *(int *) nulval, status);
+    }
+    else if (datatype == TULONG)
+    {
+      ffppnuj(fptr, group, firstelem, nelem, (unsigned long *) array,
+              *(unsigned long *) nulval,status);
+    }
+    else if (datatype == TLONG)
+    {
+      ffppnj(fptr, group, firstelem, nelem, (long *) array,
+             *(long *) nulval, status);
+    }
+    else if (datatype == TLONGLONG)
+    {
+      ffppnjj(fptr, group, firstelem, nelem, (LONGLONG *) array,
+             *(LONGLONG *) nulval, status);
+    }
+    else if (datatype == TFLOAT)
+    {
+      ffppne(fptr, group, firstelem, nelem, (float *) array,
+             *(float *) nulval, status);
+    }
+    else if (datatype == TDOUBLE)
+    {
+      ffppnd(fptr, group, firstelem, nelem, (double *) array,
+             *(double *) nulval, status);
+    }
+    else
+      *status = BAD_DATATYPE;
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffppr(  fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  datatype,   /* I - datatype of the value                   */
+            LONGLONG  firstelem, /* I - first vector element to write(1 = 1st)  */
+            LONGLONG  nelem,     /* I - number of values to write               */
+            void  *array,    /* I - array of values that are written        */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of values to the primary array.  The datatype of the
+  input array is defined by the 2nd argument. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being written).
+
+*/
+{
+    long group = 1;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (datatype == TBYTE)
+    {
+      ffpprb(fptr, group, firstelem, nelem, (unsigned char *) array, status);
+    }
+    else if (datatype == TSBYTE)
+    {
+      ffpprsb(fptr, group, firstelem, nelem, (signed char *) array, status);
+    }
+    else if (datatype == TUSHORT)
+    {
+      ffpprui(fptr, group, firstelem, nelem, (unsigned short *) array,
+              status);
+    }
+    else if (datatype == TSHORT)
+    {
+      ffppri(fptr, group, firstelem, nelem, (short *) array, status);
+    }
+    else if (datatype == TUINT)
+    {
+      ffppruk(fptr, group, firstelem, nelem, (unsigned int *) array, status);
+    }
+    else if (datatype == TINT)
+    {
+      ffpprk(fptr, group, firstelem, nelem, (int *) array, status);
+    }
+    else if (datatype == TULONG)
+    {
+      ffppruj(fptr, group, firstelem, nelem, (unsigned long *) array, status);
+    }
+    else if (datatype == TLONG)
+    {
+      ffpprj(fptr, group, firstelem, nelem, (long *) array, status);
+    }
+    else if (datatype == TLONGLONG)
+    {
+      ffpprjj(fptr, group, firstelem, nelem, (LONGLONG *) array, status);
+    }
+    else if (datatype == TFLOAT)
+    {
+      ffppre(fptr, group, firstelem, nelem, (float *) array, status);
+    }
+    else if (datatype == TDOUBLE)
+    {
+      ffpprd(fptr, group, firstelem, nelem, (double *) array, status);
+    }
+    else
+      *status = BAD_DATATYPE;
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffppn(  fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  datatype,   /* I - datatype of the value                   */
+            LONGLONG  firstelem, /* I - first vector element to write(1 = 1st)  */
+            LONGLONG  nelem,     /* I - number of values to write               */
+            void  *array,    /* I - array of values that are written        */
+            void  *nulval,   /* I - pointer to the null value               */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of values to the primary array.  The datatype of the
+  input array is defined by the 2nd argument. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being written).
+
+*/
+{
+    long group = 1;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (nulval == NULL)  /* null value not defined? */
+    {
+        ffppr(fptr, datatype, firstelem, nelem, array, status);
+        return(*status);
+    }
+
+    if (datatype == TBYTE)
+    {
+      ffppnb(fptr, group, firstelem, nelem, (unsigned char *) array, 
+             *(unsigned char *) nulval, status);
+    }
+    else if (datatype == TSBYTE)
+    {
+      ffppnsb(fptr, group, firstelem, nelem, (signed char *) array, 
+             *(signed char *) nulval, status);
+    }
+    else if (datatype == TUSHORT)
+    {
+      ffppnui(fptr, group, firstelem, nelem, (unsigned short *) array,
+              *(unsigned short *) nulval,status);
+    }
+    else if (datatype == TSHORT)
+    {
+      ffppni(fptr, group, firstelem, nelem, (short *) array,
+             *(short *) nulval, status);
+    }
+    else if (datatype == TUINT)
+    {
+      ffppnuk(fptr, group, firstelem, nelem, (unsigned int *) array,
+             *(unsigned int *) nulval, status);
+    }
+    else if (datatype == TINT)
+    {
+      ffppnk(fptr, group, firstelem, nelem, (int *) array,
+             *(int *) nulval, status);
+    }
+    else if (datatype == TULONG)
+    {
+      ffppnuj(fptr, group, firstelem, nelem, (unsigned long *) array,
+              *(unsigned long *) nulval,status);
+    }
+    else if (datatype == TLONG)
+    {
+      ffppnj(fptr, group, firstelem, nelem, (long *) array,
+             *(long *) nulval, status);
+    }
+    else if (datatype == TLONGLONG)
+    {
+      ffppnjj(fptr, group, firstelem, nelem, (LONGLONG *) array,
+             *(LONGLONG *) nulval, status);
+    }
+    else if (datatype == TFLOAT)
+    {
+      ffppne(fptr, group, firstelem, nelem, (float *) array,
+             *(float *) nulval, status);
+    }
+    else if (datatype == TDOUBLE)
+    {
+      ffppnd(fptr, group, firstelem, nelem, (double *) array,
+             *(double *) nulval, status);
+    }
+    else
+      *status = BAD_DATATYPE;
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpss(  fitsfile *fptr,   /* I - FITS file pointer                       */
+            int  datatype,    /* I - datatype of the value                   */
+            long *blc,        /* I - 'bottom left corner' of the subsection  */
+            long *trc ,       /* I - 'top right corner' of the subsection    */
+            void *array,      /* I - array of values that are written        */
+            int  *status)     /* IO - error status                           */
+/*
+  Write a section of values to the primary array. The datatype of the
+  input array is defined by the 2nd argument.  Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being written).
+
+  This routine supports writing to large images with
+  more than 2**31 pixels.
+*/
+{
+    int naxis;
+    long naxes[9];
+
+    if (*status > 0)   /* inherit input status value if > 0 */
+        return(*status);
+
+    /* get the size of the image */
+    ffgidm(fptr, &naxis, status);
+    ffgisz(fptr, 9, naxes, status);
+
+    if (datatype == TBYTE)
+    {
+        ffpssb(fptr, 1, naxis, naxes, blc, trc,
+               (unsigned char *) array, status);
+    }
+    else if (datatype == TSBYTE)
+    {
+        ffpsssb(fptr, 1, naxis, naxes, blc, trc,
+               (signed char *) array, status);
+    }
+    else if (datatype == TUSHORT)
+    {
+        ffpssui(fptr, 1, naxis, naxes, blc, trc,
+               (unsigned short *) array, status);
+    }
+    else if (datatype == TSHORT)
+    {
+        ffpssi(fptr, 1, naxis, naxes, blc, trc,
+               (short *) array, status);
+    }
+    else if (datatype == TUINT)
+    {
+        ffpssuk(fptr, 1, naxis, naxes, blc, trc,
+               (unsigned int *) array, status);
+    }
+    else if (datatype == TINT)
+    {
+        ffpssk(fptr, 1, naxis, naxes, blc, trc,
+               (int *) array, status);
+    }
+    else if (datatype == TULONG)
+    {
+        ffpssuj(fptr, 1, naxis, naxes, blc, trc,
+               (unsigned long *) array, status);
+    }
+    else if (datatype == TLONG)
+    {
+        ffpssj(fptr, 1, naxis, naxes, blc, trc,
+               (long *) array, status);
+    }
+    else if (datatype == TLONGLONG)
+    {
+        ffpssjj(fptr, 1, naxis, naxes, blc, trc,
+               (LONGLONG *) array, status);
+    }    else if (datatype == TFLOAT)
+    {
+        ffpsse(fptr, 1, naxis, naxes, blc, trc,
+               (float *) array, status);
+    }
+    else if (datatype == TDOUBLE)
+    {
+        ffpssd(fptr, 1, naxis, naxes, blc, trc,
+               (double *) array, status);
+    }
+    else
+      *status = BAD_DATATYPE;
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpcl(  fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  datatype,   /* I - datatype of the value                   */
+            int  colnum,     /* I - number of column to write (1 = 1st col) */
+            LONGLONG  firstrow,  /* I - first row to write (1 = 1st row)        */
+            LONGLONG  firstelem, /* I - first vector element to write (1 = 1st) */
+            LONGLONG  nelem,     /* I - number of elements to write             */
+            void  *array,    /* I - array of values that are written        */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of values to a table column.  The datatype of the
+  input array is defined by the 2nd argument. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS column is not the same as the array being written).
+
+*/
+{
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (datatype == TBIT)
+    {
+      ffpclx(fptr, colnum, firstrow, (long) firstelem, (long) nelem, (char *) array, 
+             status);
+    }
+    else if (datatype == TBYTE)
+    {
+      ffpclb(fptr, colnum, firstrow, firstelem, nelem, (unsigned char *) array,
+             status);
+    }
+    else if (datatype == TSBYTE)
+    {
+      ffpclsb(fptr, colnum, firstrow, firstelem, nelem, (signed char *) array,
+             status);
+    }
+    else if (datatype == TUSHORT)
+    {
+      ffpclui(fptr, colnum, firstrow, firstelem, nelem, 
+             (unsigned short *) array, status);
+    }
+    else if (datatype == TSHORT)
+    {
+      ffpcli(fptr, colnum, firstrow, firstelem, nelem, (short *) array,
+             status);
+    }
+    else if (datatype == TUINT)
+    {
+      ffpcluk(fptr, colnum, firstrow, firstelem, nelem, (unsigned int *) array,
+               status);
+    }
+    else if (datatype == TINT)
+    {
+      ffpclk(fptr, colnum, firstrow, firstelem, nelem, (int *) array,
+               status);
+    }
+    else if (datatype == TULONG)
+    {
+      ffpcluj(fptr, colnum, firstrow, firstelem, nelem, (unsigned long *) array,
+              status);
+    }
+    else if (datatype == TLONG)
+    {
+      ffpclj(fptr, colnum, firstrow, firstelem, nelem, (long *) array,
+             status);
+    }
+    else if (datatype == TLONGLONG)
+    {
+      ffpcljj(fptr, colnum, firstrow, firstelem, nelem, (LONGLONG *) array,
+             status);
+    }
+    else if (datatype == TFLOAT)
+    {
+      ffpcle(fptr, colnum, firstrow, firstelem, nelem, (float *) array,
+             status);
+    }
+    else if (datatype == TDOUBLE)
+    {
+      ffpcld(fptr, colnum, firstrow, firstelem, nelem, (double *) array,
+             status);
+    }
+    else if (datatype == TCOMPLEX)
+    {
+      ffpcle(fptr, colnum, firstrow, (firstelem - 1) * 2 + 1, nelem * 2,
+             (float *) array, status);
+    }
+    else if (datatype == TDBLCOMPLEX)
+    {
+      ffpcld(fptr, colnum, firstrow, (firstelem - 1) * 2 + 1, nelem * 2,
+             (double *) array, status);
+    }
+    else if (datatype == TLOGICAL)
+    {
+      ffpcll(fptr, colnum, firstrow, firstelem, nelem, (char *) array,
+             status);
+    }
+    else if (datatype == TSTRING)
+    {
+      ffpcls(fptr, colnum, firstrow, firstelem, nelem, (char **) array,
+             status);
+    }
+    else
+      *status = BAD_DATATYPE;
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpcn(  fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  datatype,   /* I - datatype of the value                   */
+            int  colnum,     /* I - number of column to write (1 = 1st col) */
+            LONGLONG  firstrow,  /* I - first row to write (1 = 1st row)        */
+            LONGLONG  firstelem, /* I - first vector element to write (1 = 1st) */
+            LONGLONG  nelem,     /* I - number of elements to write             */
+            void  *array,    /* I - array of values that are written        */
+            void  *nulval,   /* I - pointer to the null value               */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of values to a table column.  The datatype of the
+  input array is defined by the 2nd argument. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS column is not the same as the array being written).
+
+*/
+{
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (nulval == NULL)  /* null value not defined? */
+    {
+        ffpcl(fptr, datatype, colnum, firstrow, firstelem, nelem, array,
+              status);
+        return(*status);
+    }
+
+    if (datatype == TBYTE)
+    {
+      ffpcnb(fptr, colnum, firstrow, firstelem, nelem, (unsigned char *) array,
+            *(unsigned char *) nulval, status);
+    }
+    else if (datatype == TSBYTE)
+    {
+      ffpcnsb(fptr, colnum, firstrow, firstelem, nelem, (signed char *) array,
+            *(signed char *) nulval, status);
+    }
+    else if (datatype == TUSHORT)
+    {
+     ffpcnui(fptr, colnum, firstrow, firstelem, nelem, (unsigned short *) array,
+             *(unsigned short *) nulval, status);
+    }
+    else if (datatype == TSHORT)
+    {
+      ffpcni(fptr, colnum, firstrow, firstelem, nelem, (short *) array,
+             *(unsigned short *) nulval, status);
+    }
+    else if (datatype == TUINT)
+    {
+      ffpcnuk(fptr, colnum, firstrow, firstelem, nelem, (unsigned int *) array,
+             *(unsigned int *) nulval, status);
+    }
+    else if (datatype == TINT)
+    {
+      ffpcnk(fptr, colnum, firstrow, firstelem, nelem, (int *) array,
+             *(int *) nulval, status);
+    }
+    else if (datatype == TULONG)
+    {
+      ffpcnuj(fptr, colnum, firstrow, firstelem, nelem, (unsigned long *) array,
+              *(unsigned long *) nulval, status);
+    }
+    else if (datatype == TLONG)
+    {
+      ffpcnj(fptr, colnum, firstrow, firstelem, nelem, (long *) array,
+             *(long *) nulval, status);
+    }
+    else if (datatype == TLONGLONG)
+    {
+      ffpcnjj(fptr, colnum, firstrow, firstelem, nelem, (LONGLONG *) array,
+             *(LONGLONG *) nulval, status);
+    }
+    else if (datatype == TFLOAT)
+    {
+      ffpcne(fptr, colnum, firstrow, firstelem, nelem, (float *) array,
+             *(float *) nulval, status);
+    }
+    else if (datatype == TDOUBLE)
+    {
+      ffpcnd(fptr, colnum, firstrow, firstelem, nelem, (double *) array,
+             *(double *) nulval, status);
+    }
+    else if (datatype == TCOMPLEX)
+    {
+      ffpcne(fptr, colnum, firstrow, (firstelem - 1) * 2 + 1, nelem * 2,
+             (float *) array, *(float *) nulval, status);
+    }
+    else if (datatype == TDBLCOMPLEX)
+    {
+      ffpcnd(fptr, colnum, firstrow, (firstelem - 1) * 2 + 1, nelem * 2,
+             (double *) array, *(double *) nulval, status);
+    }
+    else if (datatype == TLOGICAL)
+    {
+      ffpcnl(fptr, colnum, firstrow, firstelem, nelem, (char *) array,
+             *(char *) nulval, status);
+    }
+    else if (datatype == TSTRING)
+    {
+      ffpcns(fptr, colnum, firstrow, firstelem, nelem, (char **) array,
+             (char *) nulval, status);
+    }
+    else
+      *status = BAD_DATATYPE;
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_iter_set_by_name(iteratorCol *col, /* I - iterator col structure */
+           fitsfile *fptr,  /* I - FITS file pointer                      */
+           char *colname,   /* I - column name                            */
+           int datatype,    /* I - column datatype                        */
+           int iotype)      /* I - InputCol, InputOutputCol, or OutputCol */
+/*
+  set all the parameters for an iterator column, by column name
+*/
+{
+    col->fptr = fptr;
+    strcpy(col->colname, colname);
+    col->colnum = 0;  /* set column number undefined since name is given */
+    col->datatype = datatype;
+    col->iotype = iotype;
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int fits_iter_set_by_num(iteratorCol *col, /* I - iterator column structure */
+           fitsfile *fptr,  /* I - FITS file pointer                      */
+           int colnum,      /* I - column number                          */
+           int datatype,    /* I - column datatype                        */
+           int iotype)      /* I - InputCol, InputOutputCol, or OutputCol */
+/*
+  set all the parameters for an iterator column, by column number
+*/
+{
+    col->fptr = fptr;
+    col->colnum = colnum; 
+    col->datatype = datatype;
+    col->iotype = iotype;
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int fits_iter_set_file(iteratorCol *col, /* I - iterator column structure   */
+           fitsfile *fptr)   /* I - FITS file pointer                      */
+/*
+  set iterator column parameter
+*/
+{
+    col->fptr = fptr;
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int fits_iter_set_colname(iteratorCol *col, /* I - iterator col structure  */
+           char *colname)    /* I - column name                            */
+/*
+  set iterator column parameter
+*/
+{
+    strcpy(col->colname, colname);
+    col->colnum = 0;  /* set column number undefined since name is given */
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int fits_iter_set_colnum(iteratorCol *col, /* I - iterator column structure */
+           int colnum)       /* I - column number                          */
+/*
+  set iterator column parameter
+*/
+{
+    col->colnum = colnum; 
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int fits_iter_set_datatype(iteratorCol *col, /* I - iterator col structure */
+           int datatype)    /* I - column datatype                        */
+/*
+  set iterator column parameter
+*/
+{
+    col->datatype = datatype;
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+int fits_iter_set_iotype(iteratorCol *col, /* I - iterator column structure */
+           int iotype)       /* I - InputCol, InputOutputCol, or OutputCol */
+/*
+  set iterator column parameter
+*/
+{
+    col->iotype = iotype;
+    return(0);
+}
+/*--------------------------------------------------------------------------*/
+fitsfile * fits_iter_get_file(iteratorCol *col) /* I -iterator col structure */
+/*
+  get iterator column parameter
+*/
+{
+     return(col->fptr);
+}
+/*--------------------------------------------------------------------------*/
+char * fits_iter_get_colname(iteratorCol *col) /* I -iterator col structure */
+/*
+  get iterator column parameter
+*/
+{
+    return(col->colname);
+}
+/*--------------------------------------------------------------------------*/
+int fits_iter_get_colnum(iteratorCol *col) /* I - iterator column structure */
+/*
+  get iterator column parameter
+*/
+{
+    return(col->colnum);
+}
+/*--------------------------------------------------------------------------*/
+int fits_iter_get_datatype(iteratorCol *col) /* I - iterator col structure */
+/*
+  get iterator column parameter
+*/
+{
+    return(col->datatype);
+}
+/*--------------------------------------------------------------------------*/
+int fits_iter_get_iotype(iteratorCol *col) /* I - iterator column structure */
+/*
+  get iterator column parameter
+*/
+{
+     return(col->iotype);
+}
+/*--------------------------------------------------------------------------*/
+void * fits_iter_get_array(iteratorCol *col) /* I - iterator col structure */
+/*
+  get iterator column parameter
+*/
+{
+     return(col->array);
+}
+/*--------------------------------------------------------------------------*/
+long fits_iter_get_tlmin(iteratorCol *col) /* I - iterator column structure */
+/*
+  get iterator column parameter
+*/
+{
+     return(col->tlmin);
+}
+/*--------------------------------------------------------------------------*/
+long fits_iter_get_tlmax(iteratorCol *col) /* I - iterator column structure */
+/*
+  get iterator column parameter
+*/
+{
+     return(col->tlmax);
+}
+/*--------------------------------------------------------------------------*/
+long fits_iter_get_repeat(iteratorCol *col) /* I - iterator col structure */
+/*
+  get iterator column parameter
+*/
+{
+     return(col->repeat);
+}
+/*--------------------------------------------------------------------------*/
+char * fits_iter_get_tunit(iteratorCol *col) /* I - iterator col structure */
+/*
+  get iterator column parameter
+*/
+{
+    return(col->tunit);
+}
+/*--------------------------------------------------------------------------*/
+char * fits_iter_get_tdisp(iteratorCol *col) /* I -iterator col structure   */
+/*
+  get iterator column parameter
+*/
+{
+    return(col->tdisp);
+}
+/*--------------------------------------------------------------------------*/
+int ffiter(int n_cols,
+           iteratorCol *cols,
+           long offset,
+           long n_per_loop,
+           int (*work_fn)(long total_n,
+                          long offset,
+                          long first_n,
+                          long n_values,
+                          int n_cols,
+                          iteratorCol *cols,
+                          void *userPointer),
+           void *userPointer,
+           int *status)
+/*
+   The iterator function.  This function will pass the specified
+   columns from a FITS table or pixels from a FITS image to the 
+   user-supplied function.  Depending on the size of the table
+   or image, only a subset of the rows or pixels may be passed to the
+   function on each call, in which case the function will be called
+   multiple times until all the rows or pixels have been processed.
+*/
+{
+    typedef struct  /* structure to store the column null value */
+    {  
+        int      nullsize;    /* length of the null value, in bytes */
+        union {   /*  default null value for the column */
+            char   *stringnull;
+            unsigned char   charnull;
+            signed char scharnull;
+            int    intnull;
+            short  shortnull;
+            long   longnull;
+            unsigned int   uintnull;
+            unsigned short ushortnull;
+            unsigned long  ulongnull;
+            float  floatnull;
+            double doublenull;
+	    LONGLONG longlongnull;
+        } null;
+    } colNulls;
+
+    void *dataptr, *defaultnull;
+    colNulls *col;
+    int ii, jj, tstatus, naxis, bitpix;
+    int typecode, hdutype, jtype, type, anynul, nfiles, nbytes;
+    long totaln, nleft, frow, felement, n_optimum, i_optimum, ntodo;
+    long rept, rowrept, width, tnull, naxes[9] = {1,1,1,1,1,1,1,1,1}, groups;
+    double zeros = 0.;
+    char message[FLEN_ERRMSG], keyname[FLEN_KEYWORD], nullstr[FLEN_VALUE];
+    char **stringptr, *nullptr, *cptr;
+
+    if (*status > 0)
+        return(*status);
+
+    if (n_cols  < 0 || n_cols > 999 )
+    {
+        ffpmsg("Illegal number of columms (ffiter)");
+        return(*status = BAD_COL_NUM);  /* negative number of columns */
+    }
+
+    /*------------------------------------------------------------*/
+    /* Make sure column numbers and datatypes are in legal range  */
+    /* and column numbers and datatypes are legal.                */ 
+    /* Also fill in other parameters in the column structure.     */
+    /*------------------------------------------------------------*/
+
+    ffghdt(cols[0].fptr, &hdutype, status);  /* type of first HDU */
+
+    for (jj = 0; jj < n_cols; jj++)
+    {
+        /* check that output datatype code value is legal */
+        type = cols[jj].datatype;  
+
+        /* Allow variable length arrays for InputCol and InputOutputCol columns,
+	   but not for OutputCol columns.  Variable length arrays have a
+	   negative type code value. */
+
+        if ((cols[jj].iotype != OutputCol) && (type<0)) {
+            type*=-1;
+        }
+
+        if (type != 0      && type != TBYTE  &&
+            type != TSBYTE && type != TLOGICAL && type != TSTRING &&
+            type != TSHORT && type != TINT     && type != TLONG && 
+            type != TFLOAT && type != TDOUBLE  && type != TCOMPLEX &&
+            type != TULONG && type != TUSHORT  && type != TDBLCOMPLEX &&
+	    type != TLONGLONG )
+        {
+	    if (type < 0) {
+	      sprintf(message,
+              "Variable length array not allowed for output column number %d (ffiter)",
+                    jj + 1);
+	    } else {
+            sprintf(message,
+                   "Illegal datatype for column number %d: %d  (ffiter)",
+                    jj + 1, cols[jj].datatype);
+	    }
+	    
+            ffpmsg(message);
+            return(*status = BAD_DATATYPE);
+        }
+
+        /* initialize TLMINn, TLMAXn, column name, and display format */
+        cols[jj].tlmin = 0;
+        cols[jj].tlmax = 0;
+        cols[jj].tunit[0] = '\0';
+        cols[jj].tdisp[0] = '\0';
+
+        ffghdt(cols[jj].fptr, &jtype, status);  /* get HDU type */
+
+        if (hdutype == IMAGE_HDU) /* operating on FITS images */
+        {
+            if (jtype != IMAGE_HDU)
+            {
+                sprintf(message,
+                "File %d not positioned to an image extension (ffiter)",
+                    jj + 1);
+                return(*status = NOT_IMAGE);
+            }
+
+            /* since this is an image, set a dummy column number = 0 */
+            cols[jj].colnum = 0;
+            strcpy(cols[jj].colname, "IMAGE");  /* dummy name for images */
+
+            tstatus = 0;
+            ffgkys(cols[jj].fptr, "BUNIT", cols[jj].tunit, 0, &tstatus);
+        }
+        else  /* operating on FITS tables */
+        {
+            if (jtype == IMAGE_HDU)
+            {
+                sprintf(message,
+                "File %d not positioned to a table extension (ffiter)",
+                    jj + 1);
+                return(*status = NOT_TABLE);
+            }
+
+            if (cols[jj].colnum < 1)
+            {
+                /* find the column number for the named column */
+                if (ffgcno(cols[jj].fptr, CASEINSEN, cols[jj].colname,
+                           &cols[jj].colnum, status) )
+                {
+                    sprintf(message,
+                      "Column '%s' not found for column number %d  (ffiter)",
+                       cols[jj].colname, jj + 1);
+                    ffpmsg(message);
+                    return(*status);
+                }
+            }
+
+            /* check that the column number is valid */
+            if (cols[jj].colnum < 1 || 
+                cols[jj].colnum > ((cols[jj].fptr)->Fptr)->tfield)
+            {
+                sprintf(message,
+                  "Column %d has illegal table position number: %d  (ffiter)",
+                    jj + 1, cols[jj].colnum);
+                ffpmsg(message);
+                return(*status = BAD_COL_NUM);
+            }
+
+            /* look for column description keywords and update structure */
+            tstatus = 0;
+            ffkeyn("TLMIN", cols[jj].colnum, keyname, &tstatus);
+            ffgkyj(cols[jj].fptr, keyname, &cols[jj].tlmin, 0, &tstatus);
+
+            tstatus = 0;
+            ffkeyn("TLMAX", cols[jj].colnum, keyname, &tstatus);
+            ffgkyj(cols[jj].fptr, keyname, &cols[jj].tlmax, 0, &tstatus);
+
+            tstatus = 0;
+            ffkeyn("TTYPE", cols[jj].colnum, keyname, &tstatus);
+            ffgkys(cols[jj].fptr, keyname, cols[jj].colname, 0, &tstatus);
+            if (tstatus)
+                cols[jj].colname[0] = '\0';
+
+            tstatus = 0;
+            ffkeyn("TUNIT", cols[jj].colnum, keyname, &tstatus);
+            ffgkys(cols[jj].fptr, keyname, cols[jj].tunit, 0, &tstatus);
+
+            tstatus = 0;
+            ffkeyn("TDISP", cols[jj].colnum, keyname, &tstatus);
+            ffgkys(cols[jj].fptr, keyname, cols[jj].tdisp, 0, &tstatus);
+        }
+    }  /* end of loop over all columns */
+
+    /*-----------------------------------------------------------------*/
+    /* use the first file to set the total number of values to process */
+    /*-----------------------------------------------------------------*/
+
+    offset = maxvalue(offset, 0L);  /* make sure offset is legal */
+
+    if (hdutype == IMAGE_HDU)   /* get total number of pixels in the image */
+    {
+      fits_get_img_dim(cols[0].fptr, &naxis, status);
+      fits_get_img_size(cols[0].fptr, 9, naxes, status);
+
+      tstatus = 0;
+      ffgkyj(cols[0].fptr, "GROUPS", &groups, NULL, &tstatus);
+      if (!tstatus && groups && (naxis > 1) && (naxes[0] == 0) )
+      {
+         /* this is a random groups file, with NAXIS1 = 0 */
+         /* Use GCOUNT, the number of groups, as the first multiplier  */
+         /* to calculate the total number of pixels in all the groups. */
+         ffgkyj(cols[0].fptr, "GCOUNT", &totaln, NULL, status);
+
+      }  else {
+         totaln = naxes[0];
+      }
+
+      for (ii = 1; ii < naxis; ii++)
+          totaln *= naxes[ii];
+
+      frow = 1;
+      felement = 1 + offset;
+    }
+    else   /* get total number or rows in the table */
+    {
+      ffgkyj(cols[0].fptr, "NAXIS2", &totaln, 0, status);
+      frow = 1 + offset;
+      felement = 1;
+    }
+
+    /*  adjust total by the input starting offset value */
+    totaln -= offset;
+    totaln = maxvalue(totaln, 0L);   /* don't allow negative number */
+
+    /*------------------------------------------------------------------*/
+    /* Determine number of values to pass to work function on each loop */
+    /*------------------------------------------------------------------*/
+
+    if (n_per_loop == 0)
+    {
+        /* Determine optimum number of values for each iteration.    */
+        /* Look at all the fitsfile pointers to determine the number */
+        /* of unique files.                                          */
+
+        nfiles = 1;
+        ffgrsz(cols[0].fptr, &n_optimum, status);
+
+        for (jj = 1; jj < n_cols; jj++)
+        {
+            for (ii = 0; ii < jj; ii++)
+            {
+                if (cols[ii].fptr == cols[jj].fptr)
+                   break;
+            }
+
+            if (ii == jj)  /* this is a new file */
+            {
+                nfiles++;
+                ffgrsz(cols[jj].fptr, &i_optimum, status);
+                n_optimum = minvalue(n_optimum, i_optimum);
+            }
+        }
+
+        /* divid n_optimum by the number of files that will be processed */
+        n_optimum = n_optimum / nfiles;
+        n_optimum = maxvalue(n_optimum, 1);
+    }
+    else if (n_per_loop < 0)  /* must pass all the values at one time */
+    {
+        n_optimum = totaln;
+    }
+    else /* calling routine specified how many values to pass at a time */
+    {
+        n_optimum = minvalue(n_per_loop, totaln);
+    }
+
+    /*--------------------------------------*/
+    /* allocate work arrays for each column */
+    /* and determine the null pixel value   */
+    /*--------------------------------------*/
+
+    col = calloc(n_cols, sizeof(colNulls) ); /* memory for the null values */
+    if (!col)
+    {
+        ffpmsg("ffiter failed to allocate memory for null values");
+        *status = MEMORY_ALLOCATION;  /* memory allocation failed */
+        return(*status);
+    }
+
+    for (jj = 0; jj < n_cols; jj++)
+    {
+        /* get image or column datatype and vector length */
+        if (hdutype == IMAGE_HDU)   /* get total number of pixels in the image */
+        {
+           fits_get_img_type(cols[jj].fptr, &bitpix, status);
+           switch(bitpix) {
+             case BYTE_IMG:
+                 typecode = TBYTE;
+                 break;
+             case SHORT_IMG:
+                 typecode = TSHORT;
+                 break;
+             case LONG_IMG:
+                 typecode = TLONG;
+                 break;
+             case FLOAT_IMG:
+                 typecode = TFLOAT;
+                 break;
+             case DOUBLE_IMG:
+                 typecode = TDOUBLE;
+                 break;
+             case LONGLONG_IMG:
+                 typecode = TLONGLONG;
+                 break;
+            }
+        }
+        else
+        {
+            if (ffgtcl(cols[jj].fptr, cols[jj].colnum, &typecode, &rept,
+                  &width, status) > 0)
+                goto cleanup;
+		
+	    if (typecode < 0) {  /* if any variable length arrays, then the */ 
+	        n_optimum = 1;   /* must process the table 1 row at a time */
+		
+              /* Allow variable length arrays for InputCol and InputOutputCol columns,
+	       but not for OutputCol columns.  Variable length arrays have a
+	       negative type code value. */
+
+              if (cols[jj].iotype == OutputCol) {
+ 	        sprintf(message,
+                "Variable length array not allowed for output column number %d (ffiter)",
+                    jj + 1);
+                ffpmsg(message);
+                return(*status = BAD_DATATYPE);
+              }
+	   }
+        }
+
+        /* special case where sizeof(long) = 8: use TINT instead of TLONG */
+        if (abs(typecode) == TLONG && sizeof(long) == 8 && sizeof(int) == 4) {
+		if(typecode<0) {
+			typecode = -TINT;
+		} else {
+			typecode = TINT;
+		}
+        }
+
+        /* Special case: interprete 'X' column as 'B' */
+        if (abs(typecode) == TBIT)
+        {
+            typecode  = typecode / TBIT * TBYTE;
+            rept = (rept + 7) / 8;
+        }
+
+        if (cols[jj].datatype == 0)    /* output datatype not specified? */
+        {
+            /* special case if sizeof(long) = 8: use TINT instead of TLONG */
+            if (abs(typecode) == TLONG && sizeof(long) == 8 && sizeof(int) == 4)
+                cols[jj].datatype = TINT;
+            else
+                cols[jj].datatype = abs(typecode);
+        }
+
+        /* calc total number of elements to do on each iteration */
+        if (hdutype == IMAGE_HDU || cols[jj].datatype == TSTRING)
+        {
+            ntodo = n_optimum; 
+            cols[jj].repeat = 1;
+
+            /* get the BLANK keyword value, if it exists */
+            if (abs(typecode) == TBYTE || abs(typecode) == TSHORT || abs(typecode) == TLONG)
+            {
+                tstatus = 0;
+                ffgkyj(cols[jj].fptr, "BLANK", &tnull, 0, &tstatus);
+                if (tstatus)
+                {
+                    tnull = 0L;  /* no null values */
+                }
+            }
+        }
+        else
+        {
+	    if (typecode < 0) 
+	    {
+              /* get max size of the variable length vector; dont't trust the value
+	         given by the TFORM keyword  */
+	      rept = 1;
+	      for (ii = 0; ii < totaln; ii++) {
+		ffgdes(cols[jj].fptr, cols[jj].colnum, frow + ii, &rowrept, NULL, status);
+		
+		rept = maxvalue(rept, rowrept);
+	      }
+            }
+	    
+            ntodo = n_optimum * rept;   /* vector columns */
+            cols[jj].repeat = rept;
+
+            /* get the TNULL keyword value, if it exists */
+            if (abs(typecode) == TBYTE || abs(typecode) == TSHORT || abs(typecode) == TLONG)
+            {
+                tstatus = 0;
+                if (hdutype == ASCII_TBL) /* TNULLn value is a string */
+                {
+                    ffkeyn("TNULL", cols[jj].colnum, keyname, &tstatus);
+                    ffgkys(cols[jj].fptr, keyname, nullstr, 0, &tstatus);
+                    if (tstatus)
+                    {
+                        tnull = 0L; /* keyword doesn't exist; no null values */
+                    }
+                    else
+                    {
+                        cptr = nullstr;
+                        while (*cptr == ' ')  /* skip over leading blanks */
+                           cptr++;
+
+                        if (*cptr == '\0')  /* TNULLn is all blanks? */
+                            tnull = LONG_MIN;
+                        else
+                        {                                                
+                            /* attempt to read TNULLn string as an integer */
+                            ffc2ii(nullstr, &tnull, &tstatus);
+
+                            if (tstatus)
+                                tnull = LONG_MIN;  /* choose smallest value */
+                        }                          /* to represent nulls */
+                    }
+                }
+                else  /* Binary table; TNULLn value is an integer */
+                {
+                    ffkeyn("TNULL", cols[jj].colnum, keyname, &tstatus);
+                    ffgkyj(cols[jj].fptr, keyname, &tnull, 0, &tstatus);
+                    if (tstatus)
+                    {
+                        tnull = 0L; /* keyword doesn't exist; no null values */
+                    }
+                    else if (tnull == 0)
+                    {
+                        /* worst possible case: a value of 0 is used to   */
+                        /* represent nulls in the FITS file.  We have to  */
+                        /* use a non-zero null value here (zero is used to */
+                        /* mean there are no null values in the array) so we */
+                        /* will use the smallest possible integer instead. */
+
+                        tnull = LONG_MIN;  /* choose smallest possible value */
+                    }
+                }
+            }
+        }
+
+        /* Note that the data array starts with 2nd element;  */
+        /* 1st element of the array gives the null data value */
+
+        switch (cols[jj].datatype)
+        {
+         case TBYTE:
+          cols[jj].array = calloc(ntodo + 1, sizeof(char));
+          col[jj].nullsize  = sizeof(char);  /* number of bytes per value */
+
+          if (abs(typecode) == TBYTE || abs(typecode) == TSHORT || abs(typecode) == TLONG)
+          {
+              tnull = minvalue(tnull, 255);
+              tnull = maxvalue(tnull, 0);
+              col[jj].null.charnull = (unsigned char) tnull;
+          }
+          else
+          {
+              col[jj].null.charnull = (unsigned char) 255; /* use 255 as null */
+          }
+          break;
+
+         case TSBYTE:
+          cols[jj].array = calloc(ntodo + 1, sizeof(char));
+          col[jj].nullsize  = sizeof(char);  /* number of bytes per value */
+
+          if (abs(typecode) == TBYTE || abs(typecode) == TSHORT || abs(typecode) == TLONG)
+          {
+              tnull = minvalue(tnull, 127);
+              tnull = maxvalue(tnull, -128);
+              col[jj].null.scharnull = (signed char) tnull;
+          }
+          else
+          {
+              col[jj].null.scharnull = (signed char) -128; /* use -128  null */
+          }
+          break;
+
+         case TSHORT:
+          cols[jj].array = calloc(ntodo + 1, sizeof(short));
+          col[jj].nullsize  = sizeof(short);  /* number of bytes per value */
+
+          if (abs(typecode) == TBYTE || abs(typecode) == TSHORT || abs(typecode) == TLONG)
+          {
+              tnull = minvalue(tnull, SHRT_MAX);
+              tnull = maxvalue(tnull, SHRT_MIN);
+              col[jj].null.shortnull = (short) tnull;
+          }
+          else
+          {
+              col[jj].null.shortnull = SHRT_MIN;  /* use minimum as null */
+          }
+          break;
+
+         case TUSHORT:
+          cols[jj].array = calloc(ntodo + 1, sizeof(unsigned short));
+          col[jj].nullsize  = sizeof(unsigned short);  /* bytes per value */
+
+          if (abs(typecode) == TBYTE || abs(typecode) == TSHORT || abs(typecode) == TLONG)
+          {
+              tnull = minvalue(tnull, (long) USHRT_MAX);
+              tnull = maxvalue(tnull, 0);  /* don't allow negative value */
+              col[jj].null.ushortnull = (unsigned short) tnull;
+          }
+          else
+          {
+              col[jj].null.ushortnull = USHRT_MAX;   /* use maximum null */
+          }
+          break;
+
+         case TINT:
+          cols[jj].array = calloc(sizeof(int), ntodo + 1);
+          col[jj].nullsize  = sizeof(int);  /* number of bytes per value */
+
+          if (abs(typecode) == TBYTE || abs(typecode) == TSHORT || abs(typecode) == TLONG)
+          {
+              tnull = minvalue(tnull, INT_MAX);
+              tnull = maxvalue(tnull, INT_MIN);
+              col[jj].null.intnull = (int) tnull;
+          }
+          else
+          {
+              col[jj].null.intnull = INT_MIN;  /* use minimum as null */
+          }
+          break;
+
+         case TUINT:
+          cols[jj].array = calloc(ntodo + 1, sizeof(unsigned int));
+          col[jj].nullsize  = sizeof(unsigned int);  /* bytes per value */
+
+          if (abs(typecode) == TBYTE || abs(typecode) == TSHORT || abs(typecode) == TLONG)
+          {
+              tnull = minvalue(tnull, INT32_MAX);
+              tnull = maxvalue(tnull, 0);
+              col[jj].null.uintnull = (unsigned int) tnull;
+          }
+          else
+          {
+              col[jj].null.intnull = UINT_MAX;  /* use maximum as null */
+          }
+          break;
+
+         case TLONG:
+          cols[jj].array = calloc(ntodo + 1, sizeof(long));
+          col[jj].nullsize  = sizeof(long);  /* number of bytes per value */
+
+          if (abs(typecode) == TBYTE || abs(typecode) == TSHORT || abs(typecode) == TLONG)
+          {
+              col[jj].null.longnull = tnull;
+          }
+          else
+          {
+              col[jj].null.longnull = LONG_MIN;   /* use minimum as null */
+          }
+          break;
+
+         case TULONG:
+          cols[jj].array = calloc(ntodo + 1, sizeof(unsigned long));
+          col[jj].nullsize  = sizeof(unsigned long);  /* bytes per value */
+
+          if (abs(typecode) == TBYTE || abs(typecode) == TSHORT || abs(typecode) == TLONG)
+          {
+              if (tnull < 0)  /* can't use a negative null value */
+                  col[jj].null.ulongnull = LONG_MAX;
+              else
+                  col[jj].null.ulongnull = (unsigned long) tnull;
+          }
+          else
+          {
+              col[jj].null.ulongnull = LONG_MAX;   /* use maximum as null */
+          }
+          break;
+
+         case TFLOAT:
+          cols[jj].array = calloc(ntodo + 1, sizeof(float));
+          col[jj].nullsize  = sizeof(float);  /* number of bytes per value */
+
+          if (abs(typecode) == TBYTE || abs(typecode) == TSHORT || abs(typecode) == TLONG)
+          {
+              col[jj].null.floatnull = (float) tnull;
+          }
+          else
+          {
+              col[jj].null.floatnull = FLOATNULLVALUE;  /* special value */
+          }
+          break;
+
+         case TCOMPLEX:
+          cols[jj].array = calloc((ntodo * 2) + 1, sizeof(float));
+          col[jj].nullsize  = sizeof(float);  /* number of bytes per value */
+          col[jj].null.floatnull = FLOATNULLVALUE;  /* special value */
+          break;
+
+         case TDOUBLE:
+          cols[jj].array = calloc(ntodo + 1, sizeof(double));
+          col[jj].nullsize  = sizeof(double);  /* number of bytes per value */
+
+          if (abs(typecode) == TBYTE || abs(typecode) == TSHORT || abs(typecode) == TLONG)
+          {
+              col[jj].null.doublenull = (double) tnull;
+          }
+          else
+          {
+              col[jj].null.doublenull = DOUBLENULLVALUE;  /* special value */
+          }
+          break;
+
+         case TDBLCOMPLEX:
+          cols[jj].array = calloc((ntodo * 2) + 1, sizeof(double));
+          col[jj].nullsize  = sizeof(double);  /* number of bytes per value */
+          col[jj].null.doublenull = DOUBLENULLVALUE;  /* special value */
+          break;
+
+         case TSTRING:
+          /* allocate array of pointers to all the strings  */
+	  if( hdutype==ASCII_TBL ) rept = width;
+          stringptr = calloc((ntodo + 1) , sizeof(stringptr));
+          cols[jj].array = stringptr;
+          col[jj].nullsize  = rept + 1;  /* number of bytes per value */
+
+          if (stringptr)
+          {
+            /* allocate string to store the null string value */
+            col[jj].null.stringnull = calloc(rept + 1, sizeof(char) );
+            col[jj].null.stringnull[1] = 1; /* to make sure string != 0 */
+
+            /* allocate big block for the array of table column strings */
+            stringptr[0] = calloc((ntodo + 1) * (rept + 1), sizeof(char) );
+
+            if (stringptr[0])
+            {
+              for (ii = 1; ii <= ntodo; ii++)
+              {   /* pointer to each string */
+                stringptr[ii] = stringptr[ii - 1] + (rept + 1);
+              }
+
+              /* get the TNULL keyword value, if it exists */
+              tstatus = 0;
+              ffkeyn("TNULL", cols[jj].colnum, keyname, &tstatus);
+              ffgkys(cols[jj].fptr, keyname, nullstr, 0, &tstatus);
+              if (!tstatus)
+                  strncat(col[jj].null.stringnull, nullstr, rept);
+            }
+            else
+            {
+              ffpmsg("ffiter failed to allocate memory arrays");
+              *status = MEMORY_ALLOCATION;  /* memory allocation failed */
+              goto cleanup;
+            }
+          }
+          break;
+
+         case TLOGICAL:
+
+          cols[jj].array = calloc(ntodo + 1, sizeof(char));
+          col[jj].nullsize  = sizeof(char);  /* number of bytes per value */
+
+          /* use value = 2 to flag null values in logical columns */
+          col[jj].null.charnull = 2;
+          break;
+
+         case TLONGLONG:
+          cols[jj].array = calloc(ntodo + 1, sizeof(LONGLONG));
+          col[jj].nullsize  = sizeof(LONGLONG);  /* number of bytes per value */
+
+          if (abs(typecode) == TBYTE || abs(typecode) == TSHORT || abs(typecode) == TLONG ||
+	      abs(typecode) == TLONGLONG)
+          {
+              col[jj].null.longlongnull = tnull;
+          }
+          else
+          {
+              col[jj].null.longlongnull = LONGLONG_MIN;   /* use minimum as null */
+          }
+          break;
+
+         default:
+          sprintf(message,
+                  "Column %d datatype currently not supported: %d:  (ffiter)",
+                   jj + 1, cols[jj].datatype);
+          ffpmsg(message);
+          *status = BAD_DATATYPE;
+          goto cleanup;
+
+        }   /* end of switch block */
+
+        /* check that all the arrays were allocated successfully */
+        if (!cols[jj].array)
+        {
+            ffpmsg("ffiter failed to allocate memory arrays");
+            *status = MEMORY_ALLOCATION;  /* memory allocation failed */
+            goto cleanup;
+        }
+    }
+
+    /*--------------------------------------------------*/
+    /* main loop while there are values left to process */
+    /*--------------------------------------------------*/
+
+    nleft = totaln;
+
+    while (nleft)
+    {
+      ntodo = minvalue(nleft, n_optimum); /* no. of values for this loop */
+
+      /*  read input columns from FITS file(s)  */
+      for (jj = 0; jj < n_cols; jj++)
+      {
+        if (cols[jj].iotype != OutputCol)
+        {
+          if (cols[jj].datatype == TSTRING)
+          {
+            stringptr = cols[jj].array;
+            dataptr = stringptr + 1;
+            defaultnull = col[jj].null.stringnull; /* ptr to the null value */
+          }
+          else
+          {
+            dataptr = (char *) cols[jj].array + col[jj].nullsize;
+            defaultnull = &col[jj].null.charnull; /* ptr to the null value */
+          }
+
+          if (hdutype == IMAGE_HDU)   
+          {
+              if (ffgpv(cols[jj].fptr, cols[jj].datatype,
+                    felement, cols[jj].repeat * ntodo, defaultnull,
+                    dataptr,  &anynul, status) > 0)
+              {
+                 break;
+              }
+          }
+          else
+          {
+	      if (ffgtcl(cols[jj].fptr, cols[jj].colnum, &typecode, &rept,&width, status) > 0)
+	          goto cleanup;
+		  
+	      if (typecode<0)
+	      {
+	        /* get size of the variable length vector */
+		ffgdes(cols[jj].fptr, cols[jj].colnum, frow,&cols[jj].repeat, NULL,status);
+	      }
+		
+              if (ffgcv(cols[jj].fptr, cols[jj].datatype, cols[jj].colnum,
+                    frow, felement, cols[jj].repeat * ntodo, defaultnull,
+                    dataptr,  &anynul, status) > 0)
+              {
+                 break;
+              }
+          }
+
+          /* copy the appropriate null value into first array element */
+
+          if (anynul)   /* are there any nulls in the data? */
+          {   
+            if (cols[jj].datatype == TSTRING)
+            {
+              stringptr = cols[jj].array;
+              memcpy(*stringptr, col[jj].null.stringnull, col[jj].nullsize);
+            }
+            else
+            {
+              memcpy(cols[jj].array, defaultnull, col[jj].nullsize);
+            }
+          }
+          else /* no null values so copy zero into first element */
+          {
+            if (cols[jj].datatype == TSTRING)
+            {
+              stringptr = cols[jj].array;
+              memset(*stringptr, 0, col[jj].nullsize);  
+            }
+            else
+            {
+              memset(cols[jj].array, 0, col[jj].nullsize);  
+            }
+          }
+        }
+      }
+
+      if (*status > 0) 
+         break;   /* looks like an error occurred; quit immediately */
+
+      /* call work function */
+
+      if (hdutype == IMAGE_HDU) 
+          *status = work_fn(totaln, offset, felement, ntodo, n_cols, cols,
+                    userPointer);
+      else
+          *status = work_fn(totaln, offset, frow, ntodo, n_cols, cols,
+                    userPointer);
+
+      if (*status > 0 || *status < -1 ) 
+         break;   /* looks like an error occurred; quit immediately */
+
+      /*  write output columns  before quiting if status = -1 */
+      tstatus = 0;
+      for (jj = 0; jj < n_cols; jj++)
+      {
+        if (cols[jj].iotype != InputCol)
+        {
+          if (cols[jj].datatype == TSTRING)
+          {
+            stringptr = cols[jj].array;
+            dataptr = stringptr + 1;
+            nullptr = *stringptr;
+            nbytes = 2;
+          }
+          else
+          {
+            dataptr = (char *) cols[jj].array + col[jj].nullsize;
+            nullptr = (char *) cols[jj].array;
+            nbytes = col[jj].nullsize;
+          }
+
+          if (memcmp(nullptr, &zeros, nbytes) ) 
+          {
+            /* null value flag not zero; must check for and write nulls */
+            if (hdutype == IMAGE_HDU)   
+            {
+                if (ffppn(cols[jj].fptr, cols[jj].datatype, 
+                      felement, cols[jj].repeat * ntodo, dataptr,
+                      nullptr, &tstatus) > 0)
+                break;
+            }
+            else
+            {
+	    	if (ffgtcl(cols[jj].fptr, cols[jj].colnum, &typecode, &rept,&width, status) > 0)
+		    goto cleanup;
+		    
+		if (typecode<0)  /* variable length array colum */
+		{
+		   ffgdes(cols[jj].fptr, cols[jj].colnum, frow,&cols[jj].repeat, NULL,status);
+		}
+
+                if (ffpcn(cols[jj].fptr, cols[jj].datatype, cols[jj].colnum, frow,
+                      felement, cols[jj].repeat * ntodo, dataptr,
+                      nullptr, &tstatus) > 0)
+                break;
+            }
+          }
+          else
+          { 
+            /* no null values; just write the array */
+            if (hdutype == IMAGE_HDU)   
+            {
+                if (ffppr(cols[jj].fptr, cols[jj].datatype,
+                      felement, cols[jj].repeat * ntodo, dataptr,
+                      &tstatus) > 0)
+                break;
+            }
+            else
+            {
+	    	if (ffgtcl(cols[jj].fptr, cols[jj].colnum, &typecode, &rept,&width, status) > 0)
+		    goto cleanup;
+		    
+		if (typecode<0)  /* variable length array column */
+		{
+		   ffgdes(cols[jj].fptr, cols[jj].colnum, frow,&cols[jj].repeat, NULL,status);
+		}
+
+                 if (ffpcl(cols[jj].fptr, cols[jj].datatype, cols[jj].colnum, frow,
+                      felement, cols[jj].repeat * ntodo, dataptr,
+                      &tstatus) > 0)
+                break;
+            }
+          }
+        }
+      }
+
+      if (*status == 0)
+         *status = tstatus;   /* propagate any error status from the writes */
+
+      if (*status) 
+         break;   /* exit on any error */
+
+      nleft -= ntodo;
+
+      if (hdutype == IMAGE_HDU)
+          felement += ntodo;
+      else
+          frow  += ntodo;
+    }
+
+cleanup:
+
+    /*----------------------------------*/
+    /* free work arrays for the columns */
+    /*----------------------------------*/
+
+    for (jj = 0; jj < n_cols; jj++)
+    {
+        if (cols[jj].datatype == TSTRING)
+        {
+            if (cols[jj].array)
+            {
+                stringptr = cols[jj].array;
+                free(*stringptr);     /* free the block of strings */
+                free(col[jj].null.stringnull); /* free the null string */
+            }
+        }
+        if (cols[jj].array)
+            free(cols[jj].array); /* memory for the array of values from the col */
+    }
+    free(col);   /* the structure containing the null values */
+    return(*status);
+}
+
diff --git a/src/plugins/cfitsio/putcolb.c b/src/plugins/cfitsio/putcolb.c
new file mode 100644
index 0000000..120cca8
--- /dev/null
+++ b/src/plugins/cfitsio/putcolb.c
@@ -0,0 +1,1012 @@
+/*  This file, putcolb.c, contains routines that write data elements to    */
+/*  a FITS image or table with char (byte) datatype.                       */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+
+#include <limits.h>
+#include <string.h>
+#include <stdlib.h>
+#include "fitsio2.h"
+
+/*--------------------------------------------------------------------------*/
+int ffpprb( fitsfile *fptr,  /* I - FITS file pointer                       */
+            long  group,     /* I - group to write(1 = 1st group)           */
+            LONGLONG  firstelem, /* I - first vector element to write(1 = 1st)  */
+            LONGLONG  nelem,     /* I - number of values to write               */
+            unsigned char *array, /* I - array of values that are written   */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being written).
+*/
+{
+    long row;
+    unsigned char nullvalue;
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        fits_write_compressed_pixels(fptr, TBYTE, firstelem, nelem,
+            0, array, &nullvalue, status);
+        return(*status);
+    }
+
+    row=maxvalue(1,group);
+
+    ffpclb(fptr, 2, row, firstelem, nelem, array, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffppnb( fitsfile *fptr,  /* I - FITS file pointer                       */
+            long  group,     /* I - group to write(1 = 1st group)           */
+            LONGLONG  firstelem, /* I - first vector element to write(1 = 1st)  */
+            LONGLONG  nelem,     /* I - number of values to write               */
+            unsigned char *array, /* I - array of values that are written   */
+            unsigned char nulval, /* I - undefined pixel value              */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being written).  Any array values
+  that are equal to the value of nulval will be replaced with the null
+  pixel value that is appropriate for this column.
+*/
+{
+    long row;
+    unsigned char nullvalue;
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+
+        nullvalue = nulval;  /* set local variable */
+        fits_write_compressed_pixels(fptr, TBYTE, firstelem, nelem,
+            1, array, &nullvalue, status);
+        return(*status);
+    }
+
+    row=maxvalue(1,group);
+
+    ffpcnb(fptr, 2, row, firstelem, nelem, array, nulval, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffp2db(fitsfile *fptr,   /* I - FITS file pointer                     */
+           long  group,      /* I - group to write(1 = 1st group)         */
+           LONGLONG  ncols,      /* I - number of pixels in each row of array */
+           LONGLONG  naxis1,     /* I - FITS image NAXIS1 value               */
+           LONGLONG  naxis2,     /* I - FITS image NAXIS2 value               */
+           unsigned char *array, /* I - array to be written               */
+           int  *status)     /* IO - error status                         */
+/*
+  Write an entire 2-D array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being written).
+*/
+{
+    /* call the 3D writing routine, with the 3rd dimension = 1 */
+
+    ffp3db(fptr, group, ncols, naxis2, naxis1, naxis2, 1, array, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffp3db(fitsfile *fptr,   /* I - FITS file pointer                     */
+           long  group,      /* I - group to write(1 = 1st group)         */
+           LONGLONG  ncols,      /* I - number of pixels in each row of array */
+           LONGLONG  nrows,      /* I - number of rows in each plane of array */
+           LONGLONG  naxis1,     /* I - FITS image NAXIS1 value               */
+           LONGLONG  naxis2,     /* I - FITS image NAXIS2 value               */
+           LONGLONG  naxis3,     /* I - FITS image NAXIS3 value               */
+           unsigned char *array, /* I - array to be written               */
+           int  *status)     /* IO - error status                         */
+/*
+  Write an entire 3-D cube of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being written).
+*/
+{
+    long tablerow, ii, jj;
+    LONGLONG nfits, narray;
+    long fpixel[3]= {1,1,1}, lpixel[3];
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+           
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+        lpixel[0] = (long) ncols;
+        lpixel[1] = (long) nrows;
+        lpixel[2] = (long) naxis3;
+       
+        fits_write_compressed_img(fptr, TBYTE, fpixel, lpixel,
+            0,  array, NULL, status);
+    
+        return(*status);
+    }
+
+    tablerow=maxvalue(1,group);
+
+    if (ncols == naxis1 && nrows == naxis2)  /* arrays have same size? */
+    {
+      /* all the image pixels are contiguous, so write all at once */
+      ffpclb(fptr, 2, tablerow, 1L, naxis1 * naxis2 * naxis3, array, status);
+      return(*status);
+    }
+
+    if (ncols < naxis1 || nrows < naxis2)
+       return(*status = BAD_DIMEN);
+
+    nfits = 1;   /* next pixel in FITS image to write to */
+    narray = 0;  /* next pixel in input array to be written */
+
+    /* loop over naxis3 planes in the data cube */
+    for (jj = 0; jj < naxis3; jj++)
+    {
+      /* loop over the naxis2 rows in the FITS image, */
+      /* writing naxis1 pixels to each row            */
+
+      for (ii = 0; ii < naxis2; ii++)
+      {
+       if (ffpclb(fptr, 2, tablerow, nfits, naxis1,&array[narray],status) > 0)
+         return(*status);
+
+       nfits += naxis1;
+       narray += ncols;
+      }
+      narray += (nrows - naxis2) * ncols;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpssb(fitsfile *fptr,   /* I - FITS file pointer                       */
+           long  group,      /* I - group to write(1 = 1st group)           */
+           long  naxis,      /* I - number of data axes in array            */
+           long  *naxes,     /* I - size of each FITS axis                  */
+           long  *fpixel,    /* I - 1st pixel in each axis to write (1=1st) */
+           long  *lpixel,    /* I - last pixel in each axis to write        */
+           unsigned char *array, /* I - array to be written                 */
+           int  *status)     /* IO - error status                           */
+/*
+  Write a subsection of pixels to the primary array or image.
+  A subsection is defined to be any contiguous rectangular
+  array of pixels within the n-dimensional FITS data file.
+  Data conversion and scaling will be performed if necessary 
+  (e.g, if the datatype of the FITS array is not the same as
+  the array being written).
+*/
+{
+    long tablerow;
+    LONGLONG fpix[7], dimen[7], astart, pstart;
+    LONGLONG off2, off3, off4, off5, off6, off7;
+    LONGLONG st10, st20, st30, st40, st50, st60, st70;
+    LONGLONG st1, st2, st3, st4, st5, st6, st7;
+    long ii, i1, i2, i3, i4, i5, i6, i7, irange[7];
+
+    if (*status > 0)
+        return(*status);
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        fits_write_compressed_img(fptr, TBYTE, fpixel, lpixel,
+            0,  array, NULL, status);
+    
+        return(*status);
+    }
+
+    if (naxis < 1 || naxis > 7)
+      return(*status = BAD_DIMEN);
+
+    tablerow=maxvalue(1,group);
+
+     /* calculate the size and number of loops to perform in each dimension */
+    for (ii = 0; ii < 7; ii++)
+    {
+      fpix[ii]=1;
+      irange[ii]=1;
+      dimen[ii]=1;
+    }
+
+    for (ii = 0; ii < naxis; ii++)
+    {    
+      fpix[ii]=fpixel[ii];
+      irange[ii]=lpixel[ii]-fpixel[ii]+1;
+      dimen[ii]=naxes[ii];
+    }
+
+    i1=irange[0];
+
+    /* compute the pixel offset between each dimension */
+    off2 =     dimen[0];
+    off3 = off2 * dimen[1];
+    off4 = off3 * dimen[2];
+    off5 = off4 * dimen[3];
+    off6 = off5 * dimen[4];
+    off7 = off6 * dimen[5];
+
+    st10 = fpix[0];
+    st20 = (fpix[1] - 1) * off2;
+    st30 = (fpix[2] - 1) * off3;
+    st40 = (fpix[3] - 1) * off4;
+    st50 = (fpix[4] - 1) * off5;
+    st60 = (fpix[5] - 1) * off6;
+    st70 = (fpix[6] - 1) * off7;
+
+    /* store the initial offset in each dimension */
+    st1 = st10;
+    st2 = st20;
+    st3 = st30;
+    st4 = st40;
+    st5 = st50;
+    st6 = st60;
+    st7 = st70;
+
+    astart = 0;
+
+    for (i7 = 0; i7 < irange[6]; i7++)
+    {
+     for (i6 = 0; i6 < irange[5]; i6++)
+     {
+      for (i5 = 0; i5 < irange[4]; i5++)
+      {
+       for (i4 = 0; i4 < irange[3]; i4++)
+       {
+        for (i3 = 0; i3 < irange[2]; i3++)
+        {
+         pstart = st1 + st2 + st3 + st4 + st5 + st6 + st7;
+
+         for (i2 = 0; i2 < irange[1]; i2++)
+         {
+           if (ffpclb(fptr, 2, tablerow, pstart, i1, &array[astart],
+              status) > 0)
+              return(*status);
+
+           astart += i1;
+           pstart += off2;
+         }
+         st2 = st20;
+         st3 = st3+off3;    
+        }
+        st3 = st30;
+        st4 = st4+off4;
+       }
+       st4 = st40;
+       st5 = st5+off5;
+      }
+      st5 = st50;
+      st6 = st6+off6;
+     }
+     st6 = st60;
+     st7 = st7+off7;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpgpb( fitsfile *fptr,   /* I - FITS file pointer                      */
+            long  group,      /* I - group to write(1 = 1st group)          */
+            long  firstelem,  /* I - first vector element to write(1 = 1st) */
+            long  nelem,      /* I - number of values to write              */
+            unsigned char *array, /* I - array of values that are written   */
+            int  *status)     /* IO - error status                          */
+/*
+  Write an array of group parameters to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being written).
+*/
+{
+    long row;
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffpclb(fptr, 1L, row, firstelem, nelem, array, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpclb( fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  colnum,     /* I - number of column to write (1 = 1st col) */
+            LONGLONG  firstrow,  /* I - first row to write (1 = 1st row)        */
+            LONGLONG  firstelem, /* I - first vector element to write (1 = 1st) */
+            LONGLONG  nelem,     /* I - number of values to write               */
+            unsigned char *array, /* I - array of values to write           */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of values to a column in the current FITS HDU.
+  The column number may refer to a real column in an ASCII or binary table, 
+  or it may refer to a virtual column in a 1 or more grouped FITS primary
+  array.  FITSIO treats a primary array as a binary table with
+  2 vector columns: the first column contains the group parameters (often
+  with length = 0) and the second column contains the array of image pixels.
+  Each row of the table represents a group in the case of multigroup FITS
+  images.
+
+  The input array of values will be converted to the datatype of the column 
+  and will be inverse-scaled by the FITS TSCALn and TZEROn values if necessary.
+*/
+{
+    int tcode, maxelem, hdutype, writeraw;
+    long twidth, incre;
+    long  ntodo;
+    LONGLONG repeat, startpos, elemnum, wrtptr, rowlen, rownum, remain, next, tnull;
+    double scale, zero;
+    char tform[20], cform[20];
+    char message[FLEN_ERRMSG];
+
+    char snull[20];   /*  the FITS null value  */
+
+    double cbuff[DBUFFSIZE / sizeof(double)]; /* align cbuff on word boundary */
+    void *buffer;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    buffer = cbuff;
+
+    /*---------------------------------------------------*/
+    /*  Check input and get parameters about the column: */
+    /*---------------------------------------------------*/
+    if (ffgcprll( fptr, colnum, firstrow, firstelem, nelem, 1, &scale, &zero,
+        tform, &twidth, &tcode, &maxelem, &startpos,  &elemnum, &incre,
+        &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0)
+        return(*status);
+
+    if (tcode == TSTRING)   
+         ffcfmt(tform, cform);     /* derive C format for writing strings */
+
+    /*
+      if there is no scaling 
+      then we can simply write the raw data bytes into the FITS file if the
+      datatype of the FITS column is the same as the input values.  Otherwise,
+      we must convert the raw values into the scaled and/or machine dependent
+      format in a temporary buffer that has been allocated for this purpose.
+    */
+    if (scale == 1. && zero == 0. && tcode == TBYTE)
+    {
+        writeraw = 1;
+        maxelem = (int) nelem;  /* we can write the entire array at one time */
+    }
+    else
+        writeraw = 0;
+
+    /*---------------------------------------------------------------------*/
+    /*  Now write the pixels to the FITS column.                           */
+    /*  First call the ffXXfYY routine to  (1) convert the datatype        */
+    /*  if necessary, and (2) scale the values by the FITS TSCALn and      */
+    /*  TZEROn linear scaling parameters into a temporary buffer.          */
+    /*---------------------------------------------------------------------*/
+    remain = nelem;           /* remaining number of values to write  */
+    next = 0;                 /* next element in array to be written  */
+    rownum = 0;               /* row number, relative to firstrow     */
+
+    while (remain)
+    {
+        /* limit the number of pixels to process a one time to the number that
+           will fit in the buffer space or to the number of pixels that remain
+           in the current vector, which ever is smaller.
+        */
+        ntodo = (long) minvalue(remain, maxelem);      
+        ntodo = (long) minvalue(ntodo, (repeat - elemnum));
+
+        wrtptr = startpos + ((LONGLONG)rownum * rowlen) + (elemnum * incre);
+        ffmbyt(fptr, wrtptr, IGNORE_EOF, status); /* move to write position */
+
+        switch (tcode) 
+        {
+            case (TBYTE):
+              if (writeraw)
+              {
+                /* write raw input bytes without conversion */
+                ffpi1b(fptr, ntodo, incre, &array[next], status);
+              }
+              else
+              {
+                /* convert the raw data before writing to FITS file */
+                ffi1fi1(&array[next], ntodo, scale, zero,
+                        (unsigned char *) buffer, status);
+                ffpi1b(fptr, ntodo, incre, (unsigned char *) buffer, status);
+              }
+
+              break;
+
+            case (TLONGLONG):
+
+                ffi1fi8(&array[next], ntodo, scale, zero,
+                        (LONGLONG *) buffer, status);
+                ffpi8b(fptr, ntodo, incre, (long *) buffer, status);
+                break;
+
+            case (TSHORT):
+ 
+                ffi1fi2(&array[next], ntodo, scale, zero,
+                        (short *) buffer, status);
+                ffpi2b(fptr, ntodo, incre, (short *) buffer, status);
+                break;
+
+            case (TLONG):
+
+                ffi1fi4(&array[next], ntodo, scale, zero,
+                        (INT32BIT *) buffer, status);
+                ffpi4b(fptr, ntodo, incre, (INT32BIT *) buffer, status);
+                break;
+
+            case (TFLOAT):
+
+                ffi1fr4(&array[next], ntodo, scale, zero,
+                        (float *)  buffer, status);
+                ffpr4b(fptr, ntodo, incre, (float *) buffer, status);
+                break;
+
+            case (TDOUBLE):
+                ffi1fr8(&array[next], ntodo, scale, zero,
+                        (double *) buffer, status);
+                ffpr8b(fptr, ntodo, incre, (double *) buffer, status);
+                break;
+
+            case (TSTRING):  /* numerical column in an ASCII table */
+
+                if (strchr(tform,'A'))
+                {
+                    /* write raw input bytes without conversion        */
+                    /* This case is a hack to let users write a stream */
+                    /* of bytes directly to the 'A' format column      */
+
+                    if (incre == twidth)
+                        ffpbyt(fptr, ntodo, &array[next], status);
+                    else
+                        ffpbytoff(fptr, twidth, ntodo/twidth, incre - twidth, 
+                                &array[next], status);
+                    break;
+                }
+                else if (cform[1] != 's')  /*  "%s" format is a string */
+                {
+                  ffi1fstr(&array[next], ntodo, scale, zero, cform,
+                          twidth, (char *) buffer, status);
+
+                  if (incre == twidth)    /* contiguous bytes */
+                     ffpbyt(fptr, ntodo * twidth, buffer, status);
+                  else
+                     ffpbytoff(fptr, twidth, ntodo, incre - twidth, buffer,
+                            status);
+                  break;
+                }
+                /* can't write to string column, so fall thru to default: */
+
+            default:  /*  error trap  */
+                sprintf(message, 
+                       "Cannot write numbers to column %d which has format %s",
+                        colnum,tform);
+                ffpmsg(message);
+                if (hdutype == ASCII_TBL)
+                    return(*status = BAD_ATABLE_FORMAT);
+                else
+                    return(*status = BAD_BTABLE_FORMAT);
+
+        } /* End of switch block */
+
+        /*-------------------------*/
+        /*  Check for fatal error  */
+        /*-------------------------*/
+        if (*status > 0)  /* test for error during previous write operation */
+        {
+          sprintf(message,
+          "Error writing elements %.0f thru %.0f of input data array (ffpclb).",
+              (double) (next+1), (double) (next+ntodo));
+          ffpmsg(message);
+          return(*status);
+        }
+
+        /*--------------------------------------------*/
+        /*  increment the counters for the next loop  */
+        /*--------------------------------------------*/
+        remain -= ntodo;
+        if (remain)
+        {
+            next += ntodo;
+            elemnum += ntodo;
+            if (elemnum == repeat)  /* completed a row; start on next row */
+            {
+                elemnum = 0;
+                rownum++;
+            }
+        }
+    }  /*  End of main while Loop  */
+
+
+    /*--------------------------------*/
+    /*  check for numerical overflow  */
+    /*--------------------------------*/
+    if (*status == OVERFLOW_ERR)
+    {
+      ffpmsg(
+      "Numerical overflow during type conversion while writing FITS data.");
+      *status = NUM_OVERFLOW;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpcnb( fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  colnum,     /* I - number of column to write (1 = 1st col) */
+            LONGLONG  firstrow,  /* I - first row to write (1 = 1st row)        */
+            LONGLONG  firstelem, /* I - first vector element to write (1 = 1st) */
+            LONGLONG  nelem,     /* I - number of values to write               */
+            unsigned char *array,   /* I - array of values to write         */
+            unsigned char nulvalue, /* I - flag for undefined pixels        */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of elements to the specified column of a table.  Any input
+  pixels equal to the value of nulvalue will be replaced by the appropriate
+  null value in the output FITS file. 
+
+  The input array of values will be converted to the datatype of the column 
+  and will be inverse-scaled by the FITS TSCALn and TZEROn values if necessary
+*/
+{
+    tcolumn *colptr;
+    long  ngood = 0, nbad = 0, ii;
+    LONGLONG repeat, first, fstelm, fstrow;
+    int tcode, overflow = 0;
+
+    if (*status > 0)
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+    {
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    }
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+    {
+        if ( ffrdef(fptr, status) > 0)               /* rescan header */
+            return(*status);
+    }
+
+    colptr  = (fptr->Fptr)->tableptr;   /* point to first column */
+    colptr += (colnum - 1);     /* offset to correct column structure */
+
+    tcode  = colptr->tdatatype;
+
+    if (tcode > 0)
+       repeat = colptr->trepeat;  /* repeat count for this column */
+    else
+       repeat = firstelem -1 + nelem;  /* variable length arrays */
+
+    /* if variable length array, first write the whole input vector, 
+       then go back and fill in the nulls */
+    if (tcode < 0) {
+      if (ffpclb(fptr, colnum, firstrow, firstelem, nelem, array, status) > 0) {
+        if (*status == NUM_OVERFLOW) 
+	{
+	  /* ignore overflows, which are possibly the null pixel values */
+	  /*  overflow = 1;   */
+	  *status = 0;
+	} else { 
+          return(*status);
+	}
+      }
+    }
+
+    /* absolute element number in the column */
+    first = (firstrow - 1) * repeat + firstelem;
+
+    for (ii = 0; ii < nelem; ii++)
+    {
+      if (array[ii] != nulvalue)  /* is this a good pixel? */
+      {
+         if (nbad)  /* write previous string of bad pixels */
+         {
+            fstelm = ii - nbad + first;  /* absolute element number */
+            fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+            fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+            if (ffpclu(fptr, colnum, fstrow, fstelm, nbad, status) > 0)
+                return(*status);
+
+            nbad=0;
+         }
+
+         ngood = ngood + 1;  /* the consecutive number of good pixels */
+      }
+      else
+      {
+         if (ngood)  /* write previous string of good pixels */
+         {
+            fstelm = ii - ngood + first;  /* absolute element number */
+            fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+            fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+            if (tcode > 0) {  /* variable length arrays have already been written */
+              if (ffpclb(fptr, colnum, fstrow, fstelm, ngood, &array[ii-ngood],
+                status) > 0) {
+		if (*status == NUM_OVERFLOW) 
+		{
+		  overflow = 1;
+		  *status = 0;
+		} else { 
+                  return(*status);
+		}
+	      }
+	    }
+            ngood=0;
+         }
+
+         nbad = nbad + 1;  /* the consecutive number of bad pixels */
+      }
+    }
+    
+    /* finished loop;  now just write the last set of pixels */
+
+    if (ngood)  /* write last string of good pixels */
+    {
+      fstelm = ii - ngood + first;  /* absolute element number */
+      fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+      fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+      if (tcode > 0) {  /* variable length arrays have already been written */
+        ffpclb(fptr, colnum, fstrow, fstelm, ngood, &array[ii-ngood], status);
+      }
+    }
+    else if (nbad) /* write last string of bad pixels */
+    {
+      fstelm = ii - nbad + first;  /* absolute element number */
+      fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+      fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+      ffpclu(fptr, colnum, fstrow, fstelm, nbad, status);
+    }
+
+    if (*status <= 0) {
+      if (overflow) {
+        *status = NUM_OVERFLOW;
+      }
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpextn( fitsfile *fptr,        /* I - FITS file pointer                        */
+            LONGLONG  offset,      /* I - byte offset from start of extension data */
+            LONGLONG  nelem,       /* I - number of elements to write              */
+            void *buffer,          /* I - stream of bytes to write                 */
+            int  *status)          /* IO - error status                            */
+/*
+  Write a stream of bytes to the current FITS HDU.  This primative routine is mainly
+  for writing non-standard "conforming" extensions and should not be used
+  for standard IMAGE, TABLE or BINTABLE extensions.
+*/
+{
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    /* rescan header if data structure is undefined */
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+        if ( ffrdef(fptr, status) > 0)               
+            return(*status);
+
+    /* move to write position */
+    ffmbyt(fptr, (fptr->Fptr)->datastart+ offset, IGNORE_EOF, status);
+    
+    /* write the buffer */
+    ffpbyt(fptr, nelem, buffer, status); 
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffi1fi1(unsigned char *input,  /* I - array of values to be converted  */
+            long ntodo,            /* I - number of elements in the array  */
+            double scale,          /* I - FITS TSCALn or BSCALE value      */
+            double zero,           /* I - FITS TZEROn or BZERO  value      */
+            unsigned char *output, /* O - output array of converted values */
+            int *status)           /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        memcpy(output, input, ntodo); /* just copy input to output */
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = ( ((double) input[ii]) - zero) / scale;
+
+            if (dvalue < DUCHAR_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = 0;
+            }
+            else if (dvalue > DUCHAR_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = UCHAR_MAX;
+            }
+            else
+                output[ii] = (unsigned char) (dvalue + .5);
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffi1fi2(unsigned char *input,  /* I - array of values to be converted  */
+            long ntodo,            /* I - number of elements in the array  */
+            double scale,          /* I - FITS TSCALn or BSCALE value      */
+            double zero,           /* I - FITS TZEROn or BZERO  value      */
+            short *output,         /* O - output array of converted values */
+            int *status)           /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+            output[ii] = input[ii];   /* just copy input to output */
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = (((double) input[ii]) - zero) / scale;
+
+            if (dvalue < DSHRT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = SHRT_MIN;
+            }
+            else if (dvalue > DSHRT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = SHRT_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0)
+                    output[ii] = (short) (dvalue + .5);
+                else
+                    output[ii] = (short) (dvalue - .5);
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffi1fi4(unsigned char *input,  /* I - array of values to be converted  */
+            long ntodo,            /* I - number of elements in the array  */
+            double scale,          /* I - FITS TSCALn or BSCALE value      */
+            double zero,           /* I - FITS TZEROn or BZERO  value      */
+            INT32BIT *output,      /* O - output array of converted values */
+            int *status)           /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+            output[ii] = (INT32BIT) input[ii];   /* copy input to output */
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = (((double) input[ii]) - zero) / scale;
+
+            if (dvalue < DINT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = INT32_MIN;
+            }
+            else if (dvalue > DINT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = INT32_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0)
+                    output[ii] = (INT32BIT) (dvalue + .5);
+                else
+                    output[ii] = (INT32BIT) (dvalue - .5);
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffi1fi8(unsigned char *input, /* I - array of values to be converted  */
+            long ntodo,           /* I - number of elements in the array  */
+            double scale,         /* I - FITS TSCALn or BSCALE value      */
+            double zero,          /* I - FITS TZEROn or BZERO  value      */
+            LONGLONG *output,     /* O - output array of converted values */
+            int *status)          /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+                output[ii] = input[ii];
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = (input[ii] - zero) / scale;
+
+            if (dvalue < DLONGLONG_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = LONGLONG_MIN;
+            }
+            else if (dvalue > DLONGLONG_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = LONGLONG_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0)
+                    output[ii] = (LONGLONG) (dvalue + .5);
+                else
+                    output[ii] = (LONGLONG) (dvalue - .5);
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffi1fr4(unsigned char *input,  /* I - array of values to be converted  */
+            long ntodo,            /* I - number of elements in the array  */
+            double scale,          /* I - FITS TSCALn or BSCALE value      */
+            double zero,           /* I - FITS TZEROn or BZERO  value      */
+            float *output,         /* O - output array of converted values */
+            int *status)           /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required.
+*/
+{
+    long ii;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (float) input[ii];
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+            output[ii] = (float) (( ( (double) input[ii] ) - zero) / scale);
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffi1fr8(unsigned char *input,  /* I - array of values to be converted  */
+            long ntodo,            /* I - number of elements in the array  */
+            double scale,          /* I - FITS TSCALn or BSCALE value      */
+            double zero,           /* I - FITS TZEROn or BZERO  value      */
+            double *output,        /* O - output array of converted values */
+            int *status)           /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required.
+*/
+{
+    long ii;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (double) input[ii];
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+            output[ii] = ( ( (double) input[ii] ) - zero) / scale;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffi1fstr(unsigned char *input, /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            char *cform,       /* I - format for output string values  */
+            long twidth,       /* I - width of each field, in chars    */
+            char *output,      /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do scaling if required.
+*/
+{
+    long ii;
+    double dvalue;
+    char *cptr;
+
+    cptr = output;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+        {
+           sprintf(output, cform, (double) input[ii]);
+           output += twidth;
+
+           if (*output)  /* if this char != \0, then overflow occurred */
+              *status = OVERFLOW_ERR;
+        }
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+          dvalue = ((double) input[ii] - zero) / scale;
+          sprintf(output, cform, dvalue);
+          output += twidth;
+
+          if (*output)  /* if this char != \0, then overflow occurred */
+            *status = OVERFLOW_ERR;
+        }
+    }
+
+    /* replace any commas with periods (e.g., in French locale) */
+    while ((cptr = strchr(cptr, ','))) *cptr = '.';
+    
+    return(*status);
+}
diff --git a/src/plugins/cfitsio/putcold.c b/src/plugins/cfitsio/putcold.c
new file mode 100644
index 0000000..b0c135a
--- /dev/null
+++ b/src/plugins/cfitsio/putcold.c
@@ -0,0 +1,1059 @@
+/*  This file, putcold.c, contains routines that write data elements to    */
+/*  a FITS image or table, with double datatype.                           */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+
+#include <limits.h>
+#include <string.h>
+#include <stdlib.h>
+#include "fitsio2.h"
+
+/*--------------------------------------------------------------------------*/
+int ffpprd( fitsfile *fptr,  /* I - FITS file pointer                       */
+            long  group,     /* I - group to write(1 = 1st group)           */
+            LONGLONG  firstelem, /* I - first vector element to write(1 = 1st)  */
+            LONGLONG  nelem,     /* I - number of values to write               */
+            double *array,   /* I - array of values that are written        */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being written).
+*/
+{
+    long row;
+    double nullvalue;
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        fits_write_compressed_pixels(fptr, TDOUBLE, firstelem, nelem,
+            0, array, &nullvalue, status);
+        return(*status);
+    }
+
+    row=maxvalue(1,group);
+
+    ffpcld(fptr, 2, row, firstelem, nelem, array, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffppnd( fitsfile *fptr,   /* I - FITS file pointer                       */
+            long  group,      /* I - group to write(1 = 1st group)           */
+            LONGLONG  firstelem,  /* I - first vector element to write(1 = 1st)  */
+            LONGLONG  nelem,      /* I - number of values to write               */
+            double *array,    /* I - array of values that are written        */
+            double nulval,    /* I - undefined pixel value                   */
+            int  *status)     /* IO - error status                           */
+/*
+  Write an array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being written).  Any array values
+  that are equal to the value of nulval will be replaced with the null
+  pixel value that is appropriate for this column.
+*/
+{
+    long row;
+    double nullvalue;
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        nullvalue = nulval;  /* set local variable */
+        fits_write_compressed_pixels(fptr, TDOUBLE, firstelem, nelem,
+            1, array, &nullvalue, status);
+        return(*status);
+    }
+
+    row=maxvalue(1,group);
+
+    ffpcnd(fptr, 2, row, firstelem, nelem, array, nulval, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffp2dd(fitsfile *fptr,   /* I - FITS file pointer                     */
+           long  group,      /* I - group to write(1 = 1st group)         */
+           LONGLONG  ncols,      /* I - number of pixels in each row of array */
+           LONGLONG  naxis1,     /* I - FITS image NAXIS1 value               */
+           LONGLONG  naxis2,     /* I - FITS image NAXIS2 value               */
+           double *array,    /* I - array to be written                   */
+           int  *status)     /* IO - error status                         */
+/*
+  Write an entire 2-D array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being written).
+*/
+{
+    /* call the 3D writing routine, with the 3rd dimension = 1 */
+
+    ffp3dd(fptr, group, ncols, naxis2, naxis1, naxis2, 1, array, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffp3dd(fitsfile *fptr,   /* I - FITS file pointer                     */
+           long  group,      /* I - group to write(1 = 1st group)         */
+           LONGLONG  ncols,      /* I - number of pixels in each row of array */
+           LONGLONG  nrows,      /* I - number of rows in each plane of array */
+           LONGLONG  naxis1,     /* I - FITS image NAXIS1 value               */
+           LONGLONG  naxis2,     /* I - FITS image NAXIS2 value               */
+           LONGLONG  naxis3,     /* I - FITS image NAXIS3 value               */
+           double *array,    /* I - array to be written                   */
+           int  *status)     /* IO - error status                         */
+/*
+  Write an entire 3-D cube of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being written).
+*/
+{
+    long tablerow, ii, jj;
+    long fpixel[3]= {1,1,1}, lpixel[3];
+    LONGLONG nfits, narray;
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+        lpixel[0] = (long) ncols;
+        lpixel[1] = (long) nrows;
+        lpixel[2] = (long) naxis3;
+
+        fits_write_compressed_img(fptr, TDOUBLE, fpixel, lpixel,
+            0,  array, NULL, status);
+    
+        return(*status);
+    }
+
+    tablerow=maxvalue(1,group);
+
+    if (ncols == naxis1 && nrows == naxis2)  /* arrays have same size? */
+    {
+      /* all the image pixels are contiguous, so write all at once */
+      ffpcld(fptr, 2, tablerow, 1L, naxis1 * naxis2 * naxis3, array, status);
+      return(*status);
+    }
+
+    if (ncols < naxis1 || nrows < naxis2)
+       return(*status = BAD_DIMEN);
+
+    nfits = 1;   /* next pixel in FITS image to write to */
+    narray = 0;  /* next pixel in input array to be written */
+
+    /* loop over naxis3 planes in the data cube */
+    for (jj = 0; jj < naxis3; jj++)
+    {
+      /* loop over the naxis2 rows in the FITS image, */
+      /* writing naxis1 pixels to each row            */
+
+      for (ii = 0; ii < naxis2; ii++)
+      {
+       if (ffpcld(fptr, 2, tablerow, nfits, naxis1,&array[narray],status) > 0)
+         return(*status);
+
+       nfits += naxis1;
+       narray += ncols;
+      }
+      narray += (nrows - naxis2) * ncols;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpssd(fitsfile *fptr,   /* I - FITS file pointer                       */
+           long  group,      /* I - group to write(1 = 1st group)           */
+           long  naxis,      /* I - number of data axes in array            */
+           long  *naxes,     /* I - size of each FITS axis                  */
+           long  *fpixel,    /* I - 1st pixel in each axis to write (1=1st) */
+           long  *lpixel,    /* I - last pixel in each axis to write        */
+           double *array,    /* I - array to be written                     */
+           int  *status)     /* IO - error status                           */
+/*
+  Write a subsection of pixels to the primary array or image.
+  A subsection is defined to be any contiguous rectangular
+  array of pixels within the n-dimensional FITS data file.
+  Data conversion and scaling will be performed if necessary 
+  (e.g, if the datatype of the FITS array is not the same as
+  the array being written).
+*/
+{
+    long tablerow;
+    LONGLONG fpix[7], dimen[7], astart, pstart;
+    LONGLONG off2, off3, off4, off5, off6, off7;
+    LONGLONG st10, st20, st30, st40, st50, st60, st70;
+    LONGLONG st1, st2, st3, st4, st5, st6, st7;
+    long ii, i1, i2, i3, i4, i5, i6, i7, irange[7];
+
+    if (*status > 0)
+        return(*status);
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        fits_write_compressed_img(fptr, TDOUBLE, fpixel, lpixel,
+            0,  array, NULL, status);
+    
+        return(*status);
+    }
+
+    if (naxis < 1 || naxis > 7)
+      return(*status = BAD_DIMEN);
+
+    tablerow=maxvalue(1,group);
+
+     /* calculate the size and number of loops to perform in each dimension */
+    for (ii = 0; ii < 7; ii++)
+    {
+      fpix[ii]=1;
+      irange[ii]=1;
+      dimen[ii]=1;
+    }
+
+    for (ii = 0; ii < naxis; ii++)
+    {    
+      fpix[ii]=fpixel[ii];
+      irange[ii]=lpixel[ii]-fpixel[ii]+1;
+      dimen[ii]=naxes[ii];
+    }
+
+    i1=irange[0];
+
+    /* compute the pixel offset between each dimension */
+    off2 =     dimen[0];
+    off3 = off2 * dimen[1];
+    off4 = off3 * dimen[2];
+    off5 = off4 * dimen[3];
+    off6 = off5 * dimen[4];
+    off7 = off6 * dimen[5];
+
+    st10 = fpix[0];
+    st20 = (fpix[1] - 1) * off2;
+    st30 = (fpix[2] - 1) * off3;
+    st40 = (fpix[3] - 1) * off4;
+    st50 = (fpix[4] - 1) * off5;
+    st60 = (fpix[5] - 1) * off6;
+    st70 = (fpix[6] - 1) * off7;
+
+    /* store the initial offset in each dimension */
+    st1 = st10;
+    st2 = st20;
+    st3 = st30;
+    st4 = st40;
+    st5 = st50;
+    st6 = st60;
+    st7 = st70;
+
+    astart = 0;
+
+    for (i7 = 0; i7 < irange[6]; i7++)
+    {
+     for (i6 = 0; i6 < irange[5]; i6++)
+     {
+      for (i5 = 0; i5 < irange[4]; i5++)
+      {
+       for (i4 = 0; i4 < irange[3]; i4++)
+       {
+        for (i3 = 0; i3 < irange[2]; i3++)
+        {
+         pstart = st1 + st2 + st3 + st4 + st5 + st6 + st7;
+
+         for (i2 = 0; i2 < irange[1]; i2++)
+         {
+           if (ffpcld(fptr, 2, tablerow, pstart, i1, &array[astart],
+              status) > 0)
+              return(*status);
+
+           astart += i1;
+           pstart += off2;
+         }
+         st2 = st20;
+         st3 = st3+off3;    
+        }
+        st3 = st30;
+        st4 = st4+off4;
+       }
+       st4 = st40;
+       st5 = st5+off5;
+      }
+      st5 = st50;
+      st6 = st6+off6;
+     }
+     st6 = st60;
+     st7 = st7+off7;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpgpd( fitsfile *fptr,   /* I - FITS file pointer                      */
+            long  group,      /* I - group to write(1 = 1st group)          */
+            long  firstelem,  /* I - first vector element to write(1 = 1st) */
+            long  nelem,      /* I - number of values to write              */
+            double *array,    /* I - array of values that are written       */
+            int  *status)     /* IO - error status                          */
+/*
+  Write an array of group parameters to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being written).
+*/
+{
+    long row;
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffpcld(fptr, 1L, row, firstelem, nelem, array, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpcld( fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  colnum,     /* I - number of column to write (1 = 1st col) */
+            LONGLONG  firstrow,  /* I - first row to write (1 = 1st row)        */
+            LONGLONG  firstelem, /* I - first vector element to write (1 = 1st) */
+            LONGLONG  nelem,     /* I - number of values to write               */
+            double *array,   /* I - array of values to write                */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of values to a column in the current FITS HDU.
+  The column number may refer to a real column in an ASCII or binary table, 
+  or it may refer to a virtual column in a 1 or more grouped FITS primary
+  array.  FITSIO treats a primary array as a binary table
+  with 2 vector columns: the first column contains the group parameters (often
+  with length = 0) and the second column contains the array of image pixels.
+  Each row of the table represents a group in the case of multigroup FITS
+  images.
+
+  The input array of values will be converted to the datatype of the column 
+  and will be inverse-scaled by the FITS TSCALn and TZEROn values if necessary.
+*/
+{
+    int tcode, maxelem, hdutype, writeraw;
+    long twidth, incre;
+    long ntodo;
+    LONGLONG repeat, startpos, elemnum, wrtptr, rowlen, rownum, remain, next, tnull;
+    double scale, zero;
+    char tform[20], cform[20];
+    char message[FLEN_ERRMSG];
+
+    char snull[20];   /*  the FITS null value  */
+
+    double cbuff[DBUFFSIZE / sizeof(double)]; /* align cbuff on word boundary */
+    void *buffer;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    buffer = cbuff;
+
+    /*---------------------------------------------------*/
+    /*  Check input and get parameters about the column: */
+    /*---------------------------------------------------*/
+    if (ffgcprll( fptr, colnum, firstrow, firstelem, nelem, 1, &scale, &zero,
+        tform, &twidth, &tcode, &maxelem, &startpos,  &elemnum, &incre,
+        &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0)
+        return(*status);
+
+    if (tcode == TSTRING)   
+         ffcfmt(tform, cform);     /* derive C format for writing strings */
+
+    /*
+      if there is no scaling and the native machine format is not byteswapped,
+      then we can simply write the raw data bytes into the FITS file if the
+      datatype of the FITS column is the same as the input values.  Otherwise,
+      we must convert the raw values into the scaled and/or machine dependent
+      format in a temporary buffer that has been allocated for this purpose.
+    */
+    if (scale == 1. && zero == 0. && 
+       MACHINE == NATIVE && tcode == TDOUBLE)
+    {
+        writeraw = 1;
+        maxelem = (int) nelem;  /* we can write the entire array at one time */
+    }
+    else
+        writeraw = 0;
+
+    /*---------------------------------------------------------------------*/
+    /*  Now write the pixels to the FITS column.                           */
+    /*  First call the ffXXfYY routine to  (1) convert the datatype        */
+    /*  if necessary, and (2) scale the values by the FITS TSCALn and      */
+    /*  TZEROn linear scaling parameters into a temporary buffer.          */
+    /*---------------------------------------------------------------------*/
+    remain = nelem;           /* remaining number of values to write  */
+    next = 0;                 /* next element in array to be written  */
+    rownum = 0;               /* row number, relative to firstrow     */
+
+    while (remain)
+    {
+        /* limit the number of pixels to process a one time to the number that
+           will fit in the buffer space or to the number of pixels that remain
+           in the current vector, which ever is smaller.
+        */
+        ntodo = (long) minvalue(remain, maxelem);      
+        ntodo = (long) minvalue(ntodo, (repeat - elemnum));
+
+        wrtptr = startpos + ((LONGLONG)rownum * rowlen) + (elemnum * incre);
+
+        ffmbyt(fptr, wrtptr, IGNORE_EOF, status); /* move to write position */
+
+        switch (tcode) 
+        {
+            case (TDOUBLE):
+              if (writeraw)
+              {
+                /* write raw input bytes without conversion */
+                ffpr8b(fptr, ntodo, incre, &array[next], status);
+              }
+              else
+              {
+                /* convert the raw data before writing to FITS file */
+                ffr8fr8(&array[next], ntodo, scale, zero,
+                        (double *) buffer, status);
+                ffpr8b(fptr, ntodo, incre, (double *) buffer, status);
+              }
+
+              break;
+
+            case (TLONGLONG):
+
+                ffr8fi8(&array[next], ntodo, scale, zero,
+                        (LONGLONG *) buffer, status);
+                ffpi8b(fptr, ntodo, incre, (long *) buffer, status);
+                break;
+
+            case (TBYTE):
+ 
+                ffr8fi1(&array[next], ntodo, scale, zero, 
+                        (unsigned char *) buffer, status);
+                ffpi1b(fptr, ntodo, incre, (unsigned char *) buffer, status);
+                break;
+
+            case (TSHORT):
+
+                ffr8fi2(&array[next], ntodo, scale, zero, 
+                       (short *) buffer, status);
+                ffpi2b(fptr, ntodo, incre, (short *) buffer, status);
+                break;
+
+            case (TLONG):
+
+                ffr8fi4(&array[next], ntodo, scale, zero,
+                        (INT32BIT *) buffer, status);
+                ffpi4b(fptr, ntodo, incre, (INT32BIT *) buffer, status);
+                break;
+
+            case (TFLOAT):
+                ffr8fr4(&array[next], ntodo, scale, zero,
+                        (float *) buffer, status);
+                ffpr4b(fptr, ntodo, incre, (float *) buffer, status);
+                break;
+
+            case (TSTRING):  /* numerical column in an ASCII table */
+
+                if (cform[1] != 's')  /*  "%s" format is a string */
+                {
+                  ffr8fstr(&array[next], ntodo, scale, zero, cform,
+                          twidth, (char *) buffer, status);
+
+                  if (incre == twidth)    /* contiguous bytes */
+                     ffpbyt(fptr, ntodo * twidth, buffer, status);
+                  else
+                     ffpbytoff(fptr, twidth, ntodo, incre - twidth, buffer,
+                            status);
+
+                  break;
+                }
+                /* can't write to string column, so fall thru to default: */
+
+            default:  /*  error trap  */
+                sprintf(message, 
+                      "Cannot write numbers to column %d which has format %s",
+                       colnum,tform);
+                ffpmsg(message);
+                if (hdutype == ASCII_TBL)
+                    return(*status = BAD_ATABLE_FORMAT);
+                else
+                    return(*status = BAD_BTABLE_FORMAT);
+
+        } /* End of switch block */
+
+        /*-------------------------*/
+        /*  Check for fatal error  */
+        /*-------------------------*/
+        if (*status > 0)  /* test for error during previous write operation */
+        {
+          sprintf(message,
+          "Error writing elements %.0f thru %.0f of input data array (ffpcld).",
+              (double) (next+1), (double) (next+ntodo));
+         ffpmsg(message);
+         return(*status);
+        }
+
+        /*--------------------------------------------*/
+        /*  increment the counters for the next loop  */
+        /*--------------------------------------------*/
+        remain -= ntodo;
+        if (remain)
+        {
+            next += ntodo;
+            elemnum += ntodo;
+            if (elemnum == repeat)  /* completed a row; start on next row */
+            {
+                elemnum = 0;
+                rownum++;
+            }
+        }
+    }  /*  End of main while Loop  */
+
+
+    /*--------------------------------*/
+    /*  check for numerical overflow  */
+    /*--------------------------------*/
+    if (*status == OVERFLOW_ERR)
+    {
+        ffpmsg(
+        "Numerical overflow during type conversion while writing FITS data.");
+        *status = NUM_OVERFLOW;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpclm( fitsfile *fptr,   /* I - FITS file pointer                       */
+            int  colnum,      /* I - number of column to write (1 = 1st col) */
+            LONGLONG  firstrow,   /* I - first row to write (1 = 1st row)        */
+            LONGLONG  firstelem,  /* I - first vector element to write (1 = 1st) */
+            LONGLONG  nelem,      /* I - number of values to write               */
+            double *array,    /* I - array of values to write                */
+            int  *status)     /* IO - error status                           */
+/*
+  Write an array of double complex values to a column in the current FITS HDU.
+  Each complex number if interpreted as a pair of float values.
+  The column number may refer to a real column in an ASCII or binary table, 
+  or it may refer to a virtual column in a 1 or more grouped FITS primary
+  array.  FITSIO treats a primary array as a binary table
+  with 2 vector columns: the first column contains the group parameters (often
+  with length = 0) and the second column contains the array of image pixels.
+  Each row of the table represents a group in the case of multigroup FITS
+  images.
+
+  The input array of values will be converted to the datatype of the column
+  if necessary, but normally complex values should only be written to a binary
+  table with TFORMn = 'rM' where r is an optional repeat count. The TSCALn and
+  TZERO keywords should not be used with complex numbers because mathmatically
+  the scaling should only be applied to the real (first) component of the
+  complex value.
+*/
+{
+    /* simply multiply the number of elements by 2, and call ffpcld */
+
+    ffpcld(fptr, colnum, firstrow, (firstelem - 1) * 2 + 1, 
+            nelem * 2, array, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpcnd( fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  colnum,     /* I - number of column to write (1 = 1st col) */
+            LONGLONG  firstrow,  /* I - first row to write (1 = 1st row)        */
+            LONGLONG  firstelem, /* I - first vector element to write (1 = 1st) */
+            LONGLONG  nelem,     /* I - number of values to write               */
+            double *array,   /* I - array of values to write                */
+            double nulvalue, /* I - value used to flag undefined pixels     */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of elements to the specified column of a table.  Any input
+  pixels equal to the value of nulvalue will be replaced by the appropriate
+  null value in the output FITS file. 
+
+  The input array of values will be converted to the datatype of the column 
+  and will be inverse-scaled by the FITS TSCALn and TZEROn values if necessary
+*/
+{
+    tcolumn *colptr;
+    long  ngood = 0, nbad = 0, ii;
+    LONGLONG repeat, first, fstelm, fstrow;
+    int tcode, overflow = 0;
+
+    if (*status > 0)
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+    {
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    }
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+    {
+        if ( ffrdef(fptr, status) > 0)               /* rescan header */
+            return(*status);
+    }
+
+    colptr  = (fptr->Fptr)->tableptr;   /* point to first column */
+    colptr += (colnum - 1);     /* offset to correct column structure */
+
+    tcode  = colptr->tdatatype;
+
+    if (tcode > 0)
+       repeat = colptr->trepeat;  /* repeat count for this column */
+    else
+       repeat = firstelem -1 + nelem;  /* variable length arrays */
+
+    if (abs(tcode) >= TCOMPLEX)
+    { /* treat complex columns as pairs of numbers */
+        repeat *= 2;
+    }
+
+    /* if variable length array, first write the whole input vector, 
+       then go back and fill in the nulls */
+    if (tcode < 0) {
+      if (ffpcld(fptr, colnum, firstrow, firstelem, nelem, array, status) > 0) {
+	if (*status == NUM_OVERFLOW) 
+	{
+	  /* ignore overflows, which are possibly the null pixel values */
+	  /*  overflow = 1;   */
+	  *status = 0;
+	} else { 
+          return(*status);
+	}
+      }
+    }
+
+    /* absolute element number in the column */
+    first = (firstrow - 1) * repeat + firstelem;
+
+    for (ii = 0; ii < nelem; ii++)
+    {
+      if (array[ii] != nulvalue)  /* is this a good pixel? */
+      {
+         if (nbad)  /* write previous string of bad pixels */
+         {
+            fstelm = ii - nbad + first;  /* absolute element number */
+            fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+            fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+            /* call ffpcluc, not ffpclu, in case we are writing to a
+	       complex ('C') binary table column */
+            if (ffpcluc(fptr, colnum, fstrow, fstelm, nbad, status) > 0)
+                return(*status);
+
+            nbad=0;
+         }
+
+         ngood = ngood +1;  /* the consecutive number of good pixels */
+      }
+      else
+      {
+         if (ngood)  /* write previous string of good pixels */
+         {
+            fstelm = ii - ngood + first;  /* absolute element number */
+            fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+            fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+            if (tcode > 0) {  /* variable length arrays have already been written */
+              if (ffpcld(fptr, colnum, fstrow, fstelm, ngood, &array[ii-ngood],
+                status) > 0) {
+		if (*status == NUM_OVERFLOW) 
+		{
+		  overflow = 1;
+		  *status = 0;
+		} else {
+                  return(*status);
+		}
+	      }
+            }
+            ngood=0;
+         }
+
+         nbad = nbad +1;  /* the consecutive number of bad pixels */
+      }
+    }
+
+    /* finished loop;  now just write the last set of pixels */
+
+    if (ngood)  /* write last string of good pixels */
+    {
+      fstelm = ii - ngood + first;  /* absolute element number */
+      fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+      fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+      if (tcode > 0) {  /* variable length arrays have already been written */
+        ffpcld(fptr, colnum, fstrow, fstelm, ngood, &array[ii-ngood], status);
+      }
+    }
+    else if (nbad) /* write last string of bad pixels */
+    {
+      fstelm = ii - nbad + first;  /* absolute element number */
+      fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+      fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+      ffpcluc(fptr, colnum, fstrow, fstelm, nbad, status);
+    }
+
+    if (*status <= 0) {
+      if (overflow) {
+        *status = NUM_OVERFLOW;
+      }
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffr8fi1(double *input,         /* I - array of values to be converted  */
+            long ntodo,            /* I - number of elements in the array  */
+            double scale,          /* I - FITS TSCALn or BSCALE value      */
+            double zero,           /* I - FITS TZEROn or BZERO  value      */
+            unsigned char *output, /* O - output array of converted values */
+            int *status)           /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required.
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            if (input[ii] < DUCHAR_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = 0;
+            }
+            else if (input[ii] > DUCHAR_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = UCHAR_MAX;
+            }
+            else
+                output[ii] = (unsigned char) input[ii];
+        }
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = (input[ii] - zero) / scale;
+
+            if (dvalue < DUCHAR_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = 0;
+            }
+            else if (dvalue > DUCHAR_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = UCHAR_MAX;
+            }
+            else
+                output[ii] = (unsigned char) (dvalue + .5);
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffr8fi2(double *input,     /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            short *output,     /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required.
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            if (input[ii] < DSHRT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = SHRT_MIN;
+            }
+            else if (input[ii] > DSHRT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = SHRT_MAX;
+            }
+            else
+                output[ii] = (short) input[ii];
+        }
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = (input[ii] - zero) / scale;
+
+            if (dvalue < DSHRT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = SHRT_MIN;
+            }
+            else if (dvalue > DSHRT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = SHRT_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0)
+                    output[ii] = (short) (dvalue + .5);
+                else
+                    output[ii] = (short) (dvalue - .5);
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffr8fi4(double *input,     /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            INT32BIT *output,  /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required.
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            if (input[ii] < DINT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = INT32_MIN;
+            }
+            else if (input[ii] > DINT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = INT32_MAX;
+            }
+            else
+                output[ii] = (INT32BIT) input[ii];
+        }
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = (input[ii] - zero) / scale;
+
+            if (dvalue < DINT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = INT32_MIN;
+            }
+            else if (dvalue > DINT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = INT32_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0)
+                    output[ii] = (INT32BIT) (dvalue + .5);
+                else
+                    output[ii] = (INT32BIT) (dvalue - .5);
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffr8fi8(double *input,     /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            LONGLONG *output,      /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required.
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            if (input[ii] < DLONGLONG_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = LONGLONG_MIN;
+            }
+            else if (input[ii] > DLONGLONG_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = LONGLONG_MAX;
+            }
+            else
+                output[ii] = (LONGLONG) input[ii];
+        }
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = (input[ii] - zero) / scale;
+
+            if (dvalue < DLONGLONG_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = LONGLONG_MIN;
+            }
+            else if (dvalue > DLONGLONG_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = LONGLONG_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0)
+                    output[ii] = (LONGLONG) (dvalue + .5);
+                else
+                    output[ii] = (LONGLONG) (dvalue - .5);
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffr8fr4(double *input,     /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            float *output,     /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required.
+*/
+{
+    long ii;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (float) input[ii];
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+            output[ii] = (float) ((input[ii] - zero) / scale);
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffr8fr8(double *input,     /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            double *output,    /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required.
+*/
+{
+    long ii;
+
+    if (scale == 1. && zero == 0.)
+    {       
+      memcpy(output, input, ntodo * sizeof(double) ); /* copy input to output */
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+            output[ii] = (input[ii] - zero) / scale;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffr8fstr(double *input,     /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            char *cform,       /* I - format for output string values  */
+            long twidth,       /* I - width of each field, in chars    */
+            char *output,      /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do scaling if required.
+*/
+{
+    long ii;
+    double dvalue;
+    char *cptr;
+    
+    cptr = output;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+        {
+           sprintf(output, cform, input[ii]);
+           output += twidth;
+
+           if (*output)  /* if this char != \0, then overflow occurred */
+              *status = OVERFLOW_ERR;
+        }
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+          dvalue = (input[ii] - zero) / scale;
+          sprintf(output, cform, dvalue);
+          output += twidth;
+
+          if (*output)  /* if this char != \0, then overflow occurred */
+            *status = OVERFLOW_ERR;
+        }
+    }
+
+    /* replace any commas with periods (e.g., in French locale) */
+    while ((cptr = strchr(cptr, ','))) *cptr = '.';
+    
+    return(*status);
+}
diff --git a/src/plugins/cfitsio/putcole.c b/src/plugins/cfitsio/putcole.c
new file mode 100644
index 0000000..c13f04c
--- /dev/null
+++ b/src/plugins/cfitsio/putcole.c
@@ -0,0 +1,1073 @@
+/*  This file, putcole.c, contains routines that write data elements to    */
+/*  a FITS image or table, with float datatype.                            */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+
+#include <limits.h>
+#include <string.h>
+#include <stdlib.h>
+#include "fitsio2.h"
+
+/*--------------------------------------------------------------------------*/
+int ffppre( fitsfile *fptr,  /* I - FITS file pointer                       */
+            long  group,     /* I - group to write(1 = 1st group)           */
+            LONGLONG firstelem, /* I - first vector element to write(1 = 1st)  */
+            LONGLONG nelem,     /* I - number of values to write               */
+            float *array,    /* I - array of values that are written        */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being written).
+
+  This routine cannot be called directly by users to write to large
+  arrays with > 2**31 pixels (although CFITSIO can do so by passing
+  the firstelem thru a LONGLONG sized global variable)
+*/
+{
+    long row;
+    float nullvalue;
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        fits_write_compressed_pixels(fptr, TFLOAT, firstelem, nelem,
+            0, array, &nullvalue, status);
+        return(*status);
+    }
+
+    row=maxvalue(1,group);
+
+    ffpcle(fptr, 2, row, firstelem, nelem, array, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffppne( fitsfile *fptr,  /* I - FITS file pointer                       */
+            long  group,     /* I - group to write(1 = 1st group)           */
+            LONGLONG firstelem, /* I - first vector element to write(1 = 1st)  */
+            LONGLONG nelem,     /* I - number of values to write               */
+            float *array,    /* I - array of values that are written        */
+            float nulval,    /* I - undefined pixel value                   */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being written).  Any array values
+  that are equal to the value of nulval will be replaced with the null
+  pixel value that is appropriate for this column.
+
+  This routine cannot be called directly by users to write to large
+  arrays with > 2**31 pixels (although CFITSIO can do so by passing
+  the firstelem thru a LONGLONG sized global variable)
+*/
+{
+    long row;
+    float nullvalue;
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        nullvalue = nulval;  /* set local variable */
+        fits_write_compressed_pixels(fptr, TFLOAT, firstelem, nelem,
+            1, array, &nullvalue, status);
+        return(*status);
+    }
+
+    row=maxvalue(1,group);
+
+    ffpcne(fptr, 2, row, firstelem, nelem, array, nulval, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffp2de(fitsfile *fptr,   /* I - FITS file pointer                     */
+           long  group,      /* I - group to write(1 = 1st group)         */
+           LONGLONG  ncols,      /* I - number of pixels in each row of array */
+           LONGLONG  naxis1,     /* I - FITS image NAXIS1 value               */
+           LONGLONG  naxis2,     /* I - FITS image NAXIS2 value               */
+           float *array,     /* I - array to be written                   */
+           int  *status)     /* IO - error status                         */
+/*
+  Write an entire 2-D array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being written).
+
+  This routine does not support writing to large images with
+  more than 2**31 pixels.
+*/
+{
+    /* call the 3D writing routine, with the 3rd dimension = 1 */
+
+    ffp3de(fptr, group, ncols, naxis2, naxis1, naxis2, 1, array, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffp3de(fitsfile *fptr,   /* I - FITS file pointer                     */
+           long  group,      /* I - group to write(1 = 1st group)         */
+           LONGLONG  ncols,      /* I - number of pixels in each row of array */
+           LONGLONG  nrows,      /* I - number of rows in each plane of array */
+           LONGLONG  naxis1,     /* I - FITS image NAXIS1 value               */
+           LONGLONG  naxis2,     /* I - FITS image NAXIS2 value               */
+           LONGLONG  naxis3,     /* I - FITS image NAXIS3 value               */
+           float *array,     /* I - array to be written                   */
+           int  *status)     /* IO - error status                         */
+/*
+  Write an entire 3-D cube of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being written).
+
+  This routine does not support writing to large images with
+  more than 2**31 pixels.
+*/
+{
+    long tablerow, ii, jj;
+    long fpixel[3]= {1,1,1}, lpixel[3];
+    LONGLONG nfits, narray;
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+           
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+        lpixel[0] = (long) ncols;
+        lpixel[1] = (long) nrows;
+        lpixel[2] = (long) naxis3;
+       
+        fits_write_compressed_img(fptr, TFLOAT, fpixel, lpixel,
+            0,  array, NULL, status);
+    
+        return(*status);
+    }
+
+    tablerow=maxvalue(1,group);
+
+    if (ncols == naxis1 && nrows == naxis2)  /* arrays have same size? */
+    {
+      /* all the image pixels are contiguous, so write all at once */
+      ffpcle(fptr, 2, tablerow, 1L, naxis1 * naxis2 * naxis3, array, status);
+      return(*status);
+    }
+
+    if (ncols < naxis1 || nrows < naxis2)
+       return(*status = BAD_DIMEN);
+
+    nfits = 1;   /* next pixel in FITS image to write to */
+    narray = 0;  /* next pixel in input array to be written */
+
+    /* loop over naxis3 planes in the data cube */
+    for (jj = 0; jj < naxis3; jj++)
+    {
+      /* loop over the naxis2 rows in the FITS image, */
+      /* writing naxis1 pixels to each row            */
+
+      for (ii = 0; ii < naxis2; ii++)
+      {
+       if (ffpcle(fptr, 2, tablerow, nfits, naxis1,&array[narray],status) > 0)
+         return(*status);
+
+       nfits += naxis1;
+       narray += ncols;
+      }
+      narray += (nrows - naxis2) * ncols;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpsse(fitsfile *fptr,   /* I - FITS file pointer                       */
+           long  group,      /* I - group to write(1 = 1st group)           */
+           long  naxis,      /* I - number of data axes in array            */
+           long  *naxes,     /* I - size of each FITS axis                  */
+           long  *fpixel,    /* I - 1st pixel in each axis to write (1=1st) */
+           long  *lpixel,    /* I - last pixel in each axis to write        */
+           float *array,     /* I - array to be written                     */
+           int  *status)     /* IO - error status                           */
+/*
+  Write a subsection of pixels to the primary array or image.
+  A subsection is defined to be any contiguous rectangular
+  array of pixels within the n-dimensional FITS data file.
+  Data conversion and scaling will be performed if necessary 
+  (e.g, if the datatype of the FITS array is not the same as
+  the array being written).
+*/
+{
+    long tablerow;
+    LONGLONG fpix[7], dimen[7], astart, pstart;
+    LONGLONG off2, off3, off4, off5, off6, off7;
+    LONGLONG st10, st20, st30, st40, st50, st60, st70;
+    LONGLONG st1, st2, st3, st4, st5, st6, st7;
+    long ii, i1, i2, i3, i4, i5, i6, i7, irange[7];
+
+    if (*status > 0)
+        return(*status);
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        fits_write_compressed_img(fptr, TFLOAT, fpixel, lpixel,
+            0,  array, NULL, status);
+    
+        return(*status);
+    }
+
+    if (naxis < 1 || naxis > 7)
+      return(*status = BAD_DIMEN);
+
+    tablerow=maxvalue(1,group);
+
+     /* calculate the size and number of loops to perform in each dimension */
+    for (ii = 0; ii < 7; ii++)
+    {
+      fpix[ii]=1;
+      irange[ii]=1;
+      dimen[ii]=1;
+    }
+
+    for (ii = 0; ii < naxis; ii++)
+    {    
+      fpix[ii]=fpixel[ii];
+      irange[ii]=lpixel[ii]-fpixel[ii]+1;
+      dimen[ii]=naxes[ii];
+    }
+
+    i1=irange[0];
+
+    /* compute the pixel offset between each dimension */
+    off2 =     dimen[0];
+    off3 = off2 * dimen[1];
+    off4 = off3 * dimen[2];
+    off5 = off4 * dimen[3];
+    off6 = off5 * dimen[4];
+    off7 = off6 * dimen[5];
+
+    st10 = fpix[0];
+    st20 = (fpix[1] - 1) * off2;
+    st30 = (fpix[2] - 1) * off3;
+    st40 = (fpix[3] - 1) * off4;
+    st50 = (fpix[4] - 1) * off5;
+    st60 = (fpix[5] - 1) * off6;
+    st70 = (fpix[6] - 1) * off7;
+
+    /* store the initial offset in each dimension */
+    st1 = st10;
+    st2 = st20;
+    st3 = st30;
+    st4 = st40;
+    st5 = st50;
+    st6 = st60;
+    st7 = st70;
+
+    astart = 0;
+
+    for (i7 = 0; i7 < irange[6]; i7++)
+    {
+     for (i6 = 0; i6 < irange[5]; i6++)
+     {
+      for (i5 = 0; i5 < irange[4]; i5++)
+      {
+       for (i4 = 0; i4 < irange[3]; i4++)
+       {
+        for (i3 = 0; i3 < irange[2]; i3++)
+        {
+         pstart = st1 + st2 + st3 + st4 + st5 + st6 + st7;
+
+         for (i2 = 0; i2 < irange[1]; i2++)
+         {
+           if (ffpcle(fptr, 2, tablerow, pstart, i1, &array[astart],
+              status) > 0)
+              return(*status);
+
+           astart += i1;
+           pstart += off2;
+         }
+         st2 = st20;
+         st3 = st3+off3;    
+        }
+        st3 = st30;
+        st4 = st4+off4;
+       }
+       st4 = st40;
+       st5 = st5+off5;
+      }
+      st5 = st50;
+      st6 = st6+off6;
+     }
+     st6 = st60;
+     st7 = st7+off7;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpgpe( fitsfile *fptr,   /* I - FITS file pointer                      */
+            long  group,      /* I - group to write(1 = 1st group)          */
+            long  firstelem,  /* I - first vector element to write(1 = 1st) */
+            long  nelem,      /* I - number of values to write              */
+            float *array,     /* I - array of values that are written       */
+            int  *status)     /* IO - error status                          */
+/*
+  Write an array of group parameters to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being written).
+*/
+{
+    long row;
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffpcle(fptr, 1L, row, firstelem, nelem, array, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpcle( fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  colnum,     /* I - number of column to write (1 = 1st col) */
+            LONGLONG  firstrow,  /* I - first row to write (1 = 1st row)        */
+            LONGLONG  firstelem, /* I - first vector element to write (1 = 1st) */
+            LONGLONG  nelem,     /* I - number of values to write               */
+            float *array,    /* I - array of values to write                */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of values to a column in the current FITS HDU.
+  The column number may refer to a real column in an ASCII or binary table, 
+  or it may refer to a virtual column in a 1 or more grouped FITS primary
+  array.  FITSIO treats a primary array as a binary table
+  with 2 vector columns: the first column contains the group parameters (often
+  with length = 0) and the second column contains the array of image pixels.
+  Each row of the table represents a group in the case of multigroup FITS
+  images.
+
+  The input array of values will be converted to the datatype of the column 
+  and will be inverse-scaled by the FITS TSCALn and TZEROn values if necessary.
+*/
+{
+    int tcode, maxelem, hdutype, writeraw;
+    long twidth, incre;
+    long ntodo;
+    LONGLONG repeat, startpos, elemnum, wrtptr, rowlen, rownum, remain, next, tnull;
+    double scale, zero;
+    char tform[20], cform[20];
+    char message[FLEN_ERRMSG];
+
+    char snull[20];   /*  the FITS null value  */
+
+    double cbuff[DBUFFSIZE / sizeof(double)]; /* align cbuff on word boundary */
+    void *buffer;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    buffer = cbuff;
+
+    /*---------------------------------------------------*/
+    /*  Check input and get parameters about the column: */
+    /*---------------------------------------------------*/
+    if (ffgcprll( fptr, colnum, firstrow, firstelem, nelem, 1, &scale, &zero,
+        tform, &twidth, &tcode, &maxelem, &startpos,  &elemnum, &incre,
+        &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0)
+        return(*status);
+
+    if (tcode == TSTRING)   
+         ffcfmt(tform, cform);     /* derive C format for writing strings */
+
+    /*
+       if there is no scaling and the native machine format is not byteswapped
+       then we can simply write the raw data bytes into the FITS file if the
+       datatype of the FITS column is the same as the input values.  Otherwise,
+       we must convert the raw values into the scaled and/or machine dependent
+       format in a temporary buffer that has been allocated for this purpose.
+    */
+    if (scale == 1. && zero == 0. && 
+       MACHINE == NATIVE && tcode == TFLOAT)
+    {
+        writeraw = 1;
+        maxelem = (int) nelem;  /* we can write the entire array at one time */
+    }
+    else
+        writeraw = 0;
+
+    /*---------------------------------------------------------------------*/
+    /*  Now write the pixels to the FITS column.                           */
+    /*  First call the ffXXfYY routine to  (1) convert the datatype        */
+    /*  if necessary, and (2) scale the values by the FITS TSCALn and      */
+    /*  TZEROn linear scaling parameters into a temporary buffer.          */
+    /*---------------------------------------------------------------------*/
+    remain = nelem;           /* remaining number of values to write  */
+    next = 0;                 /* next element in array to be written  */
+    rownum = 0;               /* row number, relative to firstrow     */
+
+    while (remain)
+    {
+        /* limit the number of pixels to process a one time to the number that
+           will fit in the buffer space or to the number of pixels that remain
+           in the current vector, which ever is smaller.
+        */
+        ntodo = (long) minvalue(remain, maxelem);      
+        ntodo = (long) minvalue(ntodo, (repeat - elemnum));
+
+        wrtptr = startpos + ((LONGLONG)rownum * rowlen) + (elemnum * incre);
+
+        ffmbyt(fptr, wrtptr, IGNORE_EOF, status); /* move to write position */
+
+        switch (tcode) 
+        {
+            case (TFLOAT):
+              if (writeraw)
+              {
+                /* write raw input bytes without conversion */
+                ffpr4b(fptr, ntodo, incre, &array[next], status);
+              }
+              else
+              {
+                /* convert the raw data before writing to FITS file */
+                ffr4fr4(&array[next], ntodo, scale, zero,
+                        (float *) buffer, status);
+                ffpr4b(fptr, ntodo, incre, (float *) buffer, status);
+              }
+
+              break;
+
+            case (TLONGLONG):
+
+                ffr4fi8(&array[next], ntodo, scale, zero,
+                        (LONGLONG *) buffer, status);
+                ffpi8b(fptr, ntodo, incre, (long *) buffer, status);
+                break;
+
+            case (TBYTE):
+ 
+                ffr4fi1(&array[next], ntodo, scale, zero, 
+                        (unsigned char *) buffer, status);
+                ffpi1b(fptr, ntodo, incre, (unsigned char *) buffer, status);
+                break;
+
+            case (TSHORT):
+
+                ffr4fi2(&array[next], ntodo, scale, zero,
+                        (short *) buffer, status);
+                ffpi2b(fptr, ntodo, incre, (short *) buffer, status);
+                break;
+
+            case (TLONG):
+
+                ffr4fi4(&array[next], ntodo, scale, zero,
+                        (INT32BIT *) buffer, status);
+                ffpi4b(fptr, ntodo, incre, (INT32BIT *) buffer, status);
+                break;
+
+            case (TDOUBLE):
+                ffr4fr8(&array[next], ntodo, scale, zero,
+                       (double *) buffer, status);
+                ffpr8b(fptr, ntodo, incre, (double *) buffer, status);
+                break;
+
+            case (TSTRING):  /* numerical column in an ASCII table */
+
+                if (cform[1] != 's')  /*  "%s" format is a string */
+                {
+                  ffr4fstr(&array[next], ntodo, scale, zero, cform,
+                          twidth, (char *) buffer, status);
+
+                  if (incre == twidth)    /* contiguous bytes */
+                     ffpbyt(fptr, ntodo * twidth, buffer, status);
+                  else
+                     ffpbytoff(fptr, twidth, ntodo, incre - twidth, buffer,
+                            status);
+
+                  break;
+                }
+                /* can't write to string column, so fall thru to default: */
+
+            default:  /*  error trap  */
+                sprintf(message, 
+                       "Cannot write numbers to column %d which has format %s",
+                        colnum,tform);
+                ffpmsg(message);
+                if (hdutype == ASCII_TBL)
+                    return(*status = BAD_ATABLE_FORMAT);
+                else
+                    return(*status = BAD_BTABLE_FORMAT);
+
+        } /* End of switch block */
+
+        /*-------------------------*/
+        /*  Check for fatal error  */
+        /*-------------------------*/
+        if (*status > 0)  /* test for error during previous write operation */
+        {
+          sprintf(message,
+          "Error writing elements %.0f thru %.0f of input data array (ffpcle).",
+             (double) (next+1), (double) (next+ntodo));
+         ffpmsg(message);
+         return(*status);
+        }
+
+        /*--------------------------------------------*/
+        /*  increment the counters for the next loop  */
+        /*--------------------------------------------*/
+        remain -= ntodo;
+        if (remain)
+        {
+            next += ntodo;
+            elemnum += ntodo;
+            if (elemnum == repeat)  /* completed a row; start on next row */
+            {
+                elemnum = 0;
+                rownum++;
+            }
+        }
+    }  /*  End of main while Loop  */
+
+
+    /*--------------------------------*/
+    /*  check for numerical overflow  */
+    /*--------------------------------*/
+    if (*status == OVERFLOW_ERR)
+    {
+        ffpmsg(
+        "Numerical overflow during type conversion while writing FITS data.");
+        *status = NUM_OVERFLOW;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpclc( fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  colnum,     /* I - number of column to write (1 = 1st col) */
+            LONGLONG  firstrow,  /* I - first row to write (1 = 1st row)        */
+            LONGLONG  firstelem, /* I - first vector element to write (1 = 1st) */
+            LONGLONG  nelem,     /* I - number of values to write               */
+            float *array,    /* I - array of values to write                */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of complex values to a column in the current FITS HDU.
+  Each complex number if interpreted as a pair of float values.
+  The column number may refer to a real column in an ASCII or binary table, 
+  or it may refer to a virtual column in a 1 or more grouped FITS primary
+  array.  FITSIO treats a primary array as a binary table
+  with 2 vector columns: the first column contains the group parameters (often
+  with length = 0) and the second column contains the array of image pixels.
+  Each row of the table represents a group in the case of multigroup FITS
+  images.
+
+  The input array of values will be converted to the datatype of the column
+  if necessary, but normally complex values should only be written to a binary
+  table with TFORMn = 'rC' where r is an optional repeat count. The TSCALn and
+  TZERO keywords should not be used with complex numbers because mathmatically
+  the scaling should only be applied to the real (first) component of the
+  complex value.
+*/
+{
+    /* simply multiply the number of elements by 2, and call ffpcle */
+
+    ffpcle(fptr, colnum, firstrow, (firstelem - 1) * 2 + 1,
+            nelem * 2, array, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpcne( fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  colnum,     /* I - number of column to write (1 = 1st col) */
+            LONGLONG  firstrow,  /* I - first row to write (1 = 1st row)        */
+            LONGLONG  firstelem, /* I - first vector element to write (1 = 1st) */
+            LONGLONG  nelem,     /* I - number of values to write               */
+            float *array,    /* I - array of values to write                */
+            float  nulvalue, /* I - value used to flag undefined pixels     */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of elements to the specified column of a table.  Any input
+  pixels equal to the value of nulvalue will be replaced by the appropriate
+  null value in the output FITS file. 
+
+  The input array of values will be converted to the datatype of the column 
+  and will be inverse-scaled by the FITS TSCALn and TZEROn values if necessary
+*/
+{
+    tcolumn *colptr;
+    long  ngood = 0, nbad = 0, ii;
+    LONGLONG repeat, first, fstelm, fstrow;
+    int tcode, overflow = 0;
+
+    if (*status > 0)
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+    {
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    }
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+    {
+        if ( ffrdef(fptr, status) > 0)               /* rescan header */
+            return(*status);
+    }
+
+    colptr  = (fptr->Fptr)->tableptr;   /* point to first column */
+    colptr += (colnum - 1);     /* offset to correct column structure */
+
+    tcode  = colptr->tdatatype;
+
+    if (tcode > 0)
+       repeat = colptr->trepeat;  /* repeat count for this column */
+    else
+       repeat = firstelem -1 + nelem;  /* variable length arrays */
+
+    if (abs(tcode) >= TCOMPLEX)
+    { /* treat complex columns as pairs of numbers */
+        repeat *= 2;
+    }
+    
+    /* if variable length array, first write the whole input vector, 
+       then go back and fill in the nulls */
+    if (tcode < 0) {
+      if (ffpcle(fptr, colnum, firstrow, firstelem, nelem, array, status) > 0) {
+        if (*status == NUM_OVERFLOW) 
+	{
+	  /* ignore overflows, which are possibly the null pixel values */
+	  /*  overflow = 1;   */
+	  *status = 0;
+	} else { 
+          return(*status);
+	}
+      }
+    }
+
+    /* absolute element number in the column */
+    first = (firstrow - 1) * repeat + firstelem;
+
+    for (ii = 0; ii < nelem; ii++)
+    {
+      if (array[ii] != nulvalue)  /* is this a good pixel? */
+      {
+         if (nbad)  /* write previous string of bad pixels */
+         {
+            fstelm = ii - nbad + first;  /* absolute element number */
+            fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+            fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+            /* call ffpcluc, not ffpclu, in case we are writing to a
+	       complex ('C') binary table column */
+            if (ffpcluc(fptr, colnum, fstrow, fstelm, nbad, status) > 0)
+                return(*status);
+
+            nbad=0;
+         }
+
+         ngood = ngood +1;  /* the consecutive number of good pixels */
+      }
+      else
+      {
+         if (ngood)  /* write previous string of good pixels */
+         {
+            fstelm = ii - ngood + first;  /* absolute element number */
+            fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+            fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+            if (tcode > 0) {  /* variable length arrays have already been written */
+              if (ffpcle(fptr, colnum, fstrow, fstelm, ngood, &array[ii-ngood],
+                status) > 0) {
+		if (*status == NUM_OVERFLOW) 
+		{
+		  overflow = 1;
+		  *status = 0;
+		} else { 
+                  return(*status);
+		}
+              }
+	    }
+            ngood=0;
+         }
+
+         nbad = nbad +1;  /* the consecutive number of bad pixels */
+      }
+    }
+
+    /* finished loop;  now just write the last set of pixels */
+
+    if (ngood)  /* write last string of good pixels */
+    {
+      fstelm = ii - ngood + first;  /* absolute element number */
+      fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+      fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+      if (tcode > 0) {  /* variable length arrays have already been written */
+        ffpcle(fptr, colnum, fstrow, fstelm, ngood, &array[ii-ngood], status);
+      }
+    }
+    else if (nbad) /* write last string of bad pixels */
+    {
+      fstelm = ii - nbad + first;  /* absolute element number */
+      fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+      fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+      ffpcluc(fptr, colnum, fstrow, fstelm, nbad, status);
+    }
+    
+    if (*status <= 0) {
+      if (overflow) {
+        *status = NUM_OVERFLOW;
+      }
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffr4fi1(float *input,          /* I - array of values to be converted  */
+            long ntodo,            /* I - number of elements in the array  */
+            double scale,          /* I - FITS TSCALn or BSCALE value      */
+            double zero,           /* I - FITS TZEROn or BZERO  value      */
+            unsigned char *output, /* O - output array of converted values */
+            int *status)           /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required.
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            if (input[ii] < DUCHAR_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = 0;
+            }
+            else if (input[ii] > DUCHAR_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = UCHAR_MAX;
+            }
+            else
+                output[ii] = (unsigned char) input[ii];
+        }
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = (input[ii] - zero) / scale;
+
+            if (dvalue < DUCHAR_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = 0;
+            }
+            else if (dvalue > DUCHAR_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = UCHAR_MAX;
+            }
+            else
+                output[ii] = (unsigned char) (dvalue + .5);
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffr4fi2(float *input,      /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            short *output,     /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required.
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 0.)
+    {           
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            if (input[ii] < DSHRT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = SHRT_MIN;
+            }
+            else if (input[ii] > DSHRT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = SHRT_MAX;
+            }
+            else
+                output[ii] = (short) input[ii];
+        }
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = (input[ii] - zero) / scale;
+
+            if (dvalue < DSHRT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = SHRT_MIN;
+            }
+            else if (dvalue > DSHRT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = SHRT_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0)
+                    output[ii] = (short) (dvalue + .5);
+                else
+                    output[ii] = (short) (dvalue - .5);
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffr4fi4(float *input,      /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            INT32BIT *output,  /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required.
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            if (input[ii] < DINT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = INT32_MIN;
+            }
+            else if (input[ii] > DINT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = INT32_MAX;
+            }
+            else
+                output[ii] = (INT32BIT) input[ii];
+        }
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = (input[ii] - zero) / scale;
+
+            if (dvalue < DINT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = INT32_MIN;
+            }
+            else if (dvalue > DINT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = INT32_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0)
+                    output[ii] = (INT32BIT) (dvalue + .5);
+                else
+                    output[ii] = (INT32BIT) (dvalue - .5);
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffr4fi8(float *input,      /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            LONGLONG *output,  /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required.
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            if (input[ii] < DLONGLONG_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = LONGLONG_MIN;
+            }
+            else if (input[ii] > DLONGLONG_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = LONGLONG_MAX;
+            }
+            else
+                output[ii] = (long) input[ii];
+        }
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = (input[ii] - zero) / scale;
+
+            if (dvalue < DLONGLONG_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = LONGLONG_MIN;
+            }
+            else if (dvalue > DINT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = LONGLONG_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0)
+                    output[ii] = (LONGLONG) (dvalue + .5);
+                else
+                    output[ii] = (LONGLONG) (dvalue - .5);
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffr4fr4(float *input,      /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            float *output,     /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required.
+*/
+{
+    long ii;
+
+    if (scale == 1. && zero == 0.)
+    {       
+      memcpy(output, input, ntodo * sizeof(float) ); /* copy input to output */
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+            output[ii] = (float) ((input[ii] - zero) / scale);
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffr4fr8(float *input,      /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            double *output,    /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required.
+*/
+{
+    long ii;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (double) input[ii];
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+            output[ii] = (input[ii] - zero) / scale;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffr4fstr(float *input,     /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            char *cform,       /* I - format for output string values  */
+            long twidth,       /* I - width of each field, in chars    */
+            char *output,      /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do scaling if required.
+*/
+{
+    long ii;
+    double dvalue;
+    char *cptr;
+    
+    cptr = output;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+        {
+           sprintf(output, cform, (double) input[ii]);
+           output += twidth;
+
+           if (*output)  /* if this char != \0, then overflow occurred */
+              *status = OVERFLOW_ERR;
+        }
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+          dvalue = (input[ii] - zero) / scale;
+          sprintf(output, cform, dvalue);
+          output += twidth;
+
+          if (*output)  /* if this char != \0, then overflow occurred */
+            *status = OVERFLOW_ERR;
+        }
+    }
+
+    /* replace any commas with periods (e.g., in French locale) */
+    while ((cptr = strchr(cptr, ','))) *cptr = '.';
+    
+    return(*status);
+}
diff --git a/src/plugins/cfitsio/putcoli.c b/src/plugins/cfitsio/putcoli.c
new file mode 100644
index 0000000..bddc459
--- /dev/null
+++ b/src/plugins/cfitsio/putcoli.c
@@ -0,0 +1,985 @@
+/*  This file, putcoli.c, contains routines that write data elements to    */
+/*  a FITS image or table, with short datatype.                            */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+
+#include <limits.h>
+#include <string.h>
+#include <stdlib.h>
+#include "fitsio2.h"
+
+/*--------------------------------------------------------------------------*/
+int ffppri( fitsfile *fptr,  /* I - FITS file pointer                       */
+            long  group,     /* I - group to write (1 = 1st group)          */
+            LONGLONG  firstelem, /* I - first vector element to write (1 = 1st) */
+            LONGLONG  nelem,     /* I - number of values to write               */
+            short *array,    /* I - array of values that are written        */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being written).
+*/
+{
+    long row;
+    short nullvalue;
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+
+        fits_write_compressed_pixels(fptr, TSHORT, firstelem, nelem,
+            0, array, &nullvalue, status);
+        return(*status);
+    }
+
+    row=maxvalue(1,group);
+
+    ffpcli(fptr, 2, row, firstelem, nelem, array, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffppni( fitsfile *fptr,  /* I - FITS file pointer                       */
+            long  group,     /* I - group to write(1 = 1st group)           */
+            LONGLONG  firstelem, /* I - first vector element to write(1 = 1st)  */
+            LONGLONG  nelem,     /* I - number of values to write               */
+            short *array,    /* I - array of values that are written        */
+            short nulval,    /* I - undefined pixel value                   */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being written).  Any array values
+  that are equal to the value of nulval will be replaced with the null
+  pixel value that is appropriate for this column.
+*/
+{
+    long row;
+    short nullvalue;
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        nullvalue = nulval;  /* set local variable */
+        fits_write_compressed_pixels(fptr, TSHORT, firstelem, nelem,
+            1, array, &nullvalue, status);
+        return(*status);
+    }
+
+    row=maxvalue(1,group);
+
+    ffpcni(fptr, 2, row, firstelem, nelem, array, nulval, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffp2di(fitsfile *fptr,   /* I - FITS file pointer                     */
+           long  group,      /* I - group to write(1 = 1st group)         */
+           LONGLONG  ncols,      /* I - number of pixels in each row of array */
+           LONGLONG  naxis1,     /* I - FITS image NAXIS1 value               */
+           LONGLONG  naxis2,     /* I - FITS image NAXIS2 value               */
+           short *array,     /* I - array to be written                   */
+           int  *status)     /* IO - error status                         */
+/*
+  Write an entire 2-D array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being written).
+*/
+{
+    /* call the 3D writing routine, with the 3rd dimension = 1 */
+
+    ffp3di(fptr, group, ncols, naxis2, naxis1, naxis2, 1, array, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffp3di(fitsfile *fptr,   /* I - FITS file pointer                     */
+           long  group,      /* I - group to write(1 = 1st group)         */
+           LONGLONG  ncols,      /* I - number of pixels in each row of array */
+           LONGLONG  nrows,      /* I - number of rows in each plane of array */
+           LONGLONG  naxis1,     /* I - FITS image NAXIS1 value               */
+           LONGLONG  naxis2,     /* I - FITS image NAXIS2 value               */
+           LONGLONG  naxis3,     /* I - FITS image NAXIS3 value               */
+           short *array,     /* I - array to be written                   */
+           int  *status)     /* IO - error status                         */
+/*
+  Write an entire 3-D cube of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being written).
+*/
+{
+    long tablerow, ii, jj;
+    long fpixel[3]= {1,1,1}, lpixel[3];
+    LONGLONG nfits, narray;
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+           
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+        lpixel[0] = (long) ncols;
+        lpixel[1] = (long) nrows;
+        lpixel[2] = (long) naxis3;
+       
+        fits_write_compressed_img(fptr, TSHORT, fpixel, lpixel,
+            0,  array, NULL, status);
+    
+        return(*status);
+    }
+
+    tablerow=maxvalue(1,group);
+
+    if (ncols == naxis1 && nrows == naxis2)  /* arrays have same size? */
+    {
+      /* all the image pixels are contiguous, so write all at once */
+      ffpcli(fptr, 2, tablerow, 1L, naxis1 * naxis2 * naxis3, array, status);
+      return(*status);
+    }
+
+    if (ncols < naxis1 || nrows < naxis2)
+       return(*status = BAD_DIMEN);
+
+    nfits = 1;   /* next pixel in FITS image to write to */
+    narray = 0;  /* next pixel in input array to be written */
+
+    /* loop over naxis3 planes in the data cube */
+    for (jj = 0; jj < naxis3; jj++)
+    {
+      /* loop over the naxis2 rows in the FITS image, */
+      /* writing naxis1 pixels to each row            */
+
+      for (ii = 0; ii < naxis2; ii++)
+      {
+       if (ffpcli(fptr, 2, tablerow, nfits, naxis1,&array[narray],status) > 0)
+         return(*status);
+
+       nfits += naxis1;
+       narray += ncols;
+      }
+      narray += (nrows - naxis2) * ncols;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpssi(fitsfile *fptr,   /* I - FITS file pointer                       */
+           long  group,      /* I - group to write(1 = 1st group)           */
+           long  naxis,      /* I - number of data axes in array            */
+           long  *naxes,     /* I - size of each FITS axis                  */
+           long  *fpixel,    /* I - 1st pixel in each axis to write (1=1st) */
+           long  *lpixel,    /* I - last pixel in each axis to write        */
+           short *array,     /* I - array to be written                     */
+           int  *status)     /* IO - error status                           */
+/*
+  Write a subsection of pixels to the primary array or image.
+  A subsection is defined to be any contiguous rectangular
+  array of pixels within the n-dimensional FITS data file.
+  Data conversion and scaling will be performed if necessary 
+  (e.g, if the datatype of the FITS array is not the same as
+  the array being written).
+*/
+{
+    long tablerow;
+    LONGLONG fpix[7], dimen[7], astart, pstart;
+    LONGLONG off2, off3, off4, off5, off6, off7;
+    LONGLONG st10, st20, st30, st40, st50, st60, st70;
+    LONGLONG st1, st2, st3, st4, st5, st6, st7;
+    long ii, i1, i2, i3, i4, i5, i6, i7, irange[7];
+
+    if (*status > 0)
+        return(*status);
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        fits_write_compressed_img(fptr, TSHORT, fpixel, lpixel,
+            0,  array, NULL, status);
+
+        return(*status);
+    }
+
+    if (naxis < 1 || naxis > 7)
+      return(*status = BAD_DIMEN);
+
+    tablerow=maxvalue(1,group);
+
+     /* calculate the size and number of loops to perform in each dimension */
+    for (ii = 0; ii < 7; ii++)
+    {
+      fpix[ii]=1;
+      irange[ii]=1;
+      dimen[ii]=1;
+    }
+
+    for (ii = 0; ii < naxis; ii++)
+    {    
+      fpix[ii]=fpixel[ii];
+      irange[ii]=lpixel[ii]-fpixel[ii]+1;
+      dimen[ii]=naxes[ii];
+    }
+
+    i1=irange[0];
+
+    /* compute the pixel offset between each dimension */
+    off2 =     dimen[0];
+    off3 = off2 * dimen[1];
+    off4 = off3 * dimen[2];
+    off5 = off4 * dimen[3];
+    off6 = off5 * dimen[4];
+    off7 = off6 * dimen[5];
+
+    st10 = fpix[0];
+    st20 = (fpix[1] - 1) * off2;
+    st30 = (fpix[2] - 1) * off3;
+    st40 = (fpix[3] - 1) * off4;
+    st50 = (fpix[4] - 1) * off5;
+    st60 = (fpix[5] - 1) * off6;
+    st70 = (fpix[6] - 1) * off7;
+
+    /* store the initial offset in each dimension */
+    st1 = st10;
+    st2 = st20;
+    st3 = st30;
+    st4 = st40;
+    st5 = st50;
+    st6 = st60;
+    st7 = st70;
+
+    astart = 0;
+
+    for (i7 = 0; i7 < irange[6]; i7++)
+    {
+     for (i6 = 0; i6 < irange[5]; i6++)
+     {
+      for (i5 = 0; i5 < irange[4]; i5++)
+      {
+       for (i4 = 0; i4 < irange[3]; i4++)
+       {
+        for (i3 = 0; i3 < irange[2]; i3++)
+        {
+         pstart = st1 + st2 + st3 + st4 + st5 + st6 + st7;
+
+         for (i2 = 0; i2 < irange[1]; i2++)
+         {
+           if (ffpcli(fptr, 2, tablerow, pstart, i1, &array[astart],
+              status) > 0)
+              return(*status);
+
+           astart += i1;
+           pstart += off2;
+         }
+         st2 = st20;
+         st3 = st3+off3;    
+        }
+        st3 = st30;
+        st4 = st4+off4;
+       }
+       st4 = st40;
+       st5 = st5+off5;
+      }
+      st5 = st50;
+      st6 = st6+off6;
+     }
+     st6 = st60;
+     st7 = st7+off7;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpgpi( fitsfile *fptr,   /* I - FITS file pointer                      */
+            long  group,      /* I - group to write(1 = 1st group)          */
+            long  firstelem,  /* I - first vector element to write(1 = 1st) */
+            long  nelem,      /* I - number of values to write              */
+            short *array,     /* I - array of values that are written       */
+            int  *status)     /* IO - error status                          */
+/*
+  Write an array of group parameters to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being written).
+*/
+{
+    long row;
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffpcli(fptr, 1L, row, firstelem, nelem, array, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpcli( fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  colnum,     /* I - number of column to write (1 = 1st col) */
+            LONGLONG  firstrow,  /* I - first row to write (1 = 1st row)        */
+            LONGLONG  firstelem, /* I - first vector element to write (1 = 1st) */
+            LONGLONG  nelem,     /* I - number of values to write               */
+            short *array,    /* I - array of values to write                */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of values to a column in the current FITS HDU.
+  The column number may refer to a real column in an ASCII or binary table, 
+  or it may refer to a virtual column in a 1 or more grouped FITS primary
+  array.  FITSIO treats a primary array as a binary table with
+  2 vector columns: the first column contains the group parameters (often
+  with length = 0) and the second column contains the array of image pixels.
+  Each row of the table represents a group in the case of multigroup FITS
+  images.
+
+  The input array of values will be converted to the datatype of the column 
+  and will be inverse-scaled by the FITS TSCALn and TZEROn values if necessary.
+*/
+{
+    int tcode, maxelem, hdutype, writeraw;
+    long twidth, incre;
+    long ntodo;
+    LONGLONG repeat, startpos, elemnum, wrtptr, rowlen, rownum, remain, next, tnull;
+    double scale, zero;
+    char tform[20], cform[20];
+    char message[FLEN_ERRMSG];
+
+    char snull[20];   /*  the FITS null value  */
+
+    double cbuff[DBUFFSIZE / sizeof(double)]; /* align cbuff on word boundary */
+    void *buffer;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    buffer = cbuff;
+
+    /*---------------------------------------------------*/
+    /*  Check input and get parameters about the column: */
+    /*---------------------------------------------------*/
+    if (ffgcprll( fptr, colnum, firstrow, firstelem, nelem, 1, &scale, &zero,
+        tform, &twidth, &tcode, &maxelem, &startpos,  &elemnum, &incre,
+        &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0)
+        return(*status);
+
+    if (tcode == TSTRING)   
+         ffcfmt(tform, cform);     /* derive C format for writing strings */
+
+    /*
+      if there is no scaling and the native machine format is not byteswapped,
+      then we can simply write the raw data bytes into the FITS file if the
+      datatype of the FITS column is the same as the input values.  Otherwise,
+      we must convert the raw values into the scaled and/or machine dependent
+      format in a temporary buffer that has been allocated for this purpose.
+    */
+    if (scale == 1. && zero == 0. &&
+       MACHINE == NATIVE && tcode == TSHORT)
+    {
+        writeraw = 1;
+        maxelem = (int) nelem;  /* we can write the entire array at one time */
+    }
+    else
+        writeraw = 0;
+
+    /*---------------------------------------------------------------------*/
+    /*  Now write the pixels to the FITS column.                           */
+    /*  First call the ffXXfYY routine to  (1) convert the datatype        */
+    /*  if necessary, and (2) scale the values by the FITS TSCALn and      */
+    /*  TZEROn linear scaling parameters into a temporary buffer.          */
+    /*---------------------------------------------------------------------*/
+    remain = nelem;           /* remaining number of values to write  */
+    next = 0;                 /* next element in array to be written  */
+    rownum = 0;               /* row number, relative to firstrow     */
+
+    while (remain)
+    {
+        /* limit the number of pixels to process a one time to the number that
+           will fit in the buffer space or to the number of pixels that remain
+           in the current vector, which ever is smaller.
+        */
+        ntodo = (long) minvalue(remain, maxelem);      
+        ntodo = (long) minvalue(ntodo, (repeat - elemnum));
+
+        wrtptr = startpos + ((LONGLONG)rownum * rowlen) + (elemnum * incre);
+
+        ffmbyt(fptr, wrtptr, IGNORE_EOF, status); /* move to write position */
+
+        switch (tcode) 
+        {
+            case (TSHORT):
+              if (writeraw)
+              {
+                /* write raw input bytes without conversion */
+                ffpi2b(fptr, ntodo, incre, &array[next], status);
+              }
+              else
+              {
+                /* convert the raw data before writing to FITS file */
+                ffi2fi2(&array[next], ntodo, scale, zero,
+                        (short *) buffer, status);
+                ffpi2b(fptr, ntodo, incre, (short *) buffer, status);
+              }
+
+              break;
+
+            case (TLONGLONG):
+
+                ffi2fi8(&array[next], ntodo, scale, zero,
+                        (LONGLONG *) buffer, status);
+                ffpi8b(fptr, ntodo, incre, (long *) buffer, status);
+                break;
+
+             case (TBYTE):
+
+                ffi2fi1(&array[next], ntodo, scale, zero,
+                        (unsigned char *) buffer, status);
+                ffpi1b(fptr, ntodo, incre, (unsigned char *) buffer, status);
+                break;
+
+            case (TLONG):
+
+                ffi2fi4(&array[next], ntodo, scale, zero,
+                        (INT32BIT *) buffer, status);
+                ffpi4b(fptr, ntodo, incre, (INT32BIT *) buffer, status);
+                break;
+
+            case (TFLOAT):
+
+                ffi2fr4(&array[next], ntodo, scale, zero,
+                        (float *) buffer, status);
+                ffpr4b(fptr, ntodo, incre, (float *) buffer, status);
+                break;
+
+            case (TDOUBLE):
+                ffi2fr8(&array[next], ntodo, scale, zero,
+                        (double *) buffer, status);
+                ffpr8b(fptr, ntodo, incre, (double *) buffer, status);
+                break;
+
+            case (TSTRING):  /* numerical column in an ASCII table */
+
+                if (cform[1] != 's')  /*  "%s" format is a string */
+                {
+                  ffi2fstr(&array[next], ntodo, scale, zero, cform,
+                          twidth, (char *) buffer, status);
+
+
+                  if (incre == twidth)    /* contiguous bytes */
+                     ffpbyt(fptr, ntodo * twidth, buffer, status);
+                  else
+                     ffpbytoff(fptr, twidth, ntodo, incre - twidth, buffer,
+                            status);
+
+                  break;
+                }
+                /* can't write to string column, so fall thru to default: */
+
+            default:  /*  error trap  */
+                sprintf(message, 
+                    "Cannot write numbers to column %d which has format %s",
+                      colnum,tform);
+                ffpmsg(message);
+                if (hdutype == ASCII_TBL)
+                    return(*status = BAD_ATABLE_FORMAT);
+                else
+                    return(*status = BAD_BTABLE_FORMAT);
+
+        } /* End of switch block */
+
+        /*-------------------------*/
+        /*  Check for fatal error  */
+        /*-------------------------*/
+        if (*status > 0)  /* test for error during previous write operation */
+        {
+         sprintf(message,
+          "Error writing elements %.0f thru %.0f of input data array (ffpcli).",
+             (double) (next+1), (double) (next+ntodo));
+         ffpmsg(message);
+         return(*status);
+        }
+
+        /*--------------------------------------------*/
+        /*  increment the counters for the next loop  */
+        /*--------------------------------------------*/
+        remain -= ntodo;
+        if (remain)
+        {
+            next += ntodo;
+            elemnum += ntodo;
+            if (elemnum == repeat)  /* completed a row; start on next row */
+            {
+                elemnum = 0;
+                rownum++;
+            }
+        }
+    }  /*  End of main while Loop  */
+
+
+    /*--------------------------------*/
+    /*  check for numerical overflow  */
+    /*--------------------------------*/
+    if (*status == OVERFLOW_ERR)
+    {
+       ffpmsg(
+       "Numerical overflow during type conversion while writing FITS data.");
+       *status = NUM_OVERFLOW;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpcni( fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  colnum,     /* I - number of column to write (1 = 1st col) */
+            LONGLONG  firstrow,  /* I - first row to write (1 = 1st row)        */
+            LONGLONG  firstelem, /* I - first vector element to write (1 = 1st) */
+            LONGLONG  nelem,     /* I - number of values to write               */
+            short *array,    /* I - array of values to write                */
+            short  nulvalue, /* I - value used to flag undefined pixels     */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of elements to the specified column of a table.  Any input
+  pixels equal to the value of nulvalue will be replaced by the appropriate
+  null value in the output FITS file. 
+
+  The input array of values will be converted to the datatype of the column 
+  and will be inverse-scaled by the FITS TSCALn and TZEROn values if necessary
+*/
+{
+    tcolumn *colptr;
+    long  ngood = 0, nbad = 0, ii;
+    LONGLONG repeat, first, fstelm, fstrow;
+    int tcode, overflow = 0;
+
+    if (*status > 0)
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+    {
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    }
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+    {
+        if ( ffrdef(fptr, status) > 0)               /* rescan header */
+            return(*status);
+    }
+
+    colptr  = (fptr->Fptr)->tableptr;   /* point to first column */
+    colptr += (colnum - 1);     /* offset to correct column structure */
+
+    tcode  = colptr->tdatatype;
+
+    if (tcode > 0)
+       repeat = colptr->trepeat;  /* repeat count for this column */
+    else
+       repeat = firstelem -1 + nelem;  /* variable length arrays */
+
+    /* if variable length array, first write the whole input vector, 
+       then go back and fill in the nulls */
+    if (tcode < 0) {
+      if (ffpcli(fptr, colnum, firstrow, firstelem, nelem, array, status) > 0) {
+        if (*status == NUM_OVERFLOW) 
+	{
+	  /* ignore overflows, which are possibly the null pixel values */
+	  /*  overflow = 1;   */
+	  *status = 0;
+	} else { 
+          return(*status);
+	}
+      }
+    }
+
+    /* absolute element number in the column */
+    first = (firstrow - 1) * repeat + firstelem;
+
+    for (ii = 0; ii < nelem; ii++)
+    {
+      if (array[ii] != nulvalue)  /* is this a good pixel? */
+      {
+         if (nbad)  /* write previous string of bad pixels */
+         {
+            fstelm = ii - nbad + first;  /* absolute element number */
+            fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+            fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+            if (ffpclu(fptr, colnum, fstrow, fstelm, nbad, status) > 0)
+                return(*status);
+
+            nbad=0;
+         }
+
+         ngood = ngood +1;  /* the consecutive number of good pixels */
+      }
+      else
+      {
+         if (ngood)  /* write previous string of good pixels */
+         {
+            fstelm = ii - ngood + first;  /* absolute element number */
+            fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+            fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+            if (tcode > 0) {  /* variable length arrays have already been written */
+              if (ffpcli(fptr, colnum, fstrow, fstelm, ngood, &array[ii-ngood],
+                status) > 0) {
+		if (*status == NUM_OVERFLOW) 
+		{
+		  overflow = 1;
+		  *status = 0;
+		} else { 
+                  return(*status);
+		}
+	      }
+	    }
+            ngood=0;
+         }
+
+         nbad = nbad +1;  /* the consecutive number of bad pixels */
+      }
+    }
+
+    /* finished loop;  now just write the last set of pixels */
+
+    if (ngood)  /* write last string of good pixels */
+    {
+      fstelm = ii - ngood + first;  /* absolute element number */
+      fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+      fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+      if (tcode > 0) {  /* variable length arrays have already been written */
+        ffpcli(fptr, colnum, fstrow, fstelm, ngood, &array[ii-ngood], status);
+      }
+    }
+    else if (nbad) /* write last string of bad pixels */
+    {
+      fstelm = ii - nbad + first;  /* absolute element number */
+      fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+      fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+      ffpclu(fptr, colnum, fstrow, fstelm, nbad, status);
+    }
+
+    if (*status <= 0) {
+      if (overflow) {
+        *status = NUM_OVERFLOW;
+      }
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffi2fi1(short *input,          /* I - array of values to be converted  */
+            long ntodo,            /* I - number of elements in the array  */
+            double scale,          /* I - FITS TSCALn or BSCALE value      */
+            double zero,           /* I - FITS TZEROn or BZERO  value      */
+            unsigned char *output, /* O - output array of converted values */
+            int *status)           /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            if (input[ii] < 0)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = 0;
+            }
+            else if (input[ii] > UCHAR_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = UCHAR_MAX;
+            }
+            else
+                output[ii] = (unsigned char) input[ii];
+        }
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = (input[ii] - zero) / scale;
+
+            if (dvalue < DUCHAR_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = 0;
+            }
+            else if (dvalue > DUCHAR_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = UCHAR_MAX;
+            }
+            else
+                output[ii] = (unsigned char) (dvalue + .5);
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffi2fi2(short *input,       /* I - array of values to be converted  */
+            long ntodo,         /* I - number of elements in the array  */
+            double scale,       /* I - FITS TSCALn or BSCALE value      */
+            double zero,        /* I - FITS TZEROn or BZERO  value      */
+            short *output,      /* O - output array of converted values */
+            int *status)        /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        memcpy(output, input, ntodo * sizeof(short) );
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = (input[ii] - zero) / scale;
+
+            if (dvalue < DSHRT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = SHRT_MIN;
+            }
+            else if (dvalue > DSHRT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = SHRT_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0)
+                    output[ii] = (short) (dvalue + .5);
+                else
+                    output[ii] = (short) (dvalue - .5);
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffi2fi4(short *input,      /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            INT32BIT *output,  /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+            output[ii] = (INT32BIT) input[ii];   /* just copy input to output */
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = (input[ii] - zero) / scale;
+
+            if (dvalue < DINT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = INT32_MIN;
+            }
+            else if (dvalue > DINT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = INT32_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0)
+                    output[ii] = (INT32BIT) (dvalue + .5);
+                else
+                    output[ii] = (INT32BIT) (dvalue - .5);
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffi2fi8(short *input,      /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            LONGLONG *output,  /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+                output[ii] = input[ii];
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = (input[ii] - zero) / scale;
+
+            if (dvalue < DLONGLONG_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = LONGLONG_MIN;
+            }
+            else if (dvalue > DLONGLONG_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = LONGLONG_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0)
+                    output[ii] = (LONGLONG) (dvalue + .5);
+                else
+                    output[ii] = (LONGLONG) (dvalue - .5);
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffi2fr4(short *input,      /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            float *output,     /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required.
+*/
+{
+    long ii;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (float) input[ii];
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+            output[ii] = (float) ((input[ii] - zero) / scale);
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffi2fr8(short *input,      /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            double *output,    /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required.
+*/
+{
+    long ii;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (double) input[ii];
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+            output[ii] = (input[ii] - zero) / scale;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffi2fstr(short *input,     /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            char *cform,       /* I - format for output string values  */
+            long twidth,       /* I - width of each field, in chars    */
+            char *output,      /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do scaling if required.
+*/
+{
+    long ii;
+    double dvalue;
+    char *cptr, *tptr;
+    
+    cptr = output;
+    tptr = output;
+
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+        {
+           sprintf(output, cform, (double) input[ii]);
+           output += twidth;
+
+           if (*output)  /* if this char != \0, then overflow occurred */
+              *status = OVERFLOW_ERR;
+        }
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+          dvalue = (input[ii] - zero) / scale;
+          sprintf(output, cform, dvalue);
+          output += twidth;
+
+          if (*output)  /* if this char != \0, then overflow occurred */
+            *status = OVERFLOW_ERR;
+        }
+    }
+
+    /* replace any commas with periods (e.g., in French locale) */
+    while ((cptr = strchr(cptr, ','))) *cptr = '.';
+
+    return(*status);
+}
diff --git a/src/plugins/cfitsio/putcolj.c b/src/plugins/cfitsio/putcolj.c
new file mode 100644
index 0000000..b7f6713
--- /dev/null
+++ b/src/plugins/cfitsio/putcolj.c
@@ -0,0 +1,1990 @@
+/*  This file, putcolj.c, contains routines that write data elements to    */
+/*  a FITS image or table, with long datatype.                             */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+
+#include <limits.h>
+#include <string.h>
+#include <stdlib.h>
+#include "fitsio2.h"
+
+/*--------------------------------------------------------------------------*/
+int ffpprj( fitsfile *fptr,  /* I - FITS file pointer                       */
+            long  group,     /* I - group to write(1 = 1st group)           */
+            LONGLONG  firstelem, /* I - first vector element to write(1 = 1st)  */
+            LONGLONG  nelem,     /* I - number of values to write               */
+            long  *array,    /* I - array of values that are written        */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being written).
+*/
+{
+    long row;
+    long nullvalue;
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        fits_write_compressed_pixels(fptr, TLONG, firstelem, nelem,
+            0, array, &nullvalue, status);
+        return(*status);
+    }
+
+    row=maxvalue(1,group);
+
+    ffpclj(fptr, 2, row, firstelem, nelem, array, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffppnj( fitsfile *fptr,  /* I - FITS file pointer                       */
+            long  group,     /* I - group to write(1 = 1st group)           */
+            LONGLONG  firstelem, /* I - first vector element to write(1 = 1st)  */
+            LONGLONG  nelem,     /* I - number of values to write               */
+            long  *array,    /* I - array of values that are written        */
+            long  nulval,    /* I - undefined pixel value                   */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being written).  Any array values
+  that are equal to the value of nulval will be replaced with the null
+  pixel value that is appropriate for this column.
+*/
+{
+    long row;
+    long nullvalue;
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        nullvalue = nulval;  /* set local variable */
+        fits_write_compressed_pixels(fptr, TLONG, firstelem, nelem,
+            1, array, &nullvalue, status);
+        return(*status);
+    }
+
+    row=maxvalue(1,group);
+
+    ffpcnj(fptr, 2, row, firstelem, nelem, array, nulval, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffp2dj(fitsfile *fptr,   /* I - FITS file pointer                     */
+           long  group,      /* I - group to write(1 = 1st group)         */
+           LONGLONG  ncols,      /* I - number of pixels in each row of array */
+           LONGLONG  naxis1,     /* I - FITS image NAXIS1 value               */
+           LONGLONG  naxis2,     /* I - FITS image NAXIS2 value               */
+           long  *array,     /* I - array to be written                   */
+           int  *status)     /* IO - error status                         */
+/*
+  Write an entire 2-D array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being written).
+*/
+{
+
+    /* call the 3D writing routine, with the 3rd dimension = 1 */
+
+    ffp3dj(fptr, group, ncols, naxis2, naxis1, naxis2, 1, array, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffp3dj(fitsfile *fptr,   /* I - FITS file pointer                     */
+           long  group,      /* I - group to write(1 = 1st group)         */
+           LONGLONG  ncols,      /* I - number of pixels in each row of array */
+           LONGLONG  nrows,      /* I - number of rows in each plane of array */
+           LONGLONG  naxis1,     /* I - FITS image NAXIS1 value               */
+           LONGLONG  naxis2,     /* I - FITS image NAXIS2 value               */
+           LONGLONG  naxis3,     /* I - FITS image NAXIS3 value               */
+           long  *array,     /* I - array to be written                   */
+           int  *status)     /* IO - error status                         */
+/*
+  Write an entire 3-D cube of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being written).
+*/
+{
+    long tablerow, ii, jj;
+    long fpixel[3]= {1,1,1}, lpixel[3];
+    LONGLONG nfits, narray;
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+           
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+        lpixel[0] = (long) ncols;
+        lpixel[1] = (long) nrows;
+        lpixel[2] = (long) naxis3;
+       
+        fits_write_compressed_img(fptr, TLONG, fpixel, lpixel,
+            0,  array, NULL, status);
+    
+        return(*status);
+    }
+
+    tablerow=maxvalue(1,group);
+
+    if (ncols == naxis1 && nrows == naxis2)  /* arrays have same size? */
+    {
+      /* all the image pixels are contiguous, so write all at once */
+      ffpclj(fptr, 2, tablerow, 1L, naxis1 * naxis2 * naxis3, array, status);
+      return(*status);
+    }
+
+    if (ncols < naxis1 || nrows < naxis2)
+       return(*status = BAD_DIMEN);
+
+    nfits = 1;   /* next pixel in FITS image to write to */
+    narray = 0;  /* next pixel in input array to be written */
+
+    /* loop over naxis3 planes in the data cube */
+    for (jj = 0; jj < naxis3; jj++)
+    {
+      /* loop over the naxis2 rows in the FITS image, */
+      /* writing naxis1 pixels to each row            */
+
+      for (ii = 0; ii < naxis2; ii++)
+      {
+       if (ffpclj(fptr, 2, tablerow, nfits, naxis1,&array[narray],status) > 0)
+         return(*status);
+
+       nfits += naxis1;
+       narray += ncols;
+      }
+      narray += (nrows - naxis2) * ncols;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpssj(fitsfile *fptr,   /* I - FITS file pointer                       */
+           long  group,      /* I - group to write(1 = 1st group)           */
+           long  naxis,      /* I - number of data axes in array            */
+           long  *naxes,     /* I - size of each FITS axis                  */
+           long  *fpixel,    /* I - 1st pixel in each axis to write (1=1st) */
+           long  *lpixel,    /* I - last pixel in each axis to write        */
+           long *array,      /* I - array to be written                     */
+           int  *status)     /* IO - error status                           */
+/*
+  Write a subsection of pixels to the primary array or image.
+  A subsection is defined to be any contiguous rectangular
+  array of pixels within the n-dimensional FITS data file.
+  Data conversion and scaling will be performed if necessary 
+  (e.g, if the datatype of the FITS array is not the same as
+  the array being written).
+*/
+{
+    long tablerow;
+    LONGLONG fpix[7], dimen[7], astart, pstart;
+    LONGLONG off2, off3, off4, off5, off6, off7;
+    LONGLONG st10, st20, st30, st40, st50, st60, st70;
+    LONGLONG st1, st2, st3, st4, st5, st6, st7;
+    long ii, i1, i2, i3, i4, i5, i6, i7, irange[7];
+
+    if (*status > 0)
+        return(*status);
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        fits_write_compressed_img(fptr, TLONG, fpixel, lpixel,
+            0,  array, NULL, status);
+    
+        return(*status);
+    }
+
+    if (naxis < 1 || naxis > 7)
+      return(*status = BAD_DIMEN);
+
+    tablerow=maxvalue(1,group);
+
+     /* calculate the size and number of loops to perform in each dimension */
+    for (ii = 0; ii < 7; ii++)
+    {
+      fpix[ii]=1;
+      irange[ii]=1;
+      dimen[ii]=1;
+    }
+
+    for (ii = 0; ii < naxis; ii++)
+    {    
+      fpix[ii]=fpixel[ii];
+      irange[ii]=lpixel[ii]-fpixel[ii]+1;
+      dimen[ii]=naxes[ii];
+    }
+
+    i1=irange[0];
+
+    /* compute the pixel offset between each dimension */
+    off2 =     dimen[0];
+    off3 = off2 * dimen[1];
+    off4 = off3 * dimen[2];
+    off5 = off4 * dimen[3];
+    off6 = off5 * dimen[4];
+    off7 = off6 * dimen[5];
+
+    st10 = fpix[0];
+    st20 = (fpix[1] - 1) * off2;
+    st30 = (fpix[2] - 1) * off3;
+    st40 = (fpix[3] - 1) * off4;
+    st50 = (fpix[4] - 1) * off5;
+    st60 = (fpix[5] - 1) * off6;
+    st70 = (fpix[6] - 1) * off7;
+
+    /* store the initial offset in each dimension */
+    st1 = st10;
+    st2 = st20;
+    st3 = st30;
+    st4 = st40;
+    st5 = st50;
+    st6 = st60;
+    st7 = st70;
+
+    astart = 0;
+
+    for (i7 = 0; i7 < irange[6]; i7++)
+    {
+     for (i6 = 0; i6 < irange[5]; i6++)
+     {
+      for (i5 = 0; i5 < irange[4]; i5++)
+      {
+       for (i4 = 0; i4 < irange[3]; i4++)
+       {
+        for (i3 = 0; i3 < irange[2]; i3++)
+        {
+         pstart = st1 + st2 + st3 + st4 + st5 + st6 + st7;
+
+         for (i2 = 0; i2 < irange[1]; i2++)
+         {
+           if (ffpclj(fptr, 2, tablerow, pstart, i1, &array[astart],
+              status) > 0)
+              return(*status);
+
+           astart += i1;
+           pstart += off2;
+         }
+         st2 = st20;
+         st3 = st3+off3;    
+        }
+        st3 = st30;
+        st4 = st4+off4;
+       }
+       st4 = st40;
+       st5 = st5+off5;
+      }
+      st5 = st50;
+      st6 = st6+off6;
+     }
+     st6 = st60;
+     st7 = st7+off7;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpgpj( fitsfile *fptr,   /* I - FITS file pointer                      */
+            long  group,      /* I - group to write(1 = 1st group)          */
+            long  firstelem,  /* I - first vector element to write(1 = 1st) */
+            long  nelem,      /* I - number of values to write              */
+            long  *array,     /* I - array of values that are written       */
+            int  *status)     /* IO - error status                          */
+/*
+  Write an array of group parameters to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being written).
+*/
+{
+    long row;
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffpclj(fptr, 1L, row, firstelem, nelem, array, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpclj( fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  colnum,     /* I - number of column to write (1 = 1st col) */
+            LONGLONG  firstrow,  /* I - first row to write (1 = 1st row)        */
+            LONGLONG  firstelem, /* I - first vector element to write (1 = 1st) */
+            LONGLONG  nelem,     /* I - number of values to write               */
+            long  *array,    /* I - array of values to write                */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of values to a column in the current FITS HDU.
+  The column number may refer to a real column in an ASCII or binary table, 
+  or it may refer to a virtual column in a 1 or more grouped FITS primary
+  array.  FITSIO treats a primary array as a binary table
+  with 2 vector columns: the first column contains the group parameters (often
+  with length = 0) and the second column contains the array of image pixels.
+  Each row of the table represents a group in the case of multigroup FITS
+  images.
+
+  The input array of values will be converted to the datatype of the column 
+  and will be inverse-scaled by the FITS TSCALn and TZEROn values if necessary.
+*/
+{
+    int tcode, maxelem, hdutype, writeraw;
+    long twidth, incre;
+    long ntodo;
+    LONGLONG repeat, startpos, elemnum, wrtptr, rowlen, rownum, remain, next, tnull;
+    double scale, zero;
+    char tform[20], cform[20];
+    char message[FLEN_ERRMSG];
+
+    char snull[20];   /*  the FITS null value  */
+
+    double cbuff[DBUFFSIZE / sizeof(double)]; /* align cbuff on word boundary */
+    void *buffer;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    buffer = cbuff;
+
+    /*---------------------------------------------------*/
+    /*  Check input and get parameters about the column: */
+    /*---------------------------------------------------*/
+    if (ffgcprll( fptr, colnum, firstrow, firstelem, nelem, 1, &scale, &zero,
+        tform, &twidth, &tcode, &maxelem, &startpos,  &elemnum, &incre,
+        &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0)
+        return(*status);
+
+    if (tcode == TSTRING)   
+         ffcfmt(tform, cform);     /* derive C format for writing strings */
+
+    /*
+       if there is no scaling and the native machine format is not byteswapped
+       then we can simply write the raw data bytes into the FITS file if the
+       datatype of the FITS column is the same as the input values.  Otherwise
+       we must convert the raw values into the scaled and/or machine dependent
+       format in a temporary buffer that has been allocated for this purpose.
+    */
+    if (scale == 1. && zero == 0. && 
+       MACHINE == NATIVE && tcode == TLONG && LONGSIZE == 32)
+    {
+        writeraw = 1;
+        maxelem = (int) nelem;  /* we can write the entire array at one time */
+    }
+    else
+        writeraw = 0;
+
+    /*---------------------------------------------------------------------*/
+    /*  Now write the pixels to the FITS column.                           */
+    /*  First call the ffXXfYY routine to  (1) convert the datatype        */
+    /*  if necessary, and (2) scale the values by the FITS TSCALn and      */
+    /*  TZEROn linear scaling parameters into a temporary buffer.          */
+    /*---------------------------------------------------------------------*/
+    remain = nelem;           /* remaining number of values to write  */
+    next = 0;                 /* next element in array to be written  */
+    rownum = 0;               /* row number, relative to firstrow     */
+
+    while (remain)
+    {
+        /* limit the number of pixels to process a one time to the number that
+           will fit in the buffer space or to the number of pixels that remain
+           in the current vector, which ever is smaller.
+        */
+        ntodo = (long) minvalue(remain, maxelem);      
+        ntodo = (long) minvalue(ntodo, (repeat - elemnum));
+
+        wrtptr = startpos + ((LONGLONG)rownum * rowlen) + (elemnum * incre);
+
+        ffmbyt(fptr, wrtptr, IGNORE_EOF, status); /* move to write position */
+
+        switch (tcode) 
+        {
+            case (TLONG):
+              if (writeraw)
+              {
+                /* write raw input bytes without conversion */
+                ffpi4b(fptr, ntodo, incre, (INT32BIT *) &array[next], status);
+              }
+              else
+              {
+                /* convert the raw data before writing to FITS file */
+                ffi4fi4(&array[next], ntodo, scale, zero,
+                        (INT32BIT *) buffer, status);
+                ffpi4b(fptr, ntodo, incre, (INT32BIT *) buffer, status);
+              }
+
+              break;
+
+            case (TLONGLONG):
+
+                fflongfi8(&array[next], ntodo, scale, zero,
+                        (LONGLONG *) buffer, status);
+                ffpi8b(fptr, ntodo, incre, (long *) buffer, status);
+                break;
+
+            case (TBYTE):
+ 
+                ffi4fi1(&array[next], ntodo, scale, zero,
+                        (unsigned char *) buffer, status);
+                ffpi1b(fptr, ntodo, incre, (unsigned char *) buffer, status);
+                break;
+
+            case (TSHORT):
+
+                ffi4fi2(&array[next], ntodo, scale, zero,
+                        (short *) buffer, status);
+                ffpi2b(fptr, ntodo, incre, (short *) buffer, status);
+                break;
+
+            case (TFLOAT):
+
+                ffi4fr4(&array[next], ntodo, scale, zero,
+                        (float *) buffer, status);
+                ffpr4b(fptr, ntodo, incre, (float *) buffer, status);
+                break;
+
+            case (TDOUBLE):
+                ffi4fr8(&array[next], ntodo, scale, zero,
+                       (double *) buffer, status);
+                ffpr8b(fptr, ntodo, incre, (double *) buffer, status);
+                break;
+
+            case (TSTRING):  /* numerical column in an ASCII table */
+
+                if (cform[1] != 's')  /*  "%s" format is a string */
+                {
+                  ffi4fstr(&array[next], ntodo, scale, zero, cform,
+                          twidth, (char *) buffer, status);
+
+                  if (incre == twidth)    /* contiguous bytes */
+                     ffpbyt(fptr, ntodo * twidth, buffer, status);
+                  else
+                     ffpbytoff(fptr, twidth, ntodo, incre - twidth, buffer,
+                            status);
+
+                  break;
+                }
+                /* can't write to string column, so fall thru to default: */
+
+            default:  /*  error trap  */
+                sprintf(message, 
+                     "Cannot write numbers to column %d which has format %s",
+                      colnum,tform);
+                ffpmsg(message);
+                if (hdutype == ASCII_TBL)
+                    return(*status = BAD_ATABLE_FORMAT);
+                else
+                    return(*status = BAD_BTABLE_FORMAT);
+
+        } /* End of switch block */
+
+        /*-------------------------*/
+        /*  Check for fatal error  */
+        /*-------------------------*/
+        if (*status > 0)  /* test for error during previous write operation */
+        {
+          sprintf(message,
+          "Error writing elements %.0f thru %.0f of input data array (ffpclj).",
+              (double) (next+1), (double) (next+ntodo));
+          ffpmsg(message);
+          return(*status);
+        }
+
+        /*--------------------------------------------*/
+        /*  increment the counters for the next loop  */
+        /*--------------------------------------------*/
+        remain -= ntodo;
+        if (remain)
+        {
+            next += ntodo;
+            elemnum += ntodo;
+            if (elemnum == repeat)  /* completed a row; start on next row */
+            {
+                elemnum = 0;
+                rownum++;
+            }
+        }
+    }  /*  End of main while Loop  */
+
+
+    /*--------------------------------*/
+    /*  check for numerical overflow  */
+    /*--------------------------------*/
+    if (*status == OVERFLOW_ERR)
+    {
+        ffpmsg(
+        "Numerical overflow during type conversion while writing FITS data.");
+        *status = NUM_OVERFLOW;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpcnj( fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  colnum,     /* I - number of column to write (1 = 1st col) */
+            LONGLONG  firstrow,  /* I - first row to write (1 = 1st row)        */
+            LONGLONG  firstelem, /* I - first vector element to write (1 = 1st) */
+            LONGLONG  nelem,     /* I - number of values to write               */
+            long  *array,    /* I - array of values to write                */
+            long   nulvalue, /* I - value used to flag undefined pixels     */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of elements to the specified column of a table.  Any input
+  pixels equal to the value of nulvalue will be replaced by the appropriate
+  null value in the output FITS file. 
+
+  The input array of values will be converted to the datatype of the column 
+  and will be inverse-scaled by the FITS TSCALn and TZEROn values if necessary
+*/
+{
+    tcolumn *colptr;
+    long  ngood = 0, nbad = 0, ii;
+    LONGLONG repeat, first, fstelm, fstrow;
+    int tcode, overflow = 0;
+ 
+    if (*status > 0)
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+    {
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    }
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+    {
+        if ( ffrdef(fptr, status) > 0)               /* rescan header */
+            return(*status);
+    }
+
+    colptr  = (fptr->Fptr)->tableptr;   /* point to first column */
+    colptr += (colnum - 1);     /* offset to correct column structure */
+
+    tcode  = colptr->tdatatype;
+
+    if (tcode > 0)
+       repeat = colptr->trepeat;  /* repeat count for this column */
+    else
+       repeat = firstelem -1 + nelem;  /* variable length arrays */
+
+    /* if variable length array, first write the whole input vector, 
+       then go back and fill in the nulls */
+    if (tcode < 0) {
+      if (ffpclj(fptr, colnum, firstrow, firstelem, nelem, array, status) > 0) {
+        if (*status == NUM_OVERFLOW) 
+	{
+	  /* ignore overflows, which are possibly the null pixel values */
+	  /*  overflow = 1;   */
+	  *status = 0;
+	} else { 
+          return(*status);
+	}
+      }
+    }
+
+    /* absolute element number in the column */
+    first = (firstrow - 1) * repeat + firstelem;
+
+    for (ii = 0; ii < nelem; ii++)
+    {
+      if (array[ii] != nulvalue)  /* is this a good pixel? */
+      {
+         if (nbad)  /* write previous string of bad pixels */
+         {
+            fstelm = ii - nbad + first;  /* absolute element number */
+            fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+            fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+	  
+            if (ffpclu(fptr, colnum, fstrow, fstelm, nbad, status) > 0)
+                return(*status);
+
+            nbad=0;
+         }
+
+         ngood = ngood + 1;  /* the consecutive number of good pixels */
+      }
+      else
+      {
+         if (ngood)  /* write previous string of good pixels */
+         {
+            fstelm = ii - ngood + first;  /* absolute element number */
+            fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+            fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+            if (tcode > 0) {  /* variable length arrays have already been written */
+              if (ffpclj(fptr, colnum, fstrow, fstelm, ngood, &array[ii-ngood],
+                status) > 0) {
+		if (*status == NUM_OVERFLOW) 
+		{
+		  overflow = 1;
+		  *status = 0;
+		} else { 
+                  return(*status);
+		}
+	      }
+	    }
+            ngood=0;
+         }
+
+         nbad = nbad +1;  /* the consecutive number of bad pixels */
+      }
+    }
+
+    /* finished loop;  now just write the last set of pixels */
+
+    if (ngood)  /* write last string of good pixels */
+    {
+      fstelm = ii - ngood + first;  /* absolute element number */
+      fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+      fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+      if (tcode > 0) {  /* variable length arrays have already been written */
+        ffpclj(fptr, colnum, fstrow, fstelm, ngood, &array[ii-ngood], status);
+      }
+    }
+    else if (nbad) /* write last string of bad pixels */
+    {
+      fstelm = ii - nbad + first;  /* absolute element number */
+      fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+      fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+      ffpclu(fptr, colnum, fstrow, fstelm, nbad, status);
+    }
+
+    if (*status <= 0) {
+      if (overflow) {
+        *status = NUM_OVERFLOW;
+      }
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffi4fi1(long *input,           /* I - array of values to be converted  */
+            long ntodo,            /* I - number of elements in the array  */
+            double scale,          /* I - FITS TSCALn or BSCALE value      */
+            double zero,           /* I - FITS TZEROn or BZERO  value      */
+            unsigned char *output, /* O - output array of converted values */
+            int *status)           /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required.
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            if (input[ii] < 0)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = 0;
+            }
+            else if (input[ii] > UCHAR_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = UCHAR_MAX;
+            }
+            else
+                output[ii] = (unsigned char) input[ii];
+        }
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = (input[ii] - zero) / scale;
+
+            if (dvalue < DUCHAR_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = 0;
+            }
+            else if (dvalue > DUCHAR_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = UCHAR_MAX;
+            }
+            else
+                output[ii] = (unsigned char) (dvalue + .5);
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffi4fi2(long *input,       /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            short *output,     /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required.
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            if (input[ii] < SHRT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = SHRT_MIN;
+            }
+            else if (input[ii] > SHRT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = SHRT_MAX;
+            }
+            else
+                output[ii] = (short) input[ii];
+        }
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = (input[ii] - zero) / scale;
+
+            if (dvalue < DSHRT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = SHRT_MIN;
+            }
+            else if (dvalue > DSHRT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = SHRT_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0)
+                    output[ii] = (short) (dvalue + .5);
+                else
+                    output[ii] = (short) (dvalue - .5);
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffi4fi4(long *input,       /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            INT32BIT *output,  /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (INT32BIT) input[ii];
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = (input[ii] - zero) / scale;
+
+            if (dvalue < DINT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = INT32_MIN;
+            }
+            else if (dvalue > DINT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = INT32_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0)
+                    output[ii] = (INT32BIT) (dvalue + .5);
+                else
+                    output[ii] = (INT32BIT) (dvalue - .5);
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fflongfi8(long *input,       /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            LONGLONG *output,      /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+                output[ii] = input[ii];
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = (input[ii] - zero) / scale;
+
+            if (dvalue < DLONGLONG_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = LONGLONG_MIN;
+            }
+            else if (dvalue > DLONGLONG_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = LONGLONG_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0)
+                    output[ii] = (LONGLONG) (dvalue + .5);
+                else
+                    output[ii] = (LONGLONG) (dvalue - .5);
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffi4fr4(long *input,       /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            float *output,     /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required.
+*/
+{
+    long ii;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (float) input[ii];
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+            output[ii] = (float) ((input[ii] - zero) / scale);
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffi4fr8(long *input,       /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            double *output,    /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required.
+*/
+{
+    long ii;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (double) input[ii];
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+            output[ii] = (input[ii] - zero) / scale;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffi4fstr(long *input,      /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            char *cform,       /* I - format for output string values  */
+            long twidth,       /* I - width of each field, in chars    */
+            char *output,      /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do scaling if required.
+*/
+{
+    long ii;
+    double dvalue;
+    char *cptr;
+
+    cptr = output;
+    
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+        {
+           sprintf(output, cform, (double) input[ii]);
+           output += twidth;
+
+           if (*output)  /* if this char != \0, then overflow occurred */
+              *status = OVERFLOW_ERR;
+        }
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+          dvalue = (input[ii] - zero) / scale;
+          sprintf(output, cform, dvalue);
+          output += twidth;
+
+          if (*output)  /* if this char != \0, then overflow occurred */
+            *status = OVERFLOW_ERR;
+        }
+    }
+
+    /* replace any commas with periods (e.g., in French locale) */
+    while ((cptr = strchr(cptr, ','))) *cptr = '.';
+
+    return(*status);
+}
+
+/* ======================================================================== */
+/*      the following routines support the 'long long' data type            */
+/* ======================================================================== */
+
+/*--------------------------------------------------------------------------*/
+int ffpprjj(fitsfile *fptr,  /* I - FITS file pointer                       */
+            long  group,     /* I - group to write(1 = 1st group)           */
+            LONGLONG  firstelem, /* I - first vector element to write(1 = 1st)  */
+            LONGLONG  nelem,     /* I - number of values to write               */
+            LONGLONG  *array, /* I - array of values that are written       */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being written).
+*/
+{
+    long row;
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        ffpmsg("writing to compressed image is not supported");
+
+        return(*status = DATA_COMPRESSION_ERR);
+    }
+
+    row=maxvalue(1,group);
+
+    ffpcljj(fptr, 2, row, firstelem, nelem, array, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffppnjj(fitsfile *fptr,  /* I - FITS file pointer                       */
+            long  group,     /* I - group to write(1 = 1st group)           */
+            LONGLONG  firstelem, /* I - first vector element to write(1 = 1st)  */
+            LONGLONG  nelem,     /* I - number of values to write               */
+            LONGLONG  *array, /* I - array of values that are written       */
+            LONGLONG  nulval,    /* I - undefined pixel value                   */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being written).  Any array values
+  that are equal to the value of nulval will be replaced with the null
+  pixel value that is appropriate for this column.
+*/
+{
+    long row;
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        ffpmsg("writing to compressed image is not supported");
+
+        return(*status = DATA_COMPRESSION_ERR);
+    }
+
+    row=maxvalue(1,group);
+
+    ffpcnjj(fptr, 2, row, firstelem, nelem, array, nulval, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffp2djj(fitsfile *fptr,  /* I - FITS file pointer                     */
+           long  group,      /* I - group to write(1 = 1st group)         */
+           LONGLONG  ncols,      /* I - number of pixels in each row of array */
+           LONGLONG  naxis1,     /* I - FITS image NAXIS1 value               */
+           LONGLONG  naxis2,     /* I - FITS image NAXIS2 value               */
+           LONGLONG  *array, /* I - array to be written                   */
+           int  *status)     /* IO - error status                         */
+/*
+  Write an entire 2-D array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being written).
+*/
+{
+
+    /* call the 3D writing routine, with the 3rd dimension = 1 */
+
+    ffp3djj(fptr, group, ncols, naxis2, naxis1, naxis2, 1, array, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffp3djj(fitsfile *fptr,  /* I - FITS file pointer                     */
+           long  group,      /* I - group to write(1 = 1st group)         */
+           LONGLONG  ncols,      /* I - number of pixels in each row of array */
+           LONGLONG  nrows,      /* I - number of rows in each plane of array */
+           LONGLONG  naxis1,     /* I - FITS image NAXIS1 value               */
+           LONGLONG  naxis2,     /* I - FITS image NAXIS2 value               */
+           LONGLONG  naxis3,     /* I - FITS image NAXIS3 value               */
+           LONGLONG  *array, /* I - array to be written                   */
+           int  *status)     /* IO - error status                         */
+/*
+  Write an entire 3-D cube of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being written).
+*/
+{
+    long tablerow, ii, jj;
+    LONGLONG nfits, narray;
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        ffpmsg("writing to compressed image is not supported");
+
+        return(*status = DATA_COMPRESSION_ERR);
+    }
+
+    tablerow=maxvalue(1,group);
+
+    if (ncols == naxis1 && nrows == naxis2)  /* arrays have same size? */
+    {
+      /* all the image pixels are contiguous, so write all at once */
+      ffpcljj(fptr, 2, tablerow, 1L, naxis1 * naxis2 * naxis3, array, status);
+      return(*status);
+    }
+
+    if (ncols < naxis1 || nrows < naxis2)
+       return(*status = BAD_DIMEN);
+
+    nfits = 1;   /* next pixel in FITS image to write to */
+    narray = 0;  /* next pixel in input array to be written */
+
+    /* loop over naxis3 planes in the data cube */
+    for (jj = 0; jj < naxis3; jj++)
+    {
+      /* loop over the naxis2 rows in the FITS image, */
+      /* writing naxis1 pixels to each row            */
+
+      for (ii = 0; ii < naxis2; ii++)
+      {
+       if (ffpcljj(fptr, 2, tablerow, nfits, naxis1,&array[narray],status) > 0)
+         return(*status);
+
+       nfits += naxis1;
+       narray += ncols;
+      }
+      narray += (nrows - naxis2) * ncols;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpssjj(fitsfile *fptr,  /* I - FITS file pointer                       */
+           long  group,      /* I - group to write(1 = 1st group)           */
+           long  naxis,      /* I - number of data axes in array            */
+           long  *naxes,     /* I - size of each FITS axis                  */
+           long  *fpixel,    /* I - 1st pixel in each axis to write (1=1st) */
+           long  *lpixel,    /* I - last pixel in each axis to write        */
+           LONGLONG *array,  /* I - array to be written                     */
+           int  *status)     /* IO - error status                           */
+/*
+  Write a subsection of pixels to the primary array or image.
+  A subsection is defined to be any contiguous rectangular
+  array of pixels within the n-dimensional FITS data file.
+  Data conversion and scaling will be performed if necessary 
+  (e.g, if the datatype of the FITS array is not the same as
+  the array being written).
+*/
+{
+    long tablerow;
+    LONGLONG fpix[7], dimen[7], astart, pstart;
+    LONGLONG off2, off3, off4, off5, off6, off7;
+    LONGLONG st10, st20, st30, st40, st50, st60, st70;
+    LONGLONG st1, st2, st3, st4, st5, st6, st7;
+    long ii, i1, i2, i3, i4, i5, i6, i7, irange[7];
+
+    if (*status > 0)
+        return(*status);
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        ffpmsg("writing to compressed image is not supported");
+
+
+        return(*status = DATA_COMPRESSION_ERR);
+    }
+
+    if (naxis < 1 || naxis > 7)
+      return(*status = BAD_DIMEN);
+
+    tablerow=maxvalue(1,group);
+
+     /* calculate the size and number of loops to perform in each dimension */
+    for (ii = 0; ii < 7; ii++)
+    {
+      fpix[ii]=1;
+      irange[ii]=1;
+      dimen[ii]=1;
+    }
+
+    for (ii = 0; ii < naxis; ii++)
+    {    
+      fpix[ii]=fpixel[ii];
+      irange[ii]=lpixel[ii]-fpixel[ii]+1;
+      dimen[ii]=naxes[ii];
+    }
+
+    i1=irange[0];
+
+    /* compute the pixel offset between each dimension */
+    off2 =     dimen[0];
+    off3 = off2 * dimen[1];
+    off4 = off3 * dimen[2];
+    off5 = off4 * dimen[3];
+    off6 = off5 * dimen[4];
+    off7 = off6 * dimen[5];
+
+    st10 = fpix[0];
+    st20 = (fpix[1] - 1) * off2;
+    st30 = (fpix[2] - 1) * off3;
+    st40 = (fpix[3] - 1) * off4;
+    st50 = (fpix[4] - 1) * off5;
+    st60 = (fpix[5] - 1) * off6;
+    st70 = (fpix[6] - 1) * off7;
+
+    /* store the initial offset in each dimension */
+    st1 = st10;
+    st2 = st20;
+    st3 = st30;
+    st4 = st40;
+    st5 = st50;
+    st6 = st60;
+    st7 = st70;
+
+    astart = 0;
+
+    for (i7 = 0; i7 < irange[6]; i7++)
+    {
+     for (i6 = 0; i6 < irange[5]; i6++)
+     {
+      for (i5 = 0; i5 < irange[4]; i5++)
+      {
+       for (i4 = 0; i4 < irange[3]; i4++)
+       {
+        for (i3 = 0; i3 < irange[2]; i3++)
+        {
+         pstart = st1 + st2 + st3 + st4 + st5 + st6 + st7;
+
+         for (i2 = 0; i2 < irange[1]; i2++)
+         {
+           if (ffpcljj(fptr, 2, tablerow, pstart, i1, &array[astart],
+              status) > 0)
+              return(*status);
+
+           astart += i1;
+           pstart += off2;
+         }
+         st2 = st20;
+         st3 = st3+off3;    
+        }
+        st3 = st30;
+        st4 = st4+off4;
+       }
+       st4 = st40;
+       st5 = st5+off5;
+      }
+      st5 = st50;
+      st6 = st6+off6;
+     }
+     st6 = st60;
+     st7 = st7+off7;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpgpjj(fitsfile *fptr,   /* I - FITS file pointer                      */
+            long  group,      /* I - group to write(1 = 1st group)          */
+            long  firstelem,  /* I - first vector element to write(1 = 1st) */
+            long  nelem,      /* I - number of values to write              */
+            LONGLONG  *array, /* I - array of values that are written       */
+            int  *status)     /* IO - error status                          */
+/*
+  Write an array of group parameters to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being written).
+*/
+{
+    long row;
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffpcljj(fptr, 1L, row, firstelem, nelem, array, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpcljj(fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  colnum,     /* I - number of column to write (1 = 1st col) */
+            LONGLONG  firstrow,  /* I - first row to write (1 = 1st row)        */
+            LONGLONG  firstelem, /* I - first vector element to write (1 = 1st) */
+            LONGLONG  nelem,     /* I - number of values to write               */
+            LONGLONG  *array, /* I - array of values to write               */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of values to a column in the current FITS HDU.
+  The column number may refer to a real column in an ASCII or binary table, 
+  or it may refer to a virtual column in a 1 or more grouped FITS primary
+  array.  FITSIO treats a primary array as a binary table
+  with 2 vector columns: the first column contains the group parameters (often
+  with length = 0) and the second column contains the array of image pixels.
+  Each row of the table represents a group in the case of multigroup FITS
+  images.
+
+  The input array of values will be converted to the datatype of the column 
+  and will be inverse-scaled by the FITS TSCALn and TZEROn values if necessary.
+*/
+{
+    int tcode, maxelem, hdutype, writeraw;
+    long twidth, incre;
+    long  ntodo;
+    LONGLONG repeat, startpos, elemnum, wrtptr, rowlen, rownum, remain, next, tnull;
+    double scale, zero;
+    char tform[20], cform[20];
+    char message[FLEN_ERRMSG];
+
+    char snull[20];   /*  the FITS null value  */
+
+    double cbuff[DBUFFSIZE / sizeof(double)]; /* align cbuff on word boundary */
+    void *buffer;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    buffer = cbuff;
+
+    /*---------------------------------------------------*/
+    /*  Check input and get parameters about the column: */
+    /*---------------------------------------------------*/
+    if (ffgcprll( fptr, colnum, firstrow, firstelem, nelem, 1, &scale, &zero,
+        tform, &twidth, &tcode, &maxelem, &startpos,  &elemnum, &incre,
+        &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0)
+        return(*status);
+
+    if (tcode == TSTRING)   
+         ffcfmt(tform, cform);     /* derive C format for writing strings */
+
+    /*
+       if there is no scaling and the native machine format is not byteswapped
+       then we can simply write the raw data bytes into the FITS file if the
+       datatype of the FITS column is the same as the input values.  Otherwise
+       we must convert the raw values into the scaled and/or machine dependent
+       format in a temporary buffer that has been allocated for this purpose.
+    */
+    if (scale == 1. && zero == 0. && 
+       MACHINE == NATIVE && tcode == TLONGLONG)
+    {
+        writeraw = 1;
+        maxelem = (int) nelem;  /* we can write the entire array at one time */
+    }
+    else
+        writeraw = 0;
+
+    /*---------------------------------------------------------------------*/
+    /*  Now write the pixels to the FITS column.                           */
+    /*  First call the ffXXfYY routine to  (1) convert the datatype        */
+    /*  if necessary, and (2) scale the values by the FITS TSCALn and      */
+    /*  TZEROn linear scaling parameters into a temporary buffer.          */
+    /*---------------------------------------------------------------------*/
+    remain = nelem;           /* remaining number of values to write  */
+    next = 0;                 /* next element in array to be written  */
+    rownum = 0;               /* row number, relative to firstrow     */
+
+    while (remain)
+    {
+        /* limit the number of pixels to process a one time to the number that
+           will fit in the buffer space or to the number of pixels that remain
+           in the current vector, which ever is smaller.
+        */
+        ntodo = (long) minvalue(remain, maxelem);      
+        ntodo = (long) minvalue(ntodo, (repeat - elemnum));
+
+        wrtptr = startpos + ((LONGLONG)rownum * rowlen) + (elemnum * incre);
+
+        ffmbyt(fptr, wrtptr, IGNORE_EOF, status); /* move to write position */
+
+        switch (tcode) 
+        {
+            case (TLONGLONG):
+              if (writeraw)
+              {
+                /* write raw input bytes without conversion */
+                ffpi8b(fptr, ntodo, incre, (long *) &array[next], status);
+              }
+              else
+              {
+                /* convert the raw data before writing to FITS file */
+                ffi8fi8(&array[next], ntodo, scale, zero,
+                        (LONGLONG *) buffer, status);
+                ffpi8b(fptr, ntodo, incre, (long *) buffer, status);
+              }
+
+              break;
+
+            case (TLONG):
+
+                ffi8fi4(&array[next], ntodo, scale, zero,
+                        (INT32BIT *) buffer, status);
+                ffpi4b(fptr, ntodo, incre, (INT32BIT *) buffer, status);
+                break;
+
+            case (TBYTE):
+ 
+                ffi8fi1(&array[next], ntodo, scale, zero,
+                        (unsigned char *) buffer, status);
+                ffpi1b(fptr, ntodo, incre, (unsigned char *) buffer, status);
+                break;
+
+            case (TSHORT):
+
+                ffi8fi2(&array[next], ntodo, scale, zero,
+                        (short *) buffer, status);
+                ffpi2b(fptr, ntodo, incre, (short *) buffer, status);
+                break;
+
+            case (TFLOAT):
+
+                ffi8fr4(&array[next], ntodo, scale, zero,
+                        (float *) buffer, status);
+                ffpr4b(fptr, ntodo, incre, (float *) buffer, status);
+                break;
+
+            case (TDOUBLE):
+                ffi8fr8(&array[next], ntodo, scale, zero,
+                       (double *) buffer, status);
+                ffpr8b(fptr, ntodo, incre, (double *) buffer, status);
+                break;
+
+            case (TSTRING):  /* numerical column in an ASCII table */
+
+                if (cform[1] != 's')  /*  "%s" format is a string */
+                {
+                  ffi8fstr(&array[next], ntodo, scale, zero, cform,
+                          twidth, (char *) buffer, status);
+
+                  if (incre == twidth)    /* contiguous bytes */
+                     ffpbyt(fptr, ntodo * twidth, buffer, status);
+                  else
+                     ffpbytoff(fptr, twidth, ntodo, incre - twidth, buffer,
+                            status);
+
+                  break;
+                }
+                /* can't write to string column, so fall thru to default: */
+
+            default:  /*  error trap  */
+                sprintf(message, 
+                     "Cannot write numbers to column %d which has format %s",
+                      colnum,tform);
+                ffpmsg(message);
+                if (hdutype == ASCII_TBL)
+                    return(*status = BAD_ATABLE_FORMAT);
+                else
+                    return(*status = BAD_BTABLE_FORMAT);
+
+        } /* End of switch block */
+
+        /*-------------------------*/
+        /*  Check for fatal error  */
+        /*-------------------------*/
+        if (*status > 0)  /* test for error during previous write operation */
+        {
+          sprintf(message,
+          "Error writing elements %.0f thru %.0f of input data array (ffpclj).",
+              (double) (next+1), (double) (next+ntodo));
+          ffpmsg(message);
+          return(*status);
+        }
+
+        /*--------------------------------------------*/
+        /*  increment the counters for the next loop  */
+        /*--------------------------------------------*/
+        remain -= ntodo;
+        if (remain)
+        {
+            next += ntodo;
+            elemnum += ntodo;
+            if (elemnum == repeat)  /* completed a row; start on next row */
+            {
+                elemnum = 0;
+                rownum++;
+            }
+        }
+    }  /*  End of main while Loop  */
+
+
+    /*--------------------------------*/
+    /*  check for numerical overflow  */
+    /*--------------------------------*/
+    if (*status == OVERFLOW_ERR)
+    {
+        ffpmsg(
+        "Numerical overflow during type conversion while writing FITS data.");
+        *status = NUM_OVERFLOW;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpcnjj(fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  colnum,     /* I - number of column to write (1 = 1st col) */
+            LONGLONG  firstrow,  /* I - first row to write (1 = 1st row)        */
+            LONGLONG  firstelem, /* I - first vector element to write (1 = 1st) */
+            LONGLONG  nelem,     /* I - number of values to write               */
+            LONGLONG *array, /* I - array of values to write                */
+            LONGLONG nulvalue, /* I - value used to flag undefined pixels   */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of elements to the specified column of a table.  Any input
+  pixels equal to the value of nulvalue will be replaced by the appropriate
+  null value in the output FITS file. 
+
+  The input array of values will be converted to the datatype of the column 
+  and will be inverse-scaled by the FITS TSCALn and TZEROn values if necessary
+*/
+{
+    tcolumn *colptr;
+    long  ngood = 0, nbad = 0, ii;
+    LONGLONG repeat, first, fstelm, fstrow;
+    int tcode, overflow = 0;
+
+    if (*status > 0)
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+    {
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    }
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+    {
+        if ( ffrdef(fptr, status) > 0)               /* rescan header */
+            return(*status);
+    }
+
+    colptr  = (fptr->Fptr)->tableptr;   /* point to first column */
+    colptr += (colnum - 1);     /* offset to correct column structure */
+
+    tcode  = colptr->tdatatype;
+
+    if (tcode > 0)
+       repeat = colptr->trepeat;  /* repeat count for this column */
+    else
+       repeat = firstelem -1 + nelem;  /* variable length arrays */
+
+    /* if variable length array, first write the whole input vector, 
+       then go back and fill in the nulls */
+    if (tcode < 0) {
+      if (ffpcljj(fptr, colnum, firstrow, firstelem, nelem, array, status) > 0) {
+        if (*status == NUM_OVERFLOW) 
+	{
+	  /* ignore overflows, which are possibly the null pixel values */
+	  /*  overflow = 1;   */
+	  *status = 0;
+	} else { 
+          return(*status);
+	}
+      }
+    }
+
+    /* absolute element number in the column */
+    first = (firstrow - 1) * repeat + firstelem;
+
+    for (ii = 0; ii < nelem; ii++)
+    {
+      if (array[ii] != nulvalue)  /* is this a good pixel? */
+      {
+         if (nbad)  /* write previous string of bad pixels */
+         {
+            fstelm = ii - nbad + first;  /* absolute element number */
+            fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+            fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+            if (ffpclu(fptr, colnum, fstrow, fstelm, nbad, status) > 0)
+                return(*status);
+
+            nbad=0;
+         }
+
+         ngood = ngood +1;  /* the consecutive number of good pixels */
+      }
+      else
+      {
+         if (ngood)  /* write previous string of good pixels */
+         {
+            fstelm = ii - ngood + first;  /* absolute element number */
+            fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+            fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+            if (tcode > 0) {  /* variable length arrays have already been written */
+              if (ffpcljj(fptr, colnum, fstrow, fstelm, ngood, &array[ii-ngood],
+                status) > 0) {
+		if (*status == NUM_OVERFLOW) 
+		{
+		  overflow = 1;
+		  *status = 0;
+		} else { 
+                  return(*status);
+		}
+	      }
+	    }
+            ngood=0;
+         }
+
+         nbad = nbad +1;  /* the consecutive number of bad pixels */
+      }
+    }
+
+    /* finished loop;  now just write the last set of pixels */
+
+    if (ngood)  /* write last string of good pixels */
+    {
+      fstelm = ii - ngood + first;  /* absolute element number */
+      fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+      fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+      if (tcode > 0) {  /* variable length arrays have already been written */
+        ffpcljj(fptr, colnum, fstrow, fstelm, ngood, &array[ii-ngood], status);
+      }
+    }
+    else if (nbad) /* write last string of bad pixels */
+    {
+      fstelm = ii - nbad + first;  /* absolute element number */
+      fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+      fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+      ffpclu(fptr, colnum, fstrow, fstelm, nbad, status);
+    }
+
+    if (*status <= 0) {
+      if (overflow) {
+        *status = NUM_OVERFLOW;
+      }
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffi8fi1(LONGLONG *input,       /* I - array of values to be converted  */
+            long ntodo,            /* I - number of elements in the array  */
+            double scale,          /* I - FITS TSCALn or BSCALE value      */
+            double zero,           /* I - FITS TZEROn or BZERO  value      */
+            unsigned char *output, /* O - output array of converted values */
+            int *status)           /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required.
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            if (input[ii] < 0)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = 0;
+            }
+            else if (input[ii] > UCHAR_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = UCHAR_MAX;
+            }
+            else
+                output[ii] = (unsigned char) input[ii];
+        }
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = (input[ii] - zero) / scale;
+
+            if (dvalue < DUCHAR_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = 0;
+            }
+            else if (dvalue > DUCHAR_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = UCHAR_MAX;
+            }
+            else
+                output[ii] = (unsigned char) (dvalue + .5);
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffi8fi2(LONGLONG *input,   /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            short *output,     /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required.
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            if (input[ii] < SHRT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = SHRT_MIN;
+            }
+            else if (input[ii] > SHRT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = SHRT_MAX;
+            }
+            else
+                output[ii] = (short) input[ii];
+        }
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = (input[ii] - zero) / scale;
+
+            if (dvalue < DSHRT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = SHRT_MIN;
+            }
+            else if (dvalue > DSHRT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = SHRT_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0)
+                    output[ii] = (short) (dvalue + .5);
+                else
+                    output[ii] = (short) (dvalue - .5);
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffi8fi4(LONGLONG *input,   /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            INT32BIT *output,  /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            if (input[ii] < INT32_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = INT32_MIN;
+            }
+            else if (input[ii] > INT32_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = INT32_MAX;
+            }
+            else
+                output[ii] = (INT32BIT) input[ii];
+        }
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = (input[ii] - zero) / scale;
+
+            if (dvalue < DINT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = INT32_MIN;
+            }
+            else if (dvalue > DINT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = INT32_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0)
+                    output[ii] = (INT32BIT) (dvalue + .5);
+                else
+                    output[ii] = (INT32BIT) (dvalue - .5);
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffi8fi8(LONGLONG *input,   /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            LONGLONG *output,  /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+                output[ii] = input[ii];
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = (input[ii] - zero) / scale;
+
+            if (dvalue < DLONGLONG_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = LONGLONG_MIN;
+            }
+            else if (dvalue > DLONGLONG_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = LONGLONG_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0)
+                    output[ii] = (LONGLONG) (dvalue + .5);
+                else
+                    output[ii] = (LONGLONG) (dvalue - .5);
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffi8fr4(LONGLONG *input,   /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            float *output,     /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required.
+*/
+{
+    long ii;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (float) input[ii];
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+            output[ii] = (float) ((input[ii] - zero) / scale);
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffi8fr8(LONGLONG *input,       /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            double *output,    /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required.
+*/
+{
+    long ii;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (double) input[ii];
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+            output[ii] = (input[ii] - zero) / scale;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffi8fstr(LONGLONG *input,  /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            char *cform,       /* I - format for output string values  */
+            long twidth,       /* I - width of each field, in chars    */
+            char *output,      /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do scaling if required.
+*/
+{
+    long ii;
+    double dvalue;
+    char *cptr;
+    
+    cptr = output;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+        {
+           sprintf(output, cform, (double) input[ii]);
+           output += twidth;
+
+           if (*output)  /* if this char != \0, then overflow occurred */
+              *status = OVERFLOW_ERR;
+        }
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+          dvalue = (input[ii] - zero) / scale;
+          sprintf(output, cform, dvalue);
+          output += twidth;
+
+          if (*output)  /* if this char != \0, then overflow occurred */
+            *status = OVERFLOW_ERR;
+        }
+    }
+
+    /* replace any commas with periods (e.g., in French locale) */
+    while ((cptr = strchr(cptr, ','))) *cptr = '.';
+    
+    return(*status);
+}
diff --git a/src/plugins/cfitsio/putcolk.c b/src/plugins/cfitsio/putcolk.c
new file mode 100644
index 0000000..005a142
--- /dev/null
+++ b/src/plugins/cfitsio/putcolk.c
@@ -0,0 +1,1012 @@
+/*  This file, putcolk.c, contains routines that write data elements to    */
+/*  a FITS image or table, with 'int' datatype.                            */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+
+#include <limits.h>
+#include <string.h>
+#include <stdlib.h>
+#include "fitsio2.h"
+
+/*--------------------------------------------------------------------------*/
+int ffpprk( fitsfile *fptr,  /* I - FITS file pointer                       */
+            long  group,     /* I - group to write(1 = 1st group)           */
+            LONGLONG  firstelem, /* I - first vector element to write(1 = 1st)  */
+            LONGLONG  nelem,     /* I - number of values to write               */
+            int   *array,    /* I - array of values that are written        */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being written).
+*/
+{
+    long row;
+    int nullvalue;
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        fits_write_compressed_pixels(fptr, TINT, firstelem, nelem,
+            0, array, &nullvalue, status);
+        return(*status);
+    }
+
+    row=maxvalue(1,group);
+
+    ffpclk(fptr, 2, row, firstelem, nelem, array, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffppnk( fitsfile *fptr,  /* I - FITS file pointer                       */
+            long  group,     /* I - group to write(1 = 1st group)           */
+            LONGLONG  firstelem, /* I - first vector element to write(1 = 1st)  */
+            LONGLONG  nelem,     /* I - number of values to write               */
+            int   *array,    /* I - array of values that are written        */
+            int   nulval,    /* I - undefined pixel value                   */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being written).  Any array values
+  that are equal to the value of nulval will be replaced with the null
+  pixel value that is appropriate for this column.
+*/
+{
+    long row;
+    int nullvalue;
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        nullvalue = nulval;  /* set local variable */
+        fits_write_compressed_pixels(fptr, TINT, firstelem, nelem,
+            1, array, &nullvalue, status);
+        return(*status);
+    }
+
+    row=maxvalue(1,group);
+
+    ffpcnk(fptr, 2, row, firstelem, nelem, array, nulval, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffp2dk(fitsfile *fptr,   /* I - FITS file pointer                     */
+           long  group,      /* I - group to write(1 = 1st group)         */
+           LONGLONG  ncols,      /* I - number of pixels in each row of array */
+           LONGLONG  naxis1,     /* I - FITS image NAXIS1 value               */
+           LONGLONG  naxis2,     /* I - FITS image NAXIS2 value               */
+           int   *array,     /* I - array to be written                   */
+           int  *status)     /* IO - error status                         */
+/*
+  Write an entire 2-D array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being written).
+*/
+{
+    /* call the 3D writing routine, with the 3rd dimension = 1 */
+
+    ffp3dk(fptr, group, ncols, naxis2, naxis1, naxis2, 1, array, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffp3dk(fitsfile *fptr,   /* I - FITS file pointer                     */
+           long  group,      /* I - group to write(1 = 1st group)         */
+           LONGLONG  ncols,      /* I - number of pixels in each row of array */
+           LONGLONG  nrows,      /* I - number of rows in each plane of array */
+           LONGLONG  naxis1,     /* I - FITS image NAXIS1 value               */
+           LONGLONG  naxis2,     /* I - FITS image NAXIS2 value               */
+           LONGLONG  naxis3,     /* I - FITS image NAXIS3 value               */
+           int   *array,     /* I - array to be written                   */
+           int  *status)     /* IO - error status                         */
+/*
+  Write an entire 3-D cube of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being written).
+*/
+{
+    long tablerow, ii, jj;
+    long fpixel[3]= {1,1,1}, lpixel[3];
+    LONGLONG nfits, narray;
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+           
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+        lpixel[0] = (long) ncols;
+        lpixel[1] = (long) nrows;
+        lpixel[2] = (long) naxis3;
+       
+        fits_write_compressed_img(fptr, TINT, fpixel, lpixel,
+            0,  array, NULL, status);
+    
+        return(*status);
+    }
+
+    tablerow=maxvalue(1,group);
+
+    if (ncols == naxis1 && nrows == naxis2)  /* arrays have same size? */
+    {
+      /* all the image pixels are contiguous, so write all at once */
+      ffpclk(fptr, 2, tablerow, 1L, naxis1 * naxis2 * naxis3, array, status);
+      return(*status);
+    }
+
+    if (ncols < naxis1 || nrows < naxis2)
+       return(*status = BAD_DIMEN);
+
+    nfits = 1;   /* next pixel in FITS image to write to */
+    narray = 0;  /* next pixel in input array to be written */
+
+    /* loop over naxis3 planes in the data cube */
+    for (jj = 0; jj < naxis3; jj++)
+    {
+      /* loop over the naxis2 rows in the FITS image, */
+      /* writing naxis1 pixels to each row            */
+
+      for (ii = 0; ii < naxis2; ii++)
+      {
+       if (ffpclk(fptr, 2, tablerow, nfits, naxis1,&array[narray],status) > 0)
+         return(*status);
+
+       nfits += naxis1;
+       narray += ncols;
+      }
+      narray += (nrows - naxis2) * ncols;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpssk(fitsfile *fptr,   /* I - FITS file pointer                       */
+           long  group,      /* I - group to write(1 = 1st group)           */
+           long  naxis,      /* I - number of data axes in array            */
+           long  *naxes,     /* I - size of each FITS axis                  */
+           long  *fpixel,    /* I - 1st pixel in each axis to write (1=1st) */
+           long  *lpixel,    /* I - last pixel in each axis to write        */
+           int *array,      /* I - array to be written                     */
+           int  *status)     /* IO - error status                           */
+/*
+  Write a subsection of pixels to the primary array or image.
+  A subsection is defined to be any contiguous rectangular
+  array of pixels within the n-dimensional FITS data file.
+  Data conversion and scaling will be performed if necessary 
+  (e.g, if the datatype of the FITS array is not the same as
+  the array being written).
+*/
+{
+    long tablerow;
+    LONGLONG fpix[7], dimen[7], astart, pstart;
+    LONGLONG off2, off3, off4, off5, off6, off7;
+    LONGLONG st10, st20, st30, st40, st50, st60, st70;
+    LONGLONG st1, st2, st3, st4, st5, st6, st7;
+    long ii, i1, i2, i3, i4, i5, i6, i7, irange[7];
+
+    if (*status > 0)
+        return(*status);
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        fits_write_compressed_img(fptr, TINT, fpixel, lpixel,
+            0,  array, NULL, status);
+    
+        return(*status);
+    }
+
+    if (naxis < 1 || naxis > 7)
+      return(*status = BAD_DIMEN);
+
+    tablerow=maxvalue(1,group);
+
+     /* calculate the size and number of loops to perform in each dimension */
+    for (ii = 0; ii < 7; ii++)
+    {
+      fpix[ii]=1;
+      irange[ii]=1;
+      dimen[ii]=1;
+    }
+
+    for (ii = 0; ii < naxis; ii++)
+    {    
+      fpix[ii]=fpixel[ii];
+      irange[ii]=lpixel[ii]-fpixel[ii]+1;
+      dimen[ii]=naxes[ii];
+    }
+
+    i1=irange[0];
+
+    /* compute the pixel offset between each dimension */
+    off2 =     dimen[0];
+    off3 = off2 * dimen[1];
+    off4 = off3 * dimen[2];
+    off5 = off4 * dimen[3];
+    off6 = off5 * dimen[4];
+    off7 = off6 * dimen[5];
+
+    st10 = fpix[0];
+    st20 = (fpix[1] - 1) * off2;
+    st30 = (fpix[2] - 1) * off3;
+    st40 = (fpix[3] - 1) * off4;
+    st50 = (fpix[4] - 1) * off5;
+    st60 = (fpix[5] - 1) * off6;
+    st70 = (fpix[6] - 1) * off7;
+
+    /* store the initial offset in each dimension */
+    st1 = st10;
+    st2 = st20;
+    st3 = st30;
+    st4 = st40;
+    st5 = st50;
+    st6 = st60;
+    st7 = st70;
+
+    astart = 0;
+
+    for (i7 = 0; i7 < irange[6]; i7++)
+    {
+     for (i6 = 0; i6 < irange[5]; i6++)
+     {
+      for (i5 = 0; i5 < irange[4]; i5++)
+      {
+       for (i4 = 0; i4 < irange[3]; i4++)
+       {
+        for (i3 = 0; i3 < irange[2]; i3++)
+        {
+         pstart = st1 + st2 + st3 + st4 + st5 + st6 + st7;
+
+         for (i2 = 0; i2 < irange[1]; i2++)
+         {
+           if (ffpclk(fptr, 2, tablerow, pstart, i1, &array[astart],
+              status) > 0)
+              return(*status);
+
+           astart += i1;
+           pstart += off2;
+         }
+         st2 = st20;
+         st3 = st3+off3;    
+        }
+        st3 = st30;
+        st4 = st4+off4;
+       }
+       st4 = st40;
+       st5 = st5+off5;
+      }
+      st5 = st50;
+      st6 = st6+off6;
+     }
+     st6 = st60;
+     st7 = st7+off7;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpgpk( fitsfile *fptr,   /* I - FITS file pointer                      */
+            long  group,      /* I - group to write(1 = 1st group)          */
+            long  firstelem,  /* I - first vector element to write(1 = 1st) */
+            long  nelem,      /* I - number of values to write              */
+            int   *array,     /* I - array of values that are written       */
+            int  *status)     /* IO - error status                          */
+/*
+  Write an array of group parameters to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being written).
+*/
+{
+    long row;
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffpclk(fptr, 1L, row, firstelem, nelem, array, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpclk( fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  colnum,     /* I - number of column to write (1 = 1st col) */
+            LONGLONG  firstrow,  /* I - first row to write (1 = 1st row)        */
+            LONGLONG  firstelem, /* I - first vector element to write (1 = 1st) */
+            LONGLONG  nelem,     /* I - number of values to write               */
+            int   *array,    /* I - array of values to write                */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of values to a column in the current FITS HDU.
+  The column number may refer to a real column in an ASCII or binary table, 
+  or it may refer to a virtual column in a 1 or more grouped FITS primary
+  array.  FITSIO treats a primary array as a binary table
+  with 2 vector columns: the first column contains the group parameters (often
+  with length = 0) and the second column contains the array of image pixels.
+  Each row of the table represents a group in the case of multigroup FITS
+  images.
+
+  The input array of values will be converted to the datatype of the column 
+  and will be inverse-scaled by the FITS TSCALn and TZEROn values if necessary.
+*/
+{
+    int tcode, maxelem, hdutype, writeraw;
+    long twidth, incre;
+    long ntodo;
+    LONGLONG repeat, startpos, elemnum, wrtptr, rowlen, rownum, remain, next, tnull;
+    double scale, zero;
+    char tform[20], cform[20];
+    char message[FLEN_ERRMSG];
+
+    char snull[20];   /*  the FITS null value  */
+
+    double cbuff[DBUFFSIZE / sizeof(double)]; /* align cbuff on word boundary */
+    void *buffer;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    /* call the 'short' or 'long' version of this routine, if possible */
+    if (sizeof(int) == sizeof(short))
+        ffpcli(fptr, colnum, firstrow, firstelem, nelem, 
+              (short *) array, status);
+    else if (sizeof(int) == sizeof(long))
+        ffpclj(fptr, colnum, firstrow, firstelem, nelem, 
+              (long *) array, status);
+    else
+    {
+    /*
+      This is a special case: sizeof(int) is not equal to sizeof(short) or
+      sizeof(long).  This occurs on Alpha OSF systems where short = 2 bytes,
+      int = 4 bytes, and long = 8 bytes.
+    */
+
+    buffer = cbuff;
+
+    /*---------------------------------------------------*/
+    /*  Check input and get parameters about the column: */
+    /*---------------------------------------------------*/
+    if (ffgcprll( fptr, colnum, firstrow, firstelem, nelem, 1, &scale, &zero,
+        tform, &twidth, &tcode, &maxelem, &startpos,  &elemnum, &incre,
+        &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0)
+        return(*status);
+
+    if (tcode == TSTRING)   
+         ffcfmt(tform, cform);     /* derive C format for writing strings */
+
+    /*
+       if there is no scaling and the native machine format is not byteswapped
+       then we can simply write the raw data bytes into the FITS file if the
+       datatype of the FITS column is the same as the input values.  Otherwise
+       we must convert the raw values into the scaled and/or machine dependent
+       format in a temporary buffer that has been allocated for this purpose.
+    */
+    if (scale == 1. && zero == 0. && 
+       MACHINE == NATIVE && tcode == TLONG)
+    {
+        writeraw = 1;
+        maxelem = (int) nelem;  /* we can write the entire array at one time */
+    }
+    else
+        writeraw = 0;
+
+    /*---------------------------------------------------------------------*/
+    /*  Now write the pixels to the FITS column.                           */
+    /*  First call the ffXXfYY routine to  (1) convert the datatype        */
+    /*  if necessary, and (2) scale the values by the FITS TSCALn and      */
+    /*  TZEROn linear scaling parameters into a temporary buffer.          */
+    /*---------------------------------------------------------------------*/
+    remain = nelem;           /* remaining number of values to write  */
+    next = 0;                 /* next element in array to be written  */
+    rownum = 0;               /* row number, relative to firstrow     */
+
+    while (remain)
+    {
+        /* limit the number of pixels to process a one time to the number that
+           will fit in the buffer space or to the number of pixels that remain
+           in the current vector, which ever is smaller.
+        */
+        ntodo = (long) minvalue(remain, maxelem);      
+        ntodo = (long) minvalue(ntodo, (repeat - elemnum));
+
+        wrtptr = startpos + ((LONGLONG)rownum * rowlen) + (elemnum * incre);
+
+        ffmbyt(fptr, wrtptr, IGNORE_EOF, status); /* move to write position */
+
+        switch (tcode) 
+        {
+            case (TLONG):
+              if (writeraw)
+              {
+                /* write raw input bytes without conversion */
+                ffpi4b(fptr, ntodo, incre, (INT32BIT *) &array[next], status);
+              }
+              else
+              {
+                /* convert the raw data before writing to FITS file */
+                ffintfi4(&array[next], ntodo, scale, zero,
+                        (INT32BIT *) buffer, status);
+                ffpi4b(fptr, ntodo, incre, (INT32BIT *) buffer, status);
+              }
+
+                break;
+
+            case (TLONGLONG):
+
+                ffintfi8(&array[next], ntodo, scale, zero,
+                        (LONGLONG *) buffer, status);
+                ffpi8b(fptr, ntodo, incre, (long *) buffer, status);
+                break;
+
+            case (TBYTE):
+ 
+                ffintfi1(&array[next], ntodo, scale, zero,
+                        (unsigned char *) buffer, status);
+                ffpi1b(fptr, ntodo, incre, (unsigned char *) buffer, status);
+                break;
+
+            case (TSHORT):
+
+                ffintfi2(&array[next], ntodo, scale, zero,
+                        (short *) buffer, status);
+                ffpi2b(fptr, ntodo, incre, (short *) buffer, status);
+                break;
+
+            case (TFLOAT):
+
+                ffintfr4(&array[next], ntodo, scale, zero,
+                        (float *) buffer, status);
+                ffpr4b(fptr, ntodo, incre, (float *) buffer, status);
+                break;
+
+            case (TDOUBLE):
+                ffintfr8(&array[next], ntodo, scale, zero,
+                       (double *) buffer, status);
+                ffpr8b(fptr, ntodo, incre, (double *) buffer, status);
+                break;
+
+            case (TSTRING):  /* numerical column in an ASCII table */
+
+                if (cform[1] != 's')  /*  "%s" format is a string */
+                {
+                  ffintfstr(&array[next], ntodo, scale, zero, cform,
+                          twidth, (char *) buffer, status);
+
+                  if (incre == twidth)    /* contiguous bytes */
+                     ffpbyt(fptr, ntodo * twidth, buffer, status);
+                  else
+                     ffpbytoff(fptr, twidth, ntodo, incre - twidth, buffer,
+                            status);
+
+                  break;
+                }
+                /* can't write to string column, so fall thru to default: */
+
+            default:  /*  error trap  */
+                sprintf(message, 
+                     "Cannot write numbers to column %d which has format %s",
+                      colnum,tform);
+                ffpmsg(message);
+                if (hdutype == ASCII_TBL)
+                    return(*status = BAD_ATABLE_FORMAT);
+                else
+                    return(*status = BAD_BTABLE_FORMAT);
+
+        } /* End of switch block */
+
+        /*-------------------------*/
+        /*  Check for fatal error  */
+        /*-------------------------*/
+        if (*status > 0)  /* test for error during previous write operation */
+        {
+          sprintf(message,
+          "Error writing elements %.0f thru %.0f of input data array (ffpclk).",
+              (double) (next+1), (double) (next+ntodo));
+          ffpmsg(message);
+          return(*status);
+        }
+
+        /*--------------------------------------------*/
+        /*  increment the counters for the next loop  */
+        /*--------------------------------------------*/
+        remain -= ntodo;
+        if (remain)
+        {
+            next += ntodo;
+            elemnum += ntodo;
+            if (elemnum == repeat)  /* completed a row; start on next row */
+            {
+                elemnum = 0;
+                rownum++;
+            }
+        }
+    }  /*  End of main while Loop  */
+
+
+    /*--------------------------------*/
+    /*  check for numerical overflow  */
+    /*--------------------------------*/
+    if (*status == OVERFLOW_ERR)
+    {
+        ffpmsg(
+        "Numerical overflow during type conversion while writing FITS data.");
+        *status = NUM_OVERFLOW;
+    }
+
+    }   /* end of Dec ALPHA special case */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpcnk( fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  colnum,     /* I - number of column to write (1 = 1st col) */
+            LONGLONG  firstrow,  /* I - first row to write (1 = 1st row)        */
+            LONGLONG  firstelem, /* I - first vector element to write (1 = 1st) */
+            LONGLONG  nelem,     /* I - number of values to write               */
+            int   *array,    /* I - array of values to write                */
+            int    nulvalue, /* I - value used to flag undefined pixels     */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of elements to the specified column of a table.  Any input
+  pixels equal to the value of nulvalue will be replaced by the appropriate
+  null value in the output FITS file. 
+
+  The input array of values will be converted to the datatype of the column 
+  and will be inverse-scaled by the FITS TSCALn and TZEROn values if necessary
+*/
+{
+    tcolumn *colptr;
+    long  ngood = 0, nbad = 0, ii;
+    LONGLONG repeat, first, fstelm, fstrow;
+    int tcode, overflow = 0;
+
+    if (*status > 0)
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+    {
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    }
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+    {
+        if ( ffrdef(fptr, status) > 0)               /* rescan header */
+            return(*status);
+    }
+
+    colptr  = (fptr->Fptr)->tableptr;   /* point to first column */
+    colptr += (colnum - 1);     /* offset to correct column structure */
+
+    tcode  = colptr->tdatatype;
+
+    if (tcode > 0)
+       repeat = colptr->trepeat;  /* repeat count for this column */
+    else
+       repeat = firstelem -1 + nelem;  /* variable length arrays */
+
+    /* if variable length array, first write the whole input vector, 
+       then go back and fill in the nulls */
+    if (tcode < 0) {
+      if (ffpclk(fptr, colnum, firstrow, firstelem, nelem, array, status) > 0) {
+        if (*status == NUM_OVERFLOW) 
+	{
+	  /* ignore overflows, which are possibly the null pixel values */
+	  /*  overflow = 1;   */
+	  *status = 0;
+	} else { 
+          return(*status);
+	}
+      }
+    }
+
+    /* absolute element number in the column */
+    first = (firstrow - 1) * repeat + firstelem;
+
+    for (ii = 0; ii < nelem; ii++)
+    {
+      if (array[ii] != nulvalue)  /* is this a good pixel? */
+      {
+         if (nbad)  /* write previous string of bad pixels */
+         {
+            fstelm = ii - nbad + first;  /* absolute element number */
+            fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+            fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+            if (ffpclu(fptr, colnum, fstrow, fstelm, nbad, status) > 0)
+                return(*status);
+
+            nbad=0;
+         }
+
+         ngood = ngood +1;  /* the consecutive number of good pixels */
+      }
+      else
+      {
+         if (ngood)  /* write previous string of good pixels */
+         {
+            fstelm = ii - ngood + first;  /* absolute element number */
+            fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+            fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+            if (tcode > 0) {  /* variable length arrays have already been written */
+              if (ffpclk(fptr, colnum, fstrow, fstelm, ngood, &array[ii-ngood],
+                status) > 0)  {
+		if (*status == NUM_OVERFLOW) 
+		{
+		  overflow = 1;
+		  *status = 0;
+		} else { 
+                  return(*status);
+		}
+	      }
+	    }
+            ngood=0;
+         }
+
+         nbad = nbad +1;  /* the consecutive number of bad pixels */
+      }
+    }
+
+    /* finished loop;  now just write the last set of pixels */
+
+    if (ngood)  /* write last string of good pixels */
+    {
+      fstelm = ii - ngood + first;  /* absolute element number */
+      fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+      fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+      if (tcode > 0) {  /* variable length arrays have already been written */
+        ffpclk(fptr, colnum, fstrow, fstelm, ngood, &array[ii-ngood], status);
+      }
+    }
+    else if (nbad) /* write last string of bad pixels */
+    {
+      fstelm = ii - nbad + first;  /* absolute element number */
+      fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+      fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+      ffpclu(fptr, colnum, fstrow, fstelm, nbad, status);
+    }
+
+    if (*status <= 0) {
+      if (overflow) {
+        *status = NUM_OVERFLOW;
+      }
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffintfi1(int *input,           /* I - array of values to be converted  */
+            long ntodo,            /* I - number of elements in the array  */
+            double scale,          /* I - FITS TSCALn or BSCALE value      */
+            double zero,           /* I - FITS TZEROn or BZERO  value      */
+            unsigned char *output, /* O - output array of converted values */
+            int *status)           /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required.
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            if (input[ii] < 0)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = 0;
+            }
+            else if (input[ii] > UCHAR_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = UCHAR_MAX;
+            }
+            else
+                output[ii] = input[ii];
+        }
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = (input[ii] - zero) / scale;
+
+            if (dvalue < DUCHAR_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = 0;
+            }
+            else if (dvalue > DUCHAR_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = UCHAR_MAX;
+            }
+            else
+                output[ii] = (unsigned char) (dvalue + .5);
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffintfi2(int *input,       /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            short *output,     /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required.
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            if (input[ii] < SHRT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = SHRT_MIN;
+            }
+            else if (input[ii] > SHRT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = SHRT_MAX;
+            }
+            else
+                output[ii] = input[ii];
+        }
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = (input[ii] - zero) / scale;
+
+            if (dvalue < DSHRT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = SHRT_MIN;
+            }
+            else if (dvalue > DSHRT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = SHRT_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0)
+                    output[ii] = (short) (dvalue + .5);
+                else
+                    output[ii] = (short) (dvalue - .5);
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffintfi4(int *input,       /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            INT32BIT *output,      /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 0.)  
+    {       
+        memcpy(output, input, ntodo * sizeof(int) );
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = (input[ii] - zero) / scale;
+
+            if (dvalue < DINT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = INT32_MIN;
+            }
+            else if (dvalue > DINT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = INT32_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0)
+                    output[ii] = (INT32BIT) (dvalue + .5);
+                else
+                    output[ii] = (INT32BIT) (dvalue - .5);
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffintfi8(int *input,  /* I - array of values to be converted  */
+            long ntodo,             /* I - number of elements in the array  */
+            double scale,           /* I - FITS TSCALn or BSCALE value      */
+            double zero,            /* I - FITS TZEROn or BZERO  value      */
+            LONGLONG *output,       /* O - output array of converted values */
+            int *status)            /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+                output[ii] = input[ii];
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = (input[ii] - zero) / scale;
+
+            if (dvalue < DLONGLONG_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = LONGLONG_MIN;
+            }
+            else if (dvalue > DLONGLONG_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = LONGLONG_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0)
+                    output[ii] = (LONGLONG) (dvalue + .5);
+                else
+                    output[ii] = (LONGLONG) (dvalue - .5);
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffintfr4(int *input,       /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            float *output,     /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required.
+*/
+{
+    long ii;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (float) input[ii];
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+            output[ii] = (float) ((input[ii] - zero) / scale);
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffintfr8(int *input,       /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            double *output,    /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required.
+*/
+{
+    long ii;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (double) input[ii];
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+            output[ii] = (input[ii] - zero) / scale;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffintfstr(int *input,      /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            char *cform,       /* I - format for output string values  */
+            long twidth,       /* I - width of each field, in chars    */
+            char *output,      /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do scaling if required.
+*/
+{
+    long ii;
+    double dvalue;
+    char *cptr;
+    
+    cptr = output;
+
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+        {
+           sprintf(output, cform, (double) input[ii]);
+           output += twidth;
+
+           if (*output)  /* if this char != \0, then overflow occurred */
+              *status = OVERFLOW_ERR;
+        }
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+          dvalue = (input[ii] - zero) / scale;
+          sprintf(output, cform, dvalue);
+          output += twidth;
+
+          if (*output)  /* if this char != \0, then overflow occurred */
+            *status = OVERFLOW_ERR;
+        }
+    }
+
+    /* replace any commas with periods (e.g., in French locale) */
+    while ((cptr = strchr(cptr, ','))) *cptr = '.';
+    
+    return(*status);
+}
diff --git a/src/plugins/cfitsio/putcoll.c b/src/plugins/cfitsio/putcoll.c
new file mode 100644
index 0000000..b26e911
--- /dev/null
+++ b/src/plugins/cfitsio/putcoll.c
@@ -0,0 +1,369 @@
+/*  This file, putcoll.c, contains routines that write data elements to    */
+/*  a FITS image or table, with logical datatype.                          */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+
+#include <string.h>
+#include <stdlib.h>
+#include "fitsio2.h"
+
+/*--------------------------------------------------------------------------*/
+int ffpcll( fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  colnum,     /* I - number of column to write (1 = 1st col) */
+            LONGLONG  firstrow,  /* I - first row to write (1 = 1st row)        */
+            LONGLONG  firstelem, /* I - first vector element to write (1 = 1st) */
+            LONGLONG  nelem,     /* I - number of values to write               */
+            char *array,     /* I - array of values to write                */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of logical values to a column in the current FITS HDU.
+*/
+{
+    int tcode, maxelem, hdutype;
+    long twidth, incre;
+    LONGLONG repeat, startpos, elemnum, wrtptr, rowlen, rownum, remain, next, tnull;
+    double scale, zero;
+    char tform[20], ctrue = 'T', cfalse = 'F';
+    char message[FLEN_ERRMSG];
+    char snull[20];   /*  the FITS null value  */
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    /*---------------------------------------------------*/
+    /*  Check input and get parameters about the column: */
+    /*---------------------------------------------------*/
+    if (ffgcprll( fptr, colnum, firstrow, firstelem, nelem, 1, &scale, &zero,
+        tform, &twidth, &tcode, &maxelem, &startpos,  &elemnum, &incre,
+        &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0)
+        return(*status);
+
+    if (tcode != TLOGICAL)   
+        return(*status = NOT_LOGICAL_COL);
+
+    /*---------------------------------------------------------------------*/
+    /*  Now write the logical values one at a time to the FITS column.     */
+    /*---------------------------------------------------------------------*/
+    remain = nelem;           /* remaining number of values to write  */
+    next = 0;                 /* next element in array to be written  */
+    rownum = 0;               /* row number, relative to firstrow     */
+
+    while (remain)
+    {
+      wrtptr = startpos + (rowlen * rownum) + (elemnum * incre);
+
+      ffmbyt(fptr, wrtptr, IGNORE_EOF, status);  /* move to write position */
+
+      if (array[next])
+         ffpbyt(fptr, 1, &ctrue, status);
+      else
+         ffpbyt(fptr, 1, &cfalse, status);
+
+      if (*status > 0)  /* test for error during previous write operation */
+      {
+        sprintf(message,
+           "Error writing element %.0f of input array of logicals (ffpcll).",
+            (double) (next+1));
+        ffpmsg(message);
+        return(*status);
+      }
+
+      /*--------------------------------------------*/
+      /*  increment the counters for the next loop  */
+      /*--------------------------------------------*/
+      remain--;
+      if (remain)
+      {
+        next++;
+        elemnum++;
+        if (elemnum == repeat)  /* completed a row; start on next row */
+        {
+           elemnum = 0;
+           rownum++;
+        }
+      }
+
+    }  /*  End of main while Loop  */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpcnl( fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  colnum,     /* I - number of column to write (1 = 1st col) */
+            LONGLONG  firstrow,  /* I - first row to write (1 = 1st row)        */
+            LONGLONG  firstelem, /* I - first vector element to write (1 = 1st) */
+            LONGLONG  nelem,     /* I - number of values to write               */
+            char  *array,    /* I - array of values to write                */
+            char  nulvalue,  /* I - array flagging undefined pixels if true */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of elements to the specified column of a table.  Any input
+  pixels flagged as null will be replaced by the appropriate
+  null value in the output FITS file. 
+*/
+{
+    tcolumn *colptr;
+    long  ngood = 0, nbad = 0, ii;
+    LONGLONG repeat, first, fstelm, fstrow;
+    int tcode;
+
+    if (*status > 0)
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+    {
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    }
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+    {
+        if ( ffrdef(fptr, status) > 0)               /* rescan header */
+            return(*status);
+    }
+
+    colptr  = (fptr->Fptr)->tableptr;   /* point to first column */
+    colptr += (colnum - 1);     /* offset to correct column structure */
+
+    tcode  = colptr->tdatatype;
+
+    if (tcode > 0)
+       repeat = colptr->trepeat;  /* repeat count for this column */
+    else
+       repeat = firstelem -1 + nelem;  /* variable length arrays */
+
+    /* first write the whole input vector, then go back and fill in the nulls */
+    if (ffpcll(fptr, colnum, firstrow, firstelem, nelem, array, status) > 0)
+          return(*status);
+
+    /* absolute element number in the column */
+    first = (firstrow - 1) * repeat + firstelem;
+
+    for (ii = 0; ii < nelem; ii++)
+    {
+      if (array[ii] != nulvalue)  /* is this a good pixel? */
+      {
+         if (nbad)  /* write previous string of bad pixels */
+         {
+            fstelm = ii - nbad + first;  /* absolute element number */
+            fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+            fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+            if (ffpclu(fptr, colnum, fstrow, fstelm, nbad, status) > 0)
+                return(*status);
+
+            nbad=0;
+         }
+
+         ngood = ngood +1;  /* the consecutive number of good pixels */
+      }
+      else
+      {
+         if (ngood)  /* write previous string of good pixels */
+         {
+            fstelm = ii - ngood + first;  /* absolute element number */
+            fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+            fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+/*  good values have already been written
+            if (ffpcll(fptr, colnum, fstrow, fstelm, ngood, &array[ii-ngood],
+                status) > 0)
+                return(*status);
+*/
+            ngood=0;
+         }
+
+         nbad = nbad +1;  /* the consecutive number of bad pixels */
+      }
+    }
+
+    /* finished loop;  now just write the last set of pixels */
+
+    if (ngood)  /* write last string of good pixels */
+    {
+      fstelm = ii - ngood + first;  /* absolute element number */
+      fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+      fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+/*  these have already been written
+      ffpcll(fptr, colnum, fstrow, fstelm, ngood, &array[ii-ngood], status);
+*/
+    }
+    else if (nbad) /* write last string of bad pixels */
+    {
+      fstelm = ii - nbad + first;  /* absolute element number */
+      fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+      fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+      ffpclu(fptr, colnum, fstrow, fstelm, nbad, status);
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpclx( fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  colnum,     /* I - number of column to write (1 = 1st col) */
+            LONGLONG  frow,      /* I - first row to write (1 = 1st row)        */
+            long  fbit,      /* I - first bit to write (1 = 1st)            */
+            long  nbit,      /* I - number of bits to write                 */
+            char *larray,    /* I - array of logicals corresponding to bits */
+            int  *status)    /* IO - error status                           */
+/*
+  write an array of logical values to a specified bit or byte
+  column of the binary table.   If larray is TRUE, then the corresponding
+  bit is set to 1, otherwise it is set to 0.
+  The binary table column being written to must have datatype 'B' or 'X'. 
+*/
+{
+    LONGLONG offset, bstart, repeat, rowlen, elemnum, rstart, estart, tnull;
+    long fbyte, lbyte, nbyte, bitloc, ndone;
+    long ii, twidth, incre;
+    int tcode, descrp, maxelem, hdutype;
+    double dummyd;
+    char tform[12], snull[12];
+    unsigned char cbuff;
+    static unsigned char onbit[8] = {128,  64,  32,  16,   8,   4,   2,   1};
+    static unsigned char offbit[8] = {127, 191, 223, 239, 247, 251, 253, 254};
+    tcolumn *colptr;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    /*  check input parameters */
+    if (nbit < 1)
+        return(*status);
+    else if (frow < 1)
+        return(*status = BAD_ROW_NUM);
+    else if (fbit < 1)
+        return(*status = BAD_ELEM_NUM);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    /* rescan header if data structure is undefined */
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+        if ( ffrdef(fptr, status) > 0)               
+            return(*status);
+
+    fbyte = (fbit + 7) / 8;
+    lbyte = (fbit + nbit + 6) / 8;
+    nbyte = lbyte - fbyte +1;
+
+    /* Save the current heapsize; ffgcprll will increment the value if */
+    /* we are writing to a variable length column. */
+    offset = (fptr->Fptr)->heapsize;
+
+    /* call ffgcprll in case we are writing beyond the current end of   */
+    /* the table; it will allocate more space and shift any following */
+    /* HDU's.  Otherwise, we have little use for most of the returned */
+    /* parameters, therefore just use dummy parameters.               */
+
+    if (ffgcprll( fptr, colnum, frow, fbyte, nbyte, 1, &dummyd, &dummyd,
+        tform, &twidth, &tcode, &maxelem, &bstart, &elemnum, &incre,
+        &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0)
+        return(*status);
+
+    bitloc = fbit - 1 - ((fbit - 1) / 8 * 8);
+    ndone = 0;
+    rstart = frow - 1;
+    estart = fbyte - 1;
+
+    colptr  = (fptr->Fptr)->tableptr;   /* point to first column */
+    colptr += (colnum - 1);     /* offset to correct column structure */
+
+    tcode = colptr->tdatatype;
+
+    if (abs(tcode) > TBYTE)
+        return(*status = NOT_LOGICAL_COL); /* not correct datatype column */
+
+    if (tcode > 0)
+    {
+        descrp = FALSE;  /* not a variable length descriptor column */
+        repeat = colptr->trepeat;
+
+        if (tcode == TBIT)
+            repeat = (repeat + 7) / 8; /* convert from bits to bytes */
+
+        if (fbyte > repeat)
+            return(*status = BAD_ELEM_NUM);
+
+        /* calc the i/o pointer location to start of sequence of pixels */
+        bstart = (fptr->Fptr)->datastart + ((fptr->Fptr)->rowlength * rstart) +
+               colptr->tbcol + estart;
+    }
+    else
+    {
+        descrp = TRUE;  /* a variable length descriptor column */
+        /* only bit arrays (tform = 'X') are supported for variable */
+        /* length arrays.  REPEAT is the number of BITS in the array. */
+
+        repeat = fbit + nbit -1;
+
+        /* write the number of elements and the starting offset.    */
+        /* Note: ffgcprll previous wrote the descripter, but with the */
+        /* wrong repeat value  (gave bytes instead of bits).        */
+
+        if (tcode == -TBIT)
+            ffpdes(fptr, colnum, frow, (long) repeat, offset, status);
+
+        /* Calc the i/o pointer location to start of sequence of pixels.   */
+        /* ffgcprll has already calculated a value for bstart that         */
+        /* points to the first element of the vector; we just have to      */
+        /* increment it to point to the first element we want to write to. */
+        /* Note: ffgcprll also already updated the size of the heap, so we */
+        /* don't have to do that again here.                               */
+
+        bstart += estart;
+    }
+
+    /* move the i/o pointer to the start of the pixel sequence */
+    ffmbyt(fptr, bstart, IGNORE_EOF, status);
+
+    /* read the next byte (we may only be modifying some of the bits) */
+    while (1)
+    {
+      if (ffgbyt(fptr, 1, &cbuff, status) == END_OF_FILE)
+      {
+        /* hit end of file trying to read the byte, so just set byte = 0 */
+        *status = 0;
+        cbuff = 0;
+      }
+
+      /* move back, to be able to overwrite the byte */
+      ffmbyt(fptr, bstart, IGNORE_EOF, status);
+ 
+      for (ii = bitloc; (ii < 8) && (ndone < nbit); ii++, ndone++)
+      {
+        if(larray[ndone])
+          cbuff = cbuff | onbit[ii];
+        else
+          cbuff = cbuff & offbit[ii];
+      }
+
+      ffpbyt(fptr, 1, &cbuff, status); /* write the modified byte */
+
+      if (ndone == nbit)  /* finished all the bits */
+        return(*status);
+
+      /* not done, so get the next byte */
+      bstart++;
+      if (!descrp)
+      {
+        estart++;
+        if (estart == repeat)
+        {
+          /* move the i/o pointer to the next row of pixels */
+          estart = 0;
+          rstart = rstart + 1;
+          bstart = (fptr->Fptr)->datastart + ((fptr->Fptr)->rowlength * rstart) +
+               colptr->tbcol;
+
+          ffmbyt(fptr, bstart, IGNORE_EOF, status);
+        }
+      }
+      bitloc = 0;
+    }
+}
+
diff --git a/src/plugins/cfitsio/putcols.c b/src/plugins/cfitsio/putcols.c
new file mode 100644
index 0000000..86d624e
--- /dev/null
+++ b/src/plugins/cfitsio/putcols.c
@@ -0,0 +1,303 @@
+/*  This file, putcols.c, contains routines that write data elements to    */
+/*  a FITS image or table, of type character string.                       */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+
+#include <string.h>
+#include <stdlib.h>
+#include "fitsio2.h"
+/*--------------------------------------------------------------------------*/
+int ffpcls( fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  colnum,     /* I - number of column to write (1 = 1st col) */
+            LONGLONG  firstrow,  /* I - first row to write (1 = 1st row)        */
+            LONGLONG  firstelem, /* I - first vector element to write (1 = 1st) */
+            LONGLONG  nelem,     /* I - number of strings to write              */
+            char  **array,   /* I - array of pointers to strings            */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of string values to a column in the current FITS HDU.
+*/
+{
+    int tcode, maxelem, hdutype, nchar;
+    long twidth, incre;
+    long ii, jj, ntodo;
+    LONGLONG repeat, startpos, elemnum, wrtptr, rowlen, rownum, remain, next, tnull;
+    double scale, zero;
+    char tform[20], *blanks;
+    char message[FLEN_ERRMSG];
+    char snull[20];   /*  the FITS null value  */
+    tcolumn *colptr;
+
+    double cbuff[DBUFFSIZE / sizeof(double)]; /* align cbuff on word boundary */
+    char *buffer, *arrayptr;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+    {
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    }
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+    {
+        if ( ffrdef(fptr, status) > 0)               /* rescan header */
+            return(*status);
+    }
+
+    /*---------------------------------------------------*/
+    /*  Check input and get parameters about the column: */
+    /*---------------------------------------------------*/
+    if (colnum < 1 || colnum > (fptr->Fptr)->tfield)
+    {
+        sprintf(message, "Specified column number is out of range: %d",
+                colnum);
+        ffpmsg(message);
+        return(*status = BAD_COL_NUM);
+    }
+
+    colptr  = (fptr->Fptr)->tableptr;   /* point to first column */
+    colptr += (colnum - 1);     /* offset to correct column structure */
+    tcode = colptr->tdatatype;
+
+    if (tcode == -TSTRING) /* variable length column in a binary table? */
+    {
+      /* only write a single string; ignore value of firstelem */
+      nchar = maxvalue(1,strlen(array[0])); /* will write at least 1 char */
+                                          /* even if input string is null */
+
+      if (ffgcprll( fptr, colnum, firstrow, 1, nchar, 1, &scale, &zero,
+        tform, &twidth, &tcode, &maxelem, &startpos,  &elemnum, &incre,
+        &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0)
+        return(*status);
+	
+      /* simply move to write position, then write the string */
+      ffmbyt(fptr, startpos, IGNORE_EOF, status); 
+      ffpbyt(fptr, nchar, array[0], status);
+
+      if (*status > 0)  /* test for error during previous write operation */
+      {
+         sprintf(message,
+          "Error writing to variable length string column (ffpcls).");
+         ffpmsg(message);
+      }
+
+      return(*status);
+    }
+    else if (tcode == TSTRING)
+    {
+      if (ffgcprll( fptr, colnum, firstrow, firstelem, nelem, 1, &scale, &zero,
+        tform, &twidth, &tcode, &maxelem, &startpos,  &elemnum, &incre,
+        &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0)
+        return(*status);
+
+      /* if string length is greater than a FITS block (2880 char) then must */
+      /* only write 1 string at a time, to force writein by ffpbyt instead of */
+      /* ffpbytoff (ffpbytoff can't handle this case) */
+      if (twidth > IOBUFLEN) {
+        maxelem = 1;
+        incre = twidth;
+        repeat = 1;
+      }   
+
+      blanks = (char *) malloc(twidth); /* string for blank fill values */
+      if (!blanks)
+      {
+        ffpmsg("Could not allocate memory for string (ffpcls)");
+        return(*status = ARRAY_TOO_BIG);
+      }
+
+      for (ii = 0; ii < twidth; ii++)
+          blanks[ii] = ' ';          /* fill string with blanks */
+
+      remain = nelem;           /* remaining number of values to write  */
+    }
+    else 
+      return(*status = NOT_ASCII_COL);
+ 
+    /*-------------------------------------------------------*/
+    /*  Now write the strings to the FITS column.            */
+    /*-------------------------------------------------------*/
+
+    next = 0;                 /* next element in array to be written  */
+    rownum = 0;               /* row number, relative to firstrow     */
+
+    while (remain)
+    {
+      /* limit the number of pixels to process at one time to the number that
+         will fit in the buffer space or to the number of pixels that remain
+         in the current vector, which ever is smaller.
+      */
+      ntodo = (long) minvalue(remain, maxelem);      
+      ntodo = (long) minvalue(ntodo, (repeat - elemnum));
+
+      wrtptr = startpos + (rownum * rowlen) + (elemnum * incre);
+      ffmbyt(fptr, wrtptr, IGNORE_EOF, status);  /* move to write position */
+
+      buffer = (char *) cbuff;
+
+      /* copy the user's strings into the buffer */
+      for (ii = 0; ii < ntodo; ii++)
+      {
+         arrayptr = array[next];
+
+         for (jj = 0; jj < twidth; jj++)  /*  copy the string, char by char */
+         {
+            if (*arrayptr)
+            {
+              *buffer = *arrayptr;
+              buffer++;
+              arrayptr++;
+            }
+            else
+              break;
+         }
+
+         for (;jj < twidth; jj++)    /* fill field with blanks, if needed */
+         {
+           *buffer = ' ';
+           buffer++;
+         }
+
+         next++;
+      }
+
+      /* write the buffer full of strings to the FITS file */
+      if (incre == twidth)
+         ffpbyt(fptr, ntodo * twidth, cbuff, status);
+      else
+         ffpbytoff(fptr, twidth, ntodo, incre - twidth, cbuff, status);
+
+      if (*status > 0)  /* test for error during previous write operation */
+      {
+         sprintf(message,
+          "Error writing elements %.0f thru %.0f of input data array (ffpcls).",
+             (double) (next+1), (double) (next+ntodo));
+         ffpmsg(message);
+
+         if (blanks)
+           free(blanks);
+
+         return(*status);
+      }
+
+      /*--------------------------------------------*/
+      /*  increment the counters for the next loop  */
+      /*--------------------------------------------*/
+      remain -= ntodo;
+      if (remain)
+      {
+          elemnum += ntodo;
+          if (elemnum == repeat)  /* completed a row; start on next row */
+          {
+              elemnum = 0;
+              rownum++;
+          }
+       }
+    }  /*  End of main while Loop  */
+
+    if (blanks)
+      free(blanks);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpcns( fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  colnum,     /* I - number of column to write (1 = 1st col) */
+            LONGLONG  firstrow,  /* I - first row to write (1 = 1st row)        */
+            LONGLONG  firstelem, /* I - first vector element to write (1 = 1st) */
+            LONGLONG  nelem,     /* I - number of values to write               */
+            char  **array,   /* I - array of values to write                */
+            char  *nulvalue, /* I - string representing a null value        */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of elements to the specified column of a table.  Any input
+  pixels flagged as null will be replaced by the appropriate
+  null value in the output FITS file. 
+*/
+{
+    long repeat, width, ngood = 0, nbad = 0, ii;
+    LONGLONG first, fstelm, fstrow;
+
+    if (*status > 0)
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+    {
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    }
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+    {
+        if ( ffrdef(fptr, status) > 0)               /* rescan header */
+            return(*status);
+    }
+
+    /* get the vector repeat length of the column */
+    ffgtcl(fptr, colnum, NULL, &repeat, &width, status);
+
+    if ((fptr->Fptr)->hdutype == BINARY_TBL)
+        repeat = repeat / width;    /* convert from chars to unit strings */
+
+    /* absolute element number in the column */
+    first = (firstrow - 1) * repeat + firstelem;
+
+    for (ii = 0; ii < nelem; ii++)
+    {
+      if (strcmp(nulvalue, array[ii]))  /* is this a good pixel? */
+      {
+         if (nbad)  /* write previous string of bad pixels */
+         {
+            fstelm = ii - nbad + first;  /* absolute element number */
+            fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+            fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+            if (ffpclu(fptr, colnum, fstrow, fstelm, nbad, status) > 0)
+                return(*status);
+            nbad=0;
+         }
+
+         ngood = ngood +1;  /* the consecutive number of good pixels */
+      }
+      else
+      {
+         if (ngood)  /* write previous string of good pixels */
+         {
+            fstelm = ii - ngood + first;  /* absolute element number */
+            fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+            fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+            if (ffpcls(fptr, colnum, fstrow, fstelm, ngood, &array[ii-ngood],
+                status) > 0)
+                return(*status);
+
+            ngood=0;
+         }
+
+         nbad = nbad +1;  /* the consecutive number of bad pixels */
+      }
+    }
+
+    /* finished loop;  now just write the last set of pixels */
+
+    if (ngood)  /* write last string of good pixels */
+    {
+      fstelm = ii - ngood + first;  /* absolute element number */
+      fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+      fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+      ffpcls(fptr, colnum, fstrow, fstelm, ngood, &array[ii-ngood], status);
+    }
+    else if (nbad) /* write last string of bad pixels */
+    {
+      fstelm = ii - nbad + first;  /* absolute element number */
+      fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+      fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+      ffpclu(fptr, colnum, fstrow, fstelm, nbad, status);
+    }
+
+    return(*status);
+}
diff --git a/src/plugins/cfitsio/putcolsb.c b/src/plugins/cfitsio/putcolsb.c
new file mode 100644
index 0000000..25ee5d1
--- /dev/null
+++ b/src/plugins/cfitsio/putcolsb.c
@@ -0,0 +1,974 @@
+/*  This file, putcolsb.c, contains routines that write data elements to   */
+/*  a FITS image or table with signed char (signed byte) datatype.         */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+
+#include <limits.h>
+#include <string.h>
+#include <stdlib.h>
+#include "fitsio2.h"
+
+/*--------------------------------------------------------------------------*/
+int ffpprsb( fitsfile *fptr,  /* I - FITS file pointer                      */
+            long  group,     /* I - group to write(1 = 1st group)           */
+            LONGLONG  firstelem, /* I - first vector element to write(1 = 1st)  */
+            LONGLONG  nelem,     /* I - number of values to write               */
+            signed char *array, /* I - array of values that are written     */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being written).
+*/
+{
+    long row;
+    signed char nullvalue;
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        fits_write_compressed_pixels(fptr, TSBYTE, firstelem, nelem,
+            0, array, &nullvalue, status);
+        return(*status);
+    }
+
+    row=maxvalue(1,group);
+
+    ffpclsb(fptr, 2, row, firstelem, nelem, array, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffppnsb( fitsfile *fptr,  /* I - FITS file pointer                      */
+            long  group,     /* I - group to write(1 = 1st group)           */
+            LONGLONG  firstelem, /* I - first vector element to write(1 = 1st)  */
+            LONGLONG  nelem,     /* I - number of values to write               */
+            signed char *array, /* I - array of values that are written     */
+            signed char nulval, /* I - undefined pixel value                */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being written).  Any array values
+  that are equal to the value of nulval will be replaced with the null
+  pixel value that is appropriate for this column.
+*/
+{
+    long row;
+    signed char nullvalue;
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        nullvalue = nulval;  /* set local variable */
+        fits_write_compressed_pixels(fptr, TSBYTE, firstelem, nelem,
+            1, array, &nullvalue, status);
+        return(*status);
+    }
+
+    row=maxvalue(1,group);
+
+    ffpcnsb(fptr, 2, row, firstelem, nelem, array, nulval, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffp2dsb(fitsfile *fptr,   /* I - FITS file pointer                    */
+           long  group,      /* I - group to write(1 = 1st group)         */
+           LONGLONG  ncols,      /* I - number of pixels in each row of array */
+           LONGLONG  naxis1,     /* I - FITS image NAXIS1 value               */
+           LONGLONG  naxis2,     /* I - FITS image NAXIS2 value               */
+           signed char *array, /* I - array to be written                 */
+           int  *status)     /* IO - error status                         */
+/*
+  Write an entire 2-D array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being written).
+*/
+{
+    /* call the 3D writing routine, with the 3rd dimension = 1 */
+
+    ffp3dsb(fptr, group, ncols, naxis2, naxis1, naxis2, 1, array, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffp3dsb(fitsfile *fptr,   /* I - FITS file pointer                    */
+           long  group,      /* I - group to write(1 = 1st group)         */
+           LONGLONG  ncols,      /* I - number of pixels in each row of array */
+           LONGLONG  nrows,      /* I - number of rows in each plane of array */
+           LONGLONG  naxis1,     /* I - FITS image NAXIS1 value               */
+           LONGLONG  naxis2,     /* I - FITS image NAXIS2 value               */
+           LONGLONG  naxis3,     /* I - FITS image NAXIS3 value               */
+           signed char *array, /* I - array to be written                 */
+           int  *status)     /* IO - error status                         */
+/*
+  Write an entire 3-D cube of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being written).
+*/
+{
+    long tablerow, ii, jj;
+    long fpixel[3]= {1,1,1}, lpixel[3];
+    LONGLONG nfits, narray;
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+           
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+        lpixel[0] = (long) ncols;
+        lpixel[1] = (long) nrows;
+        lpixel[2] = (long) naxis3;
+       
+        fits_write_compressed_img(fptr, TSBYTE, fpixel, lpixel,
+            0,  array, NULL, status);
+    
+        return(*status);
+    }
+
+    tablerow=maxvalue(1,group);
+
+    if (ncols == naxis1 && nrows == naxis2)  /* arrays have same size? */
+    {
+      /* all the image pixels are contiguous, so write all at once */
+      ffpclsb(fptr, 2, tablerow, 1L, naxis1 * naxis2 * naxis3, array, status);
+      return(*status);
+    }
+
+    if (ncols < naxis1 || nrows < naxis2)
+       return(*status = BAD_DIMEN);
+
+    nfits = 1;   /* next pixel in FITS image to write to */
+    narray = 0;  /* next pixel in input array to be written */
+
+    /* loop over naxis3 planes in the data cube */
+    for (jj = 0; jj < naxis3; jj++)
+    {
+      /* loop over the naxis2 rows in the FITS image, */
+      /* writing naxis1 pixels to each row            */
+
+      for (ii = 0; ii < naxis2; ii++)
+      {
+       if (ffpclsb(fptr, 2, tablerow, nfits, naxis1,&array[narray],status) > 0)
+         return(*status);
+
+       nfits += naxis1;
+       narray += ncols;
+      }
+      narray += (nrows - naxis2) * ncols;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpsssb(fitsfile *fptr,   /* I - FITS file pointer                      */
+           long  group,      /* I - group to write(1 = 1st group)           */
+           long  naxis,      /* I - number of data axes in array            */
+           long  *naxes,     /* I - size of each FITS axis                  */
+           long  *fpixel,    /* I - 1st pixel in each axis to write (1=1st) */
+           long  *lpixel,    /* I - last pixel in each axis to write        */
+           signed char *array, /* I - array to be written                   */
+           int  *status)     /* IO - error status                           */
+/*
+  Write a subsection of pixels to the primary array or image.
+  A subsection is defined to be any contiguous rectangular
+  array of pixels within the n-dimensional FITS data file.
+  Data conversion and scaling will be performed if necessary 
+  (e.g, if the datatype of the FITS array is not the same as
+  the array being written).
+*/
+{
+    long tablerow;
+    LONGLONG fpix[7], dimen[7], astart, pstart;
+    LONGLONG off2, off3, off4, off5, off6, off7;
+    LONGLONG st10, st20, st30, st40, st50, st60, st70;
+    LONGLONG st1, st2, st3, st4, st5, st6, st7;
+    long ii, i1, i2, i3, i4, i5, i6, i7, irange[7];
+
+    if (*status > 0)
+        return(*status);
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        fits_write_compressed_img(fptr, TSBYTE, fpixel, lpixel,
+            0,  array, NULL, status);
+    
+        return(*status);
+    }
+
+    if (naxis < 1 || naxis > 7)
+      return(*status = BAD_DIMEN);
+
+    tablerow=maxvalue(1,group);
+
+     /* calculate the size and number of loops to perform in each dimension */
+    for (ii = 0; ii < 7; ii++)
+    {
+      fpix[ii]=1;
+      irange[ii]=1;
+      dimen[ii]=1;
+    }
+
+    for (ii = 0; ii < naxis; ii++)
+    {    
+      fpix[ii]=fpixel[ii];
+      irange[ii]=lpixel[ii]-fpixel[ii]+1;
+      dimen[ii]=naxes[ii];
+    }
+
+    i1=irange[0];
+
+    /* compute the pixel offset between each dimension */
+    off2 =     dimen[0];
+    off3 = off2 * dimen[1];
+    off4 = off3 * dimen[2];
+    off5 = off4 * dimen[3];
+    off6 = off5 * dimen[4];
+    off7 = off6 * dimen[5];
+
+    st10 = fpix[0];
+    st20 = (fpix[1] - 1) * off2;
+    st30 = (fpix[2] - 1) * off3;
+    st40 = (fpix[3] - 1) * off4;
+    st50 = (fpix[4] - 1) * off5;
+    st60 = (fpix[5] - 1) * off6;
+    st70 = (fpix[6] - 1) * off7;
+
+    /* store the initial offset in each dimension */
+    st1 = st10;
+    st2 = st20;
+    st3 = st30;
+    st4 = st40;
+    st5 = st50;
+    st6 = st60;
+    st7 = st70;
+
+    astart = 0;
+
+    for (i7 = 0; i7 < irange[6]; i7++)
+    {
+     for (i6 = 0; i6 < irange[5]; i6++)
+     {
+      for (i5 = 0; i5 < irange[4]; i5++)
+      {
+       for (i4 = 0; i4 < irange[3]; i4++)
+       {
+        for (i3 = 0; i3 < irange[2]; i3++)
+        {
+         pstart = st1 + st2 + st3 + st4 + st5 + st6 + st7;
+
+         for (i2 = 0; i2 < irange[1]; i2++)
+         {
+           if (ffpclsb(fptr, 2, tablerow, pstart, i1, &array[astart],
+              status) > 0)
+              return(*status);
+
+           astart += i1;
+           pstart += off2;
+         }
+         st2 = st20;
+         st3 = st3+off3;    
+        }
+        st3 = st30;
+        st4 = st4+off4;
+       }
+       st4 = st40;
+       st5 = st5+off5;
+      }
+      st5 = st50;
+      st6 = st6+off6;
+     }
+     st6 = st60;
+     st7 = st7+off7;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpgpsb( fitsfile *fptr,   /* I - FITS file pointer                     */
+            long  group,      /* I - group to write(1 = 1st group)          */
+            long  firstelem,  /* I - first vector element to write(1 = 1st) */
+            long  nelem,      /* I - number of values to write              */
+            signed char *array, /* I - array of values that are written     */
+            int  *status)     /* IO - error status                          */
+/*
+  Write an array of group parameters to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being written).
+*/
+{
+    long row;
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffpclsb(fptr, 1L, row, firstelem, nelem, array, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpclsb( fitsfile *fptr,  /* I - FITS file pointer                      */
+            int  colnum,     /* I - number of column to write (1 = 1st col) */
+            LONGLONG  firstrow,  /* I - first row to write (1 = 1st row)        */
+            LONGLONG  firstelem, /* I - first vector element to write (1 = 1st) */
+            LONGLONG  nelem,     /* I - number of values to write               */
+            signed char *array, /* I - array of values to write             */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of values to a column in the current FITS HDU.
+  The column number may refer to a real column in an ASCII or binary table, 
+  or it may refer to a virtual column in a 1 or more grouped FITS primary
+  array.  FITSIO treats a primary array as a binary table with
+  2 vector columns: the first column contains the group parameters (often
+  with length = 0) and the second column contains the array of image pixels.
+  Each row of the table represents a group in the case of multigroup FITS
+  images.
+
+  The input array of values will be converted to the datatype of the column 
+  and will be inverse-scaled by the FITS TSCALn and TZEROn values if necessary.
+*/
+{
+    int tcode, maxelem, hdutype;
+    long twidth, incre;
+    long ntodo;
+    LONGLONG repeat, startpos, elemnum, wrtptr, rowlen, rownum, remain, next, tnull;
+    double scale, zero;
+    char tform[20], cform[20];
+    char message[FLEN_ERRMSG];
+
+    char snull[20];   /*  the FITS null value  */
+
+    double cbuff[DBUFFSIZE / sizeof(double)]; /* align cbuff on word boundary */
+    void *buffer;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    buffer = cbuff;
+
+    /*---------------------------------------------------*/
+    /*  Check input and get parameters about the column: */
+    /*---------------------------------------------------*/
+    if (ffgcprll( fptr, colnum, firstrow, firstelem, nelem, 1, &scale, &zero,
+        tform, &twidth, &tcode, &maxelem, &startpos,  &elemnum, &incre,
+        &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0)
+        return(*status);
+
+    if (tcode == TSTRING)   
+         ffcfmt(tform, cform);     /* derive C format for writing strings */
+
+    /*---------------------------------------------------------------------*/
+    /*  Now write the pixels to the FITS column.                           */
+    /*  First call the ffXXfYY routine to  (1) convert the datatype        */
+    /*  if necessary, and (2) scale the values by the FITS TSCALn and      */
+    /*  TZEROn linear scaling parameters into a temporary buffer.          */
+    /*---------------------------------------------------------------------*/
+    remain = nelem;           /* remaining number of values to write  */
+    next = 0;                 /* next element in array to be written  */
+    rownum = 0;               /* row number, relative to firstrow     */
+
+    while (remain)
+    {
+        /* limit the number of pixels to process a one time to the number that
+           will fit in the buffer space or to the number of pixels that remain
+           in the current vector, which ever is smaller.
+        */
+        ntodo = (long) minvalue(remain, maxelem);      
+        ntodo = (long) minvalue(ntodo, (repeat - elemnum));
+
+        wrtptr = startpos + ((LONGLONG)rownum * rowlen) + (elemnum * incre);
+        ffmbyt(fptr, wrtptr, IGNORE_EOF, status); /* move to write position */
+
+        switch (tcode) 
+        {
+            case (TBYTE):
+
+                /* convert the raw data before writing to FITS file */
+                ffs1fi1(&array[next], ntodo, scale, zero,
+                        (unsigned char *) buffer, status);
+                ffpi1b(fptr, ntodo, incre, (unsigned char *) buffer, status);
+
+              break;
+
+            case (TLONGLONG):
+
+                ffs1fi8(&array[next], ntodo, scale, zero,
+                        (LONGLONG *) buffer, status);
+                ffpi8b(fptr, ntodo, incre, (long *) buffer, status);
+                break;
+
+            case (TSHORT):
+ 
+                ffs1fi2(&array[next], ntodo, scale, zero,
+                        (short *) buffer, status);
+                ffpi2b(fptr, ntodo, incre, (short *) buffer, status);
+                break;
+
+            case (TLONG):
+
+                ffs1fi4(&array[next], ntodo, scale, zero,
+                        (INT32BIT *) buffer, status);
+                ffpi4b(fptr, ntodo, incre, (INT32BIT *) buffer, status);
+                break;
+
+            case (TFLOAT):
+
+                ffs1fr4(&array[next], ntodo, scale, zero,
+                        (float *)  buffer, status);
+                ffpr4b(fptr, ntodo, incre, (float *) buffer, status);
+                break;
+
+            case (TDOUBLE):
+                ffs1fr8(&array[next], ntodo, scale, zero,
+                        (double *) buffer, status);
+                ffpr8b(fptr, ntodo, incre, (double *) buffer, status);
+                break;
+
+            case (TSTRING):  /* numerical column in an ASCII table */
+
+                if (strchr(tform,'A'))
+                {
+                    /* write raw input bytes without conversion        */
+                    /* This case is a hack to let users write a stream */
+                    /* of bytes directly to the 'A' format column      */
+
+                    if (incre == twidth)
+                        ffpbyt(fptr, ntodo, &array[next], status);
+                    else
+                        ffpbytoff(fptr, twidth, ntodo/twidth, incre - twidth, 
+                                &array[next], status);
+                    break;
+                }
+                else if (cform[1] != 's')  /*  "%s" format is a string */
+                {
+                  ffs1fstr(&array[next], ntodo, scale, zero, cform,
+                          twidth, (char *) buffer, status);
+
+                  if (incre == twidth)    /* contiguous bytes */
+                     ffpbyt(fptr, ntodo * twidth, buffer, status);
+                  else
+                     ffpbytoff(fptr, twidth, ntodo, incre - twidth, buffer,
+                            status);
+                  break;
+                }
+                /* can't write to string column, so fall thru to default: */
+
+            default:  /*  error trap  */
+                sprintf(message, 
+                       "Cannot write numbers to column %d which has format %s",
+                        colnum,tform);
+                ffpmsg(message);
+                if (hdutype == ASCII_TBL)
+                    return(*status = BAD_ATABLE_FORMAT);
+                else
+                    return(*status = BAD_BTABLE_FORMAT);
+
+        } /* End of switch block */
+
+        /*-------------------------*/
+        /*  Check for fatal error  */
+        /*-------------------------*/
+        if (*status > 0)  /* test for error during previous write operation */
+        {
+          sprintf(message,
+          "Error writing elements %.0f thru %.0f of input data array (ffpclsb).",
+              (double) (next+1), (double) (next+ntodo));
+          ffpmsg(message);
+          return(*status);
+        }
+
+        /*--------------------------------------------*/
+        /*  increment the counters for the next loop  */
+        /*--------------------------------------------*/
+        remain -= ntodo;
+        if (remain)
+        {
+            next += ntodo;
+            elemnum += ntodo;
+            if (elemnum == repeat)  /* completed a row; start on next row */
+            {
+                elemnum = 0;
+                rownum++;
+            }
+        }
+    }  /*  End of main while Loop  */
+
+
+    /*--------------------------------*/
+    /*  check for numerical overflow  */
+    /*--------------------------------*/
+    if (*status == OVERFLOW_ERR)
+    {
+      ffpmsg(
+      "Numerical overflow during type conversion while writing FITS data.");
+      *status = NUM_OVERFLOW;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpcnsb( fitsfile *fptr,  /* I - FITS file pointer                      */
+            int  colnum,     /* I - number of column to write (1 = 1st col) */
+            LONGLONG  firstrow,  /* I - first row to write (1 = 1st row)        */
+            LONGLONG  firstelem, /* I - first vector element to write (1 = 1st) */
+            LONGLONG  nelem,     /* I - number of values to write               */
+            signed char *array,   /* I - array of values to write           */
+            signed char nulvalue, /* I - flag for undefined pixels          */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of elements to the specified column of a table.  Any input
+  pixels equal to the value of nulvalue will be replaced by the appropriate
+  null value in the output FITS file. 
+
+  The input array of values will be converted to the datatype of the column 
+  and will be inverse-scaled by the FITS TSCALn and TZEROn values if necessary
+*/
+{
+    tcolumn *colptr;
+    long  ngood = 0, nbad = 0, ii;
+    LONGLONG repeat, first, fstelm, fstrow;
+    int tcode, overflow = 0;
+
+    if (*status > 0)
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+    {
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    }
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+    {
+        if ( ffrdef(fptr, status) > 0)               /* rescan header */
+            return(*status);
+    }
+
+    colptr  = (fptr->Fptr)->tableptr;   /* point to first column */
+    colptr += (colnum - 1);     /* offset to correct column structure */
+
+    tcode  = colptr->tdatatype;
+
+    if (tcode > 0)
+       repeat = colptr->trepeat;  /* repeat count for this column */
+    else
+       repeat = firstelem -1 + nelem;  /* variable length arrays */
+
+    /* if variable length array, first write the whole input vector, 
+       then go back and fill in the nulls */
+    if (tcode < 0) {
+      if (ffpclsb(fptr, colnum, firstrow, firstelem, nelem, array, status) > 0) {
+        if (*status == NUM_OVERFLOW) 
+	{
+	  /* ignore overflows, which are possibly the null pixel values */
+	  /*  overflow = 1;   */
+	  *status = 0;
+	} else { 
+          return(*status);
+	}
+      }
+    }
+
+    /* absolute element number in the column */
+    first = (firstrow - 1) * repeat + firstelem;
+
+    for (ii = 0; ii < nelem; ii++)
+    {
+      if (array[ii] != nulvalue)  /* is this a good pixel? */
+      {
+         if (nbad)  /* write previous string of bad pixels */
+         {
+            fstelm = ii - nbad + first;  /* absolute element number */
+            fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+            fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+            if (ffpclu(fptr, colnum, fstrow, fstelm, nbad, status) > 0)
+                return(*status);
+
+            nbad=0;
+         }
+
+         ngood = ngood + 1;  /* the consecutive number of good pixels */
+      }
+      else
+      {
+         if (ngood)  /* write previous string of good pixels */
+         {
+            fstelm = ii - ngood + first;  /* absolute element number */
+            fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+            fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+            if (tcode > 0) {  /* variable length arrays have already been written */
+              if (ffpclsb(fptr, colnum, fstrow, fstelm, ngood, &array[ii-ngood],
+                status) > 0) {
+		if (*status == NUM_OVERFLOW) 
+		{
+		  overflow = 1;
+		  *status = 0;
+		} else { 
+                  return(*status);
+		}
+	      }
+	    }
+            ngood=0;
+         }
+
+         nbad = nbad + 1;  /* the consecutive number of bad pixels */
+      }
+    }
+
+    /* finished loop;  now just write the last set of pixels */
+
+    if (ngood)  /* write last string of good pixels */
+    {
+      fstelm = ii - ngood + first;  /* absolute element number */
+      fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+      fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+      if (tcode > 0) {  /* variable length arrays have already been written */
+        ffpclsb(fptr, colnum, fstrow, fstelm, ngood, &array[ii-ngood], status);
+      }
+    }
+    else if (nbad) /* write last string of bad pixels */
+    {
+      fstelm = ii - nbad + first;  /* absolute element number */
+      fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+      fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+      ffpclu(fptr, colnum, fstrow, fstelm, nbad, status);
+    }
+
+    if (*status <= 0) {
+      if (overflow) {
+        *status = NUM_OVERFLOW;
+      }
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffs1fi1(signed char *input,    /* I - array of values to be converted  */
+            long ntodo,            /* I - number of elements in the array  */
+            double scale,          /* I - FITS TSCALn or BSCALE value      */
+            double zero,           /* I - FITS TZEROn or BZERO  value      */
+            unsigned char *output, /* O - output array of converted values */
+            int *status)           /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == -128.)
+    {
+        /* Instead of adding 128, it is more efficient */
+        /* to just flip the sign bit with the XOR operator */
+
+        for (ii = 0; ii < ntodo; ii++)
+             output[ii] =  ( *(unsigned char *) &input[ii] ) ^ 0x80;
+    }
+    else if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            if (input[ii] < 0)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = 0;
+            }
+            else
+                output[ii] = (unsigned char) input[ii];
+        }
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = ( ((double) input[ii]) - zero) / scale;
+
+            if (dvalue < DUCHAR_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = 0;
+            }
+            else if (dvalue > DUCHAR_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = UCHAR_MAX;
+            }
+            else
+                output[ii] = (unsigned char) (dvalue + .5);
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffs1fi2(signed char *input,    /* I - array of values to be converted  */
+            long ntodo,            /* I - number of elements in the array  */
+            double scale,          /* I - FITS TSCALn or BSCALE value      */
+            double zero,           /* I - FITS TZEROn or BZERO  value      */
+            short *output,         /* O - output array of converted values */
+            int *status)           /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+            output[ii] = input[ii];   /* just copy input to output */
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = (((double) input[ii]) - zero) / scale;
+
+            if (dvalue < DSHRT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = SHRT_MIN;
+            }
+            else if (dvalue > DSHRT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = SHRT_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0)
+                    output[ii] = (short) (dvalue + .5);
+                else
+                    output[ii] = (short) (dvalue - .5);
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffs1fi4(signed char *input,    /* I - array of values to be converted  */
+            long ntodo,            /* I - number of elements in the array  */
+            double scale,          /* I - FITS TSCALn or BSCALE value      */
+            double zero,           /* I - FITS TZEROn or BZERO  value      */
+            INT32BIT *output,      /* O - output array of converted values */
+            int *status)           /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+            output[ii] = (INT32BIT) input[ii];   /* copy input to output */
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = (((double) input[ii]) - zero) / scale;
+
+            if (dvalue < DINT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = INT32_MIN;
+            }
+            else if (dvalue > DINT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = INT32_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0)
+                    output[ii] = (INT32BIT) (dvalue + .5);
+                else
+                    output[ii] = (INT32BIT) (dvalue - .5);
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffs1fi8(signed char *input,   /* I - array of values to be converted  */
+            long ntodo,           /* I - number of elements in the array  */
+            double scale,         /* I - FITS TSCALn or BSCALE value      */
+            double zero,          /* I - FITS TZEROn or BZERO  value      */
+            LONGLONG *output,     /* O - output array of converted values */
+            int *status)          /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+                output[ii] = input[ii];
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = (input[ii] - zero) / scale;
+
+            if (dvalue < DLONGLONG_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = LONGLONG_MIN;
+            }
+            else if (dvalue > DLONGLONG_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = LONGLONG_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0)
+                    output[ii] = (LONGLONG) (dvalue + .5);
+                else
+                    output[ii] = (LONGLONG) (dvalue - .5);
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffs1fr4(signed char *input,    /* I - array of values to be converted  */
+            long ntodo,            /* I - number of elements in the array  */
+            double scale,          /* I - FITS TSCALn or BSCALE value      */
+            double zero,           /* I - FITS TZEROn or BZERO  value      */
+            float *output,         /* O - output array of converted values */
+            int *status)           /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required.
+*/
+{
+    long ii;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (float) input[ii];
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+            output[ii] = (float) (( ( (double) input[ii] ) - zero) / scale);
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffs1fr8(signed char *input,    /* I - array of values to be converted  */
+            long ntodo,            /* I - number of elements in the array  */
+            double scale,          /* I - FITS TSCALn or BSCALE value      */
+            double zero,           /* I - FITS TZEROn or BZERO  value      */
+            double *output,        /* O - output array of converted values */
+            int *status)           /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required.
+*/
+{
+    long ii;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (double) input[ii];
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+            output[ii] = ( ( (double) input[ii] ) - zero) / scale;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffs1fstr(signed char *input, /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            char *cform,       /* I - format for output string values  */
+            long twidth,       /* I - width of each field, in chars    */
+            char *output,      /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do scaling if required.
+*/
+{
+    long ii;
+    double dvalue;
+    char *cptr;
+    
+    cptr = output;
+
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+        {
+           sprintf(output, cform, (double) input[ii]);
+           output += twidth;
+
+           if (*output)  /* if this char != \0, then overflow occurred */
+              *status = OVERFLOW_ERR;
+        }
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+          dvalue = ((double) input[ii] - zero) / scale;
+          sprintf(output, cform, dvalue);
+          output += twidth;
+
+          if (*output)  /* if this char != \0, then overflow occurred */
+            *status = OVERFLOW_ERR;
+        }
+    }
+
+    /* replace any commas with periods (e.g., in French locale) */
+    while ((cptr = strchr(cptr, ','))) *cptr = '.';
+    
+    return(*status);
+}
diff --git a/src/plugins/cfitsio/putcolu.c b/src/plugins/cfitsio/putcolu.c
new file mode 100644
index 0000000..90227f0
--- /dev/null
+++ b/src/plugins/cfitsio/putcolu.c
@@ -0,0 +1,629 @@
+/*  This file, putcolu.c, contains routines that write data elements to    */
+/*  a FITS image or table.  Writes null values.                            */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+
+#include <string.h>
+#include <stdlib.h>
+#include "fitsio2.h"
+
+/*--------------------------------------------------------------------------*/
+int ffppru( fitsfile *fptr,  /* I - FITS file pointer                       */
+            long  group,      /* I - group to write(1 = 1st group)          */
+            LONGLONG  firstelem,  /* I - first vector element to write(1 = 1st) */
+            LONGLONG  nelem,      /* I - number of values to write              */
+            int  *status)     /* IO - error status                          */
+/*
+  Write null values to the primary array.
+
+*/
+{
+    long row;
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        ffpmsg("writing to compressed image is not supported");
+
+        return(*status = DATA_COMPRESSION_ERR);
+    }
+
+    row=maxvalue(1,group);
+
+    ffpclu(fptr, 2, row, firstelem, nelem, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpprn( fitsfile *fptr,  /* I - FITS file pointer                       */
+            LONGLONG  firstelem,  /* I - first vector element to write(1 = 1st) */
+            LONGLONG  nelem,      /* I - number of values to write              */
+            int  *status)     /* IO - error status                          */
+/*
+  Write null values to the primary array. (Doesn't support groups).
+
+*/
+{
+    long row = 1;
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        ffpmsg("writing to compressed image is not supported");
+
+        return(*status = DATA_COMPRESSION_ERR);
+    }
+
+    ffpclu(fptr, 2, row, firstelem, nelem, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpclu( fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  colnum,     /* I - number of column to write (1 = 1st col) */
+            LONGLONG  firstrow,  /* I - first row to write (1 = 1st row)        */
+            LONGLONG  firstelem, /* I - first vector element to write (1 = 1st) */
+            LONGLONG  nelempar,     /* I - number of values to write               */
+            int  *status)    /* IO - error status                           */
+/*
+  Set elements of a table column to the appropriate null value for the column
+  The column number may refer to a real column in an ASCII or binary table, 
+  or it may refer to a virtual column in a 1 or more grouped FITS primary
+  array.  FITSIO treats a primary array as a binary table
+  with 2 vector columns: the first column contains the group parameters (often
+  with length = 0) and the second column contains the array of image pixels.
+  Each row of the table represents a group in the case of multigroup FITS
+  images.
+  
+  This routine support COMPLEX and DOUBLE COMPLEX binary table columns, and
+  sets both the real and imaginary components of the element to a NaN.
+*/
+{
+    int tcode, maxelem, hdutype, writemode = 2, leng;
+    short i2null;
+    INT32BIT i4null;
+    long twidth, incre;
+    long ii;
+    LONGLONG largeelem, nelem, tnull, i8null;
+    LONGLONG repeat, startpos, elemnum, wrtptr, rowlen, rownum, remain, next, ntodo;
+    double scale, zero;
+    unsigned char i1null, lognul = 0;
+    char tform[20], *cstring = 0;
+    char message[FLEN_ERRMSG];
+    char snull[20];   /*  the FITS null value  */
+    long   jbuff[2] = { -1, -1};  /* all bits set is equivalent to a NaN */
+    size_t buffsize;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    nelem = nelempar;
+    
+    largeelem = firstelem;
+
+    /*---------------------------------------------------*/
+    /*  Check input and get parameters about the column: */
+    /*---------------------------------------------------*/
+
+    /* note that writemode = 2 by default (not 1), so that the returned */
+    /* repeat and incre values will be the actual values for this column. */
+
+    /* If writing nulls to a variable length column then dummy data values  */
+    /* must have already been written to the heap. */
+    /* We just have to overwrite the previous values with null values. */
+    /* Set writemode = 0 in this case, to test that values have been written */
+
+    fits_get_coltype(fptr, colnum, &tcode, NULL, NULL, status);
+    if (tcode < 0)
+         writemode = 0;  /* this is a variable length column */
+
+    if (abs(tcode) >= TCOMPLEX)
+    { /* treat complex columns as pairs of numbers */
+      largeelem = (largeelem - 1) * 2 + 1;
+      nelem *= 2;
+    }
+
+    if (ffgcprll( fptr, colnum, firstrow, largeelem, nelem, writemode, &scale,
+       &zero, tform, &twidth, &tcode, &maxelem, &startpos,  &elemnum, &incre,
+        &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0)
+        return(*status);
+
+    if (tcode == TSTRING)
+    {
+      if (snull[0] == ASCII_NULL_UNDEFINED)
+      {
+        ffpmsg(
+        "Null value string for ASCII table column is not defined (FTPCLU).");
+        return(*status = NO_NULL);
+      }
+
+      /* allocate buffer to hold the null string.  Must write the entire */
+      /* width of the column (twidth bytes) to avoid possible problems */
+      /* with uninitialized FITS blocks, in case the field spans blocks */
+
+      buffsize = maxvalue(20, twidth);
+      cstring = (char *) malloc(buffsize);
+      if (!cstring)
+         return(*status = MEMORY_ALLOCATION);
+
+      memset(cstring, ' ', buffsize);  /* initialize  with blanks */
+
+      leng = strlen(snull);
+      if (hdutype == BINARY_TBL)
+         leng++;        /* copy the terminator too in binary tables */
+
+      strncpy(cstring, snull, leng);  /* copy null string to temp buffer */
+    }
+    else if ( tcode == TBYTE  ||
+              tcode == TSHORT ||
+              tcode == TLONG  ||
+              tcode == TLONGLONG) 
+    {
+      if (tnull == NULL_UNDEFINED)
+      {
+        ffpmsg(
+        "Null value for integer table column is not defined (FTPCLU).");
+        return(*status = NO_NULL);
+      }
+
+      if (tcode == TBYTE)
+         i1null = (unsigned char) tnull;
+      else if (tcode == TSHORT)
+      {
+         i2null = (short) tnull;
+#if BYTESWAPPED
+         ffswap2(&i2null, 1); /* reverse order of bytes */
+#endif
+      }
+      else if (tcode == TLONG)
+      {
+         i4null = (INT32BIT) tnull;
+#if BYTESWAPPED
+         ffswap4(&i4null, 1); /* reverse order of bytes */
+#endif
+      }
+      else
+      {
+         i8null = tnull;
+#if BYTESWAPPED
+         ffswap4( (INT32BIT*) (&i8null), 2); /* reverse order of bytes */
+#endif
+      }
+    }
+
+    /*---------------------------------------------------------------------*/
+    /*  Now write the pixels to the FITS column.                           */
+    /*---------------------------------------------------------------------*/
+    remain = nelem;           /* remaining number of values to write  */
+    next = 0;                 /* next element in array to be written  */
+    rownum = 0;               /* row number, relative to firstrow     */
+    ntodo = remain;           /* number of elements to write at one time */
+
+    while (ntodo)
+    {
+        /* limit the number of pixels to process at one time to the number that
+           will fit in the buffer space or to the number of pixels that remain
+           in the current vector, which ever is smaller.
+        */
+        ntodo = minvalue(ntodo, (repeat - elemnum));
+        wrtptr = startpos + ((LONGLONG)rownum * rowlen) + (elemnum * incre);
+
+        ffmbyt(fptr, wrtptr, IGNORE_EOF, status); /* move to write position */
+
+        switch (tcode) 
+        {
+            case (TBYTE):
+ 
+                for (ii = 0; ii < ntodo; ii++)
+                  ffpbyt(fptr, 1,  &i1null, status);
+                break;
+
+            case (TSHORT):
+
+                for (ii = 0; ii < ntodo; ii++)
+                  ffpbyt(fptr, 2, &i2null, status);
+                break;
+
+            case (TLONG):
+
+                for (ii = 0; ii < ntodo; ii++)
+                  ffpbyt(fptr, 4, &i4null, status);
+                break;
+
+            case (TLONGLONG):
+
+                for (ii = 0; ii < ntodo; ii++)
+                  ffpbyt(fptr, 8, &i8null, status);
+                break;
+
+            case (TFLOAT):
+
+                for (ii = 0; ii < ntodo; ii++)
+                  ffpbyt(fptr, 4, jbuff, status);
+                break;
+
+            case (TDOUBLE):
+
+                for (ii = 0; ii < ntodo; ii++)
+                  ffpbyt(fptr, 8, jbuff, status);
+                break;
+
+            case (TLOGICAL):
+ 
+                for (ii = 0; ii < ntodo; ii++)
+                  ffpbyt(fptr, 1, &lognul, status);
+                break;
+
+            case (TSTRING):  /* an ASCII table column */
+                /* repeat always = 1, so ntodo is also guaranteed to = 1 */
+                ffpbyt(fptr, twidth, cstring, status);
+                break;
+
+            default:  /*  error trap  */
+                sprintf(message, 
+                   "Cannot write null value to column %d which has format %s",
+                     colnum,tform);
+                ffpmsg(message);
+                return(*status);
+
+        } /* End of switch block */
+
+        /*-------------------------*/
+        /*  Check for fatal error  */
+        /*-------------------------*/
+        if (*status > 0)  /* test for error during previous write operation */
+        {
+           sprintf(message,
+             "Error writing %.0f thru %.0f of null values (ffpclu).",
+              (double) (next+1), (double) (next+ntodo));
+           ffpmsg(message);
+
+           if (cstring)
+              free(cstring);
+
+           return(*status);
+        }
+
+        /*--------------------------------------------*/
+        /*  increment the counters for the next loop  */
+        /*--------------------------------------------*/
+        remain -= ntodo;
+        if (remain)
+        {
+            next += ntodo;
+            elemnum += ntodo;
+            if (elemnum == repeat)  /* completed a row; start on next row */
+            {
+                elemnum = 0;
+                rownum++;
+            }
+        }
+        ntodo = remain;  /* this is the maximum number to do in next loop */
+
+    }  /*  End of main while Loop  */
+
+    if (cstring)
+       free(cstring);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpcluc( fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  colnum,     /* I - number of column to write (1 = 1st col) */
+            LONGLONG  firstrow,  /* I - first row to write (1 = 1st row)        */
+            LONGLONG  firstelem, /* I - first vector element to write (1 = 1st) */
+            LONGLONG  nelem,     /* I - number of values to write               */
+            int  *status)    /* IO - error status                           */
+/*
+  Set elements of a table column to the appropriate null value for the column
+  The column number may refer to a real column in an ASCII or binary table, 
+  or it may refer to a virtual column in a 1 or more grouped FITS primary
+  array.  FITSIO treats a primary array as a binary table
+  with 2 vector columns: the first column contains the group parameters (often
+  with length = 0) and the second column contains the array of image pixels.
+  Each row of the table represents a group in the case of multigroup FITS
+  images.
+  
+  This routine does not do anything special in the case of COMPLEX table columns
+  (unlike the similar ffpclu routine).  This routine is mainly for use by
+  ffpcne which already compensates for the effective doubling of the number of 
+  elements in a complex column.
+*/
+{
+    int tcode, maxelem, hdutype, writemode = 2, leng;
+    short i2null;
+    INT32BIT i4null;
+    long twidth, incre;
+    long ii;
+    LONGLONG tnull, i8null;
+    LONGLONG repeat, startpos, elemnum, wrtptr, rowlen, rownum, remain, next, ntodo;
+    double scale, zero;
+    unsigned char i1null, lognul = 0;
+    char tform[20], *cstring = 0;
+    char message[FLEN_ERRMSG];
+    char snull[20];   /*  the FITS null value  */
+    long   jbuff[2] = { -1, -1};  /* all bits set is equivalent to a NaN */
+    size_t buffsize;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    /*---------------------------------------------------*/
+    /*  Check input and get parameters about the column: */
+    /*---------------------------------------------------*/
+
+    /* note that writemode = 2 by default (not 1), so that the returned */
+    /* repeat and incre values will be the actual values for this column. */
+
+    /* If writing nulls to a variable length column then dummy data values  */
+    /* must have already been written to the heap. */
+    /* We just have to overwrite the previous values with null values. */
+    /* Set writemode = 0 in this case, to test that values have been written */
+
+    fits_get_coltype(fptr, colnum, &tcode, NULL, NULL, status);
+    if (tcode < 0)
+         writemode = 0;  /* this is a variable length column */
+    
+    if (ffgcprll( fptr, colnum, firstrow, firstelem, nelem, writemode, &scale,
+       &zero, tform, &twidth, &tcode, &maxelem, &startpos,  &elemnum, &incre,
+        &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0)
+        return(*status);
+
+    if (tcode == TSTRING)
+    {
+      if (snull[0] == ASCII_NULL_UNDEFINED)
+      {
+        ffpmsg(
+        "Null value string for ASCII table column is not defined (FTPCLU).");
+        return(*status = NO_NULL);
+      }
+
+      /* allocate buffer to hold the null string.  Must write the entire */
+      /* width of the column (twidth bytes) to avoid possible problems */
+      /* with uninitialized FITS blocks, in case the field spans blocks */
+
+      buffsize = maxvalue(20, twidth);
+      cstring = (char *) malloc(buffsize);
+      if (!cstring)
+         return(*status = MEMORY_ALLOCATION);
+
+      memset(cstring, ' ', buffsize);  /* initialize  with blanks */
+
+      leng = strlen(snull);
+      if (hdutype == BINARY_TBL)
+         leng++;        /* copy the terminator too in binary tables */
+
+      strncpy(cstring, snull, leng);  /* copy null string to temp buffer */
+
+    }
+    else if ( tcode == TBYTE  ||
+              tcode == TSHORT ||
+              tcode == TLONG  ||
+              tcode == TLONGLONG) 
+    {
+      if (tnull == NULL_UNDEFINED)
+      {
+        ffpmsg(
+        "Null value for integer table column is not defined (FTPCLU).");
+        return(*status = NO_NULL);
+      }
+
+      if (tcode == TBYTE)
+         i1null = (unsigned char) tnull;
+      else if (tcode == TSHORT)
+      {
+         i2null = (short) tnull;
+#if BYTESWAPPED
+         ffswap2(&i2null, 1); /* reverse order of bytes */
+#endif
+      }
+      else if (tcode == TLONG)
+      {
+         i4null = (INT32BIT) tnull;
+#if BYTESWAPPED
+         ffswap4(&i4null, 1); /* reverse order of bytes */
+#endif
+      }
+      else
+      {
+         i8null = tnull;
+#if BYTESWAPPED
+         ffswap4( (INT32BIT*) &i8null, 2); /* reverse order of bytes */
+#endif
+      }
+    }
+
+    /*---------------------------------------------------------------------*/
+    /*  Now write the pixels to the FITS column.                           */
+    /*---------------------------------------------------------------------*/
+    remain = nelem;           /* remaining number of values to write  */
+    next = 0;                 /* next element in array to be written  */
+    rownum = 0;               /* row number, relative to firstrow     */
+    ntodo = remain;           /* number of elements to write at one time */
+
+    while (ntodo)
+    {
+        /* limit the number of pixels to process at one time to the number that
+           will fit in the buffer space or to the number of pixels that remain
+           in the current vector, which ever is smaller.
+        */
+        ntodo = minvalue(ntodo, (repeat - elemnum));
+        wrtptr = startpos + ((LONGLONG)rownum * rowlen) + (elemnum * incre);
+
+        ffmbyt(fptr, wrtptr, IGNORE_EOF, status); /* move to write position */
+
+        switch (tcode) 
+        {
+            case (TBYTE):
+ 
+                for (ii = 0; ii < ntodo; ii++)
+                  ffpbyt(fptr, 1,  &i1null, status);
+                break;
+
+            case (TSHORT):
+
+                for (ii = 0; ii < ntodo; ii++)
+                  ffpbyt(fptr, 2, &i2null, status);
+                break;
+
+            case (TLONG):
+
+                for (ii = 0; ii < ntodo; ii++)
+                  ffpbyt(fptr, 4, &i4null, status);
+                break;
+
+            case (TLONGLONG):
+
+                for (ii = 0; ii < ntodo; ii++)
+                  ffpbyt(fptr, 8, &i8null, status);
+                break;
+
+            case (TFLOAT):
+
+                for (ii = 0; ii < ntodo; ii++)
+                  ffpbyt(fptr, 4, jbuff, status);
+                break;
+
+            case (TDOUBLE):
+
+                for (ii = 0; ii < ntodo; ii++)
+                  ffpbyt(fptr, 8, jbuff, status);
+                break;
+
+            case (TLOGICAL):
+ 
+                for (ii = 0; ii < ntodo; ii++)
+                  ffpbyt(fptr, 1, &lognul, status);
+                break;
+
+            case (TSTRING):  /* an ASCII table column */
+                /* repeat always = 1, so ntodo is also guaranteed to = 1 */
+                ffpbyt(fptr, twidth, cstring, status);
+                break;
+
+            default:  /*  error trap  */
+                sprintf(message, 
+                   "Cannot write null value to column %d which has format %s",
+                     colnum,tform);
+                ffpmsg(message);
+                return(*status);
+
+        } /* End of switch block */
+
+        /*-------------------------*/
+        /*  Check for fatal error  */
+        /*-------------------------*/
+        if (*status > 0)  /* test for error during previous write operation */
+        {
+           sprintf(message,
+             "Error writing %.0f thru %.0f of null values (ffpclu).",
+              (double) (next+1), (double) (next+ntodo));
+           ffpmsg(message);
+
+           if (cstring)
+              free(cstring);
+
+           return(*status);
+        }
+
+        /*--------------------------------------------*/
+        /*  increment the counters for the next loop  */
+        /*--------------------------------------------*/
+        remain -= ntodo;
+        if (remain)
+        {
+            next += ntodo;
+            elemnum += ntodo;
+            if (elemnum == repeat)  /* completed a row; start on next row */
+            {
+                elemnum = 0;
+                rownum++;
+            }
+        }
+        ntodo = remain;  /* this is the maximum number to do in next loop */
+
+    }  /*  End of main while Loop  */
+
+    if (cstring)
+       free(cstring);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffprwu(fitsfile *fptr,
+           LONGLONG firstrow,
+           LONGLONG nrows, 
+           int *status)
+
+/* 
+ * fits_write_nullrows / ffprwu - write TNULLs to all columns in one or more rows
+ *
+ * fitsfile *fptr - pointer to FITS HDU opened for read/write
+ * long int firstrow - first table row to set to null. (firstrow >= 1)
+ * long int nrows - total number or rows to set to null. (nrows >= 1)
+ * int *status - upon return, *status contains CFITSIO status code
+ *
+ * RETURNS: CFITSIO status code
+ *
+ * written by Craig Markwardt, GSFC 
+ */
+{
+  LONGLONG ntotrows;
+  int ncols, i;
+  int typecode = 0;
+  LONGLONG repeat = 0, width = 0;
+  int nullstatus;
+
+  if (*status > 0) return *status;
+
+  if ((firstrow <= 0) || (nrows <= 0)) return (*status = BAD_ROW_NUM);
+
+  fits_get_num_rowsll(fptr, &ntotrows, status);
+
+  if (firstrow + nrows - 1 > ntotrows) return (*status = BAD_ROW_NUM);
+  
+  fits_get_num_cols(fptr, &ncols, status);
+  if (*status) return *status;
+
+
+  /* Loop through each column and write nulls */
+  for (i=1; i <= ncols; i++) {
+    repeat = 0;  typecode = 0;  width = 0;
+    fits_get_coltypell(fptr, i, &typecode, &repeat, &width, status);
+    if (*status) break;
+
+    /* NOTE: data of TSTRING type must not write the total repeat
+       count, since the repeat count is the *character* count, not the
+       nstring count.  Divide by string width to get number of
+       strings. */
+    
+    if (typecode == TSTRING) repeat /= width;
+
+    /* Write NULLs */
+    nullstatus = 0;
+    fits_write_col_null(fptr, i, firstrow, 1, repeat*nrows, &nullstatus);
+
+    /* ignore error if no null value is defined for the column */
+    if (nullstatus && nullstatus != NO_NULL) return (*status = nullstatus);
+    
+  }
+    
+  return *status;
+}
+
diff --git a/src/plugins/cfitsio/putcolui.c b/src/plugins/cfitsio/putcolui.c
new file mode 100644
index 0000000..e52b176
--- /dev/null
+++ b/src/plugins/cfitsio/putcolui.c
@@ -0,0 +1,969 @@
+/*  This file, putcolui.c, contains routines that write data elements to    */
+/*  a FITS image or table, with unsigned short datatype.                            */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+
+#include <limits.h>
+#include <string.h>
+#include <stdlib.h>
+#include "fitsio2.h"
+
+/*--------------------------------------------------------------------------*/
+int ffpprui(fitsfile *fptr,  /* I - FITS file pointer                       */
+            long  group,     /* I - group to write (1 = 1st group)          */
+            LONGLONG  firstelem, /* I - first vector element to write (1 = 1st) */
+            LONGLONG  nelem,     /* I - number of values to write               */
+   unsigned short *array,    /* I - array of values that are written        */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being written).
+*/
+{
+    long row;
+    unsigned short nullvalue;
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        fits_write_compressed_pixels(fptr, TUSHORT, firstelem, nelem,
+            0, array, &nullvalue, status);
+        return(*status);
+    }
+
+    row=maxvalue(1,group);
+
+    ffpclui(fptr, 2, row, firstelem, nelem, array, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffppnui(fitsfile *fptr,  /* I - FITS file pointer                       */
+            long  group,     /* I - group to write(1 = 1st group)           */
+            LONGLONG  firstelem, /* I - first vector element to write(1 = 1st)  */
+            LONGLONG  nelem,     /* I - number of values to write               */
+   unsigned short *array,    /* I - array of values that are written        */
+   unsigned short nulval,    /* I - undefined pixel value                   */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being written).  Any array values
+  that are equal to the value of nulval will be replaced with the null
+  pixel value that is appropriate for this column.
+*/
+{
+    long row;
+    unsigned short nullvalue;
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        nullvalue = nulval;  /* set local variable */
+        fits_write_compressed_pixels(fptr, TUSHORT, firstelem, nelem,
+            1, array, &nullvalue, status);
+        return(*status);
+    }
+
+    row=maxvalue(1,group);
+
+    ffpcnui(fptr, 2, row, firstelem, nelem, array, nulval, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffp2dui(fitsfile *fptr,   /* I - FITS file pointer                     */
+           long  group,      /* I - group to write(1 = 1st group)         */
+           LONGLONG  ncols,      /* I - number of pixels in each row of array */
+           LONGLONG  naxis1,     /* I - FITS image NAXIS1 value               */
+           LONGLONG  naxis2,     /* I - FITS image NAXIS2 value               */
+  unsigned short *array,     /* I - array to be written                   */
+           int  *status)     /* IO - error status                         */
+/*
+  Write an entire 2-D array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being written).
+*/
+{
+    /* call the 3D writing routine, with the 3rd dimension = 1 */
+
+    ffp3dui(fptr, group, ncols, naxis2, naxis1, naxis2, 1, array, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffp3dui(fitsfile *fptr,   /* I - FITS file pointer                     */
+           long  group,      /* I - group to write(1 = 1st group)         */
+           LONGLONG  ncols,      /* I - number of pixels in each row of array */
+           LONGLONG  nrows,      /* I - number of rows in each plane of array */
+           LONGLONG  naxis1,     /* I - FITS image NAXIS1 value               */
+           LONGLONG  naxis2,     /* I - FITS image NAXIS2 value               */
+           LONGLONG  naxis3,     /* I - FITS image NAXIS3 value               */
+  unsigned short *array,     /* I - array to be written                   */
+           int  *status)     /* IO - error status                         */
+/*
+  Write an entire 3-D cube of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being written).
+*/
+{
+    long tablerow, ii, jj;
+    long fpixel[3]= {1,1,1}, lpixel[3];
+    LONGLONG nfits, narray;
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+           
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+        lpixel[0] = (long) ncols;
+        lpixel[1] = (long) nrows;
+        lpixel[2] = (long) naxis3;
+       
+        fits_write_compressed_img(fptr, TUSHORT, fpixel, lpixel,
+            0,  array, NULL, status);
+    
+        return(*status);
+    }
+
+    tablerow=maxvalue(1,group);
+
+    if (ncols == naxis1 && nrows == naxis2)  /* arrays have same size? */
+    {
+      /* all the image pixels are contiguous, so write all at once */
+      ffpclui(fptr, 2, tablerow, 1L, naxis1 * naxis2 * naxis3, array, status);
+      return(*status);
+    }
+
+    if (ncols < naxis1 || nrows < naxis2)
+       return(*status = BAD_DIMEN);
+
+    nfits = 1;   /* next pixel in FITS image to write to */
+    narray = 0;  /* next pixel in input array to be written */
+
+    /* loop over naxis3 planes in the data cube */
+    for (jj = 0; jj < naxis3; jj++)
+    {
+      /* loop over the naxis2 rows in the FITS image, */
+      /* writing naxis1 pixels to each row            */
+
+      for (ii = 0; ii < naxis2; ii++)
+      {
+       if (ffpclui(fptr, 2, tablerow, nfits, naxis1,&array[narray],status) > 0)
+         return(*status);
+
+       nfits += naxis1;
+       narray += ncols;
+      }
+      narray += (nrows - naxis2) * ncols;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpssui(fitsfile *fptr,   /* I - FITS file pointer                       */
+           long  group,      /* I - group to write(1 = 1st group)           */
+           long  naxis,      /* I - number of data axes in array            */
+           long  *naxes,     /* I - size of each FITS axis                  */
+           long  *fpixel,    /* I - 1st pixel in each axis to write (1=1st) */
+           long  *lpixel,    /* I - last pixel in each axis to write        */
+  unsigned short *array,     /* I - array to be written                     */
+           int  *status)     /* IO - error status                           */
+/*
+  Write a subsection of pixels to the primary array or image.
+  A subsection is defined to be any contiguous rectangular
+  array of pixels within the n-dimensional FITS data file.
+  Data conversion and scaling will be performed if necessary 
+  (e.g, if the datatype of the FITS array is not the same as
+  the array being written).
+*/
+{
+    long tablerow;
+    LONGLONG fpix[7], dimen[7], astart, pstart;
+    LONGLONG off2, off3, off4, off5, off6, off7;
+    LONGLONG st10, st20, st30, st40, st50, st60, st70;
+    LONGLONG st1, st2, st3, st4, st5, st6, st7;
+    long ii, i1, i2, i3, i4, i5, i6, i7, irange[7];
+
+    if (*status > 0)
+        return(*status);
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        fits_write_compressed_img(fptr, TUSHORT, fpixel, lpixel,
+            0,  array, NULL, status);
+    
+        return(*status);
+    }
+
+    if (naxis < 1 || naxis > 7)
+      return(*status = BAD_DIMEN);
+
+    tablerow=maxvalue(1,group);
+
+     /* calculate the size and number of loops to perform in each dimension */
+    for (ii = 0; ii < 7; ii++)
+    {
+      fpix[ii]=1;
+      irange[ii]=1;
+      dimen[ii]=1;
+    }
+
+    for (ii = 0; ii < naxis; ii++)
+    {    
+      fpix[ii]=fpixel[ii];
+      irange[ii]=lpixel[ii]-fpixel[ii]+1;
+      dimen[ii]=naxes[ii];
+    }
+
+    i1=irange[0];
+
+    /* compute the pixel offset between each dimension */
+    off2 =     dimen[0];
+    off3 = off2 * dimen[1];
+    off4 = off3 * dimen[2];
+    off5 = off4 * dimen[3];
+    off6 = off5 * dimen[4];
+    off7 = off6 * dimen[5];
+
+    st10 = fpix[0];
+    st20 = (fpix[1] - 1) * off2;
+    st30 = (fpix[2] - 1) * off3;
+    st40 = (fpix[3] - 1) * off4;
+    st50 = (fpix[4] - 1) * off5;
+    st60 = (fpix[5] - 1) * off6;
+    st70 = (fpix[6] - 1) * off7;
+
+    /* store the initial offset in each dimension */
+    st1 = st10;
+    st2 = st20;
+    st3 = st30;
+    st4 = st40;
+    st5 = st50;
+    st6 = st60;
+    st7 = st70;
+
+    astart = 0;
+
+    for (i7 = 0; i7 < irange[6]; i7++)
+    {
+     for (i6 = 0; i6 < irange[5]; i6++)
+     {
+      for (i5 = 0; i5 < irange[4]; i5++)
+      {
+       for (i4 = 0; i4 < irange[3]; i4++)
+       {
+        for (i3 = 0; i3 < irange[2]; i3++)
+        {
+         pstart = st1 + st2 + st3 + st4 + st5 + st6 + st7;
+
+         for (i2 = 0; i2 < irange[1]; i2++)
+         {
+           if (ffpclui(fptr, 2, tablerow, pstart, i1, &array[astart],
+              status) > 0)
+              return(*status);
+
+           astart += i1;
+           pstart += off2;
+         }
+         st2 = st20;
+         st3 = st3+off3;    
+        }
+        st3 = st30;
+        st4 = st4+off4;
+       }
+       st4 = st40;
+       st5 = st5+off5;
+      }
+      st5 = st50;
+      st6 = st6+off6;
+     }
+     st6 = st60;
+     st7 = st7+off7;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpgpui( fitsfile *fptr,   /* I - FITS file pointer                      */
+            long  group,      /* I - group to write(1 = 1st group)          */
+            long  firstelem,  /* I - first vector element to write(1 = 1st) */
+            long  nelem,      /* I - number of values to write              */
+   unsigned short *array,     /* I - array of values that are written       */
+            int  *status)     /* IO - error status                          */
+/*
+  Write an array of group parameters to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being written).
+*/
+{
+    long row;
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffpclui(fptr, 1L, row, firstelem, nelem, array, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpclui( fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  colnum,     /* I - number of column to write (1 = 1st col) */
+            LONGLONG  firstrow,  /* I - first row to write (1 = 1st row)        */
+            LONGLONG  firstelem, /* I - first vector element to write (1 = 1st) */
+            LONGLONG  nelem,     /* I - number of values to write               */
+   unsigned short *array,    /* I - array of values to write                */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of values to a column in the current FITS HDU.
+  The column number may refer to a real column in an ASCII or binary table, 
+  or it may refer to a virtual column in a 1 or more grouped FITS primary
+  array.  FITSIO treats a primary array as a binary table with
+  2 vector columns: the first column contains the group parameters (often
+  with length = 0) and the second column contains the array of image pixels.
+  Each row of the table represents a group in the case of multigroup FITS
+  images.
+
+  The input array of values will be converted to the datatype of the column 
+  and will be inverse-scaled by the FITS TSCALn and TZEROn values if necessary.
+*/
+{
+    int tcode, maxelem, hdutype;
+    long twidth, incre;
+    long ntodo;
+    LONGLONG repeat, startpos, elemnum, wrtptr, rowlen, rownum, remain, next, tnull;
+    double scale, zero;
+    char tform[20], cform[20];
+    char message[FLEN_ERRMSG];
+
+    char snull[20];   /*  the FITS null value  */
+
+    double cbuff[DBUFFSIZE / sizeof(double)]; /* align cbuff on word boundary */
+    void *buffer;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    buffer = cbuff;
+
+    /*---------------------------------------------------*/
+    /*  Check input and get parameters about the column: */
+    /*---------------------------------------------------*/
+    if (ffgcprll( fptr, colnum, firstrow, firstelem, nelem, 1, &scale, &zero,
+        tform, &twidth, &tcode, &maxelem, &startpos,  &elemnum, &incre,
+        &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0)
+        return(*status);
+
+    if (tcode == TSTRING)   
+         ffcfmt(tform, cform);     /* derive C format for writing strings */
+
+    /*---------------------------------------------------------------------*/
+    /*  Now write the pixels to the FITS column.                           */
+    /*  First call the ffXXfYY routine to  (1) convert the datatype        */
+    /*  if necessary, and (2) scale the values by the FITS TSCALn and      */
+    /*  TZEROn linear scaling parameters into a temporary buffer.          */
+    /*---------------------------------------------------------------------*/
+    remain = nelem;           /* remaining number of values to write  */
+    next = 0;                 /* next element in array to be written  */
+    rownum = 0;               /* row number, relative to firstrow     */
+
+    while (remain)
+    {
+        /* limit the number of pixels to process a one time to the number that
+           will fit in the buffer space or to the number of pixels that remain
+           in the current vector, which ever is smaller.
+        */
+        ntodo = (long) minvalue(remain, maxelem);      
+        ntodo = (long) minvalue(ntodo, (repeat - elemnum));
+
+        wrtptr = startpos + ((LONGLONG)rownum * rowlen) + (elemnum * incre);
+
+        ffmbyt(fptr, wrtptr, IGNORE_EOF, status); /* move to write position */
+
+        switch (tcode) 
+        {
+            case (TSHORT):
+
+              ffu2fi2(&array[next], ntodo, scale, zero,
+                      (short *) buffer, status);
+              ffpi2b(fptr, ntodo, incre, (short *) buffer, status);
+              break;
+
+            case (TLONGLONG):
+
+                ffu2fi8(&array[next], ntodo, scale, zero,
+                        (LONGLONG *) buffer, status);
+                ffpi8b(fptr, ntodo, incre, (long *) buffer, status);
+                break;
+
+            case (TBYTE):
+ 
+                ffu2fi1(&array[next], ntodo, scale, zero,
+                        (unsigned char *) buffer, status);
+                ffpi1b(fptr, ntodo, incre, (unsigned char *) buffer, status);
+                break;
+
+            case (TLONG):
+
+                ffu2fi4(&array[next], ntodo, scale, zero,
+                        (INT32BIT *) buffer, status);
+                ffpi4b(fptr, ntodo, incre, (INT32BIT *) buffer, status);
+                break;
+
+            case (TFLOAT):
+
+                ffu2fr4(&array[next], ntodo, scale, zero,
+                        (float *) buffer, status);
+                ffpr4b(fptr, ntodo, incre, (float *) buffer, status);
+                break;
+
+            case (TDOUBLE):
+                ffu2fr8(&array[next], ntodo, scale, zero,
+                        (double *) buffer, status);
+                ffpr8b(fptr, ntodo, incre, (double *) buffer, status);
+                break;
+
+            case (TSTRING):  /* numerical column in an ASCII table */
+
+                if (cform[1] != 's')  /*  "%s" format is a string */
+                {
+                  ffu2fstr(&array[next], ntodo, scale, zero, cform,
+                          twidth, (char *) buffer, status);
+
+
+                  if (incre == twidth)    /* contiguous bytes */
+                     ffpbyt(fptr, ntodo * twidth, buffer, status);
+                  else
+                     ffpbytoff(fptr, twidth, ntodo, incre - twidth, buffer,
+                            status);
+
+                  break;
+                }
+                /* can't write to string column, so fall thru to default: */
+
+            default:  /*  error trap  */
+                sprintf(message, 
+                    "Cannot write numbers to column %d which has format %s",
+                      colnum,tform);
+                ffpmsg(message);
+                if (hdutype == ASCII_TBL)
+                    return(*status = BAD_ATABLE_FORMAT);
+                else
+                    return(*status = BAD_BTABLE_FORMAT);
+
+        } /* End of switch block */
+
+        /*-------------------------*/
+        /*  Check for fatal error  */
+        /*-------------------------*/
+        if (*status > 0)  /* test for error during previous write operation */
+        {
+         sprintf(message,
+          "Error writing elements %.0f thru %.0f of input data array (ffpclui).",
+             (double) (next+1), (double) (next+ntodo));
+         ffpmsg(message);
+         return(*status);
+        }
+
+        /*--------------------------------------------*/
+        /*  increment the counters for the next loop  */
+        /*--------------------------------------------*/
+        remain -= ntodo;
+        if (remain)
+        {
+            next += ntodo;
+            elemnum += ntodo;
+            if (elemnum == repeat)  /* completed a row; start on next row */
+            {
+                elemnum = 0;
+                rownum++;
+            }
+        }
+    }  /*  End of main while Loop  */
+
+
+    /*--------------------------------*/
+    /*  check for numerical overflow  */
+    /*--------------------------------*/
+    if (*status == OVERFLOW_ERR)
+    {
+       ffpmsg(
+       "Numerical overflow during type conversion while writing FITS data.");
+       *status = NUM_OVERFLOW;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpcnui(fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  colnum,     /* I - number of column to write (1 = 1st col) */
+            LONGLONG  firstrow,  /* I - first row to write (1 = 1st row)        */
+            LONGLONG  firstelem, /* I - first vector element to write (1 = 1st) */
+            LONGLONG  nelem,     /* I - number of values to write               */
+   unsigned short *array,    /* I - array of values to write                */
+   unsigned short  nulvalue, /* I - value used to flag undefined pixels     */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of elements to the specified column of a table.  Any input
+  pixels equal to the value of nulvalue will be replaced by the appropriate
+  null value in the output FITS file. 
+
+  The input array of values will be converted to the datatype of the column 
+  and will be inverse-scaled by the FITS TSCALn and TZEROn values if necessary
+*/
+{
+    tcolumn *colptr;
+    long  ngood = 0, nbad = 0, ii;
+    LONGLONG repeat, first, fstelm, fstrow;
+    int tcode, overflow = 0;
+
+    if (*status > 0)
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+    {
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    }
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+    {
+        if ( ffrdef(fptr, status) > 0)               /* rescan header */
+            return(*status);
+    }
+
+    colptr  = (fptr->Fptr)->tableptr;   /* point to first column */
+    colptr += (colnum - 1);     /* offset to correct column structure */
+
+    tcode  = colptr->tdatatype;
+
+    if (tcode > 0)
+       repeat = colptr->trepeat;  /* repeat count for this column */
+    else
+       repeat = firstelem -1 + nelem;  /* variable length arrays */
+
+    /* if variable length array, first write the whole input vector, 
+       then go back and fill in the nulls */
+    if (tcode < 0) {
+      if (ffpclui(fptr, colnum, firstrow, firstelem, nelem, array, status) > 0) {
+        if (*status == NUM_OVERFLOW) 
+	{
+	  /* ignore overflows, which are possibly the null pixel values */
+	  /*  overflow = 1;   */
+	  *status = 0;
+	} else { 
+          return(*status);
+	}
+      }
+    }
+
+    /* absolute element number in the column */
+    first = (firstrow - 1) * repeat + firstelem;
+
+    for (ii = 0; ii < nelem; ii++)
+    {
+      if (array[ii] != nulvalue)  /* is this a good pixel? */
+      {
+         if (nbad)  /* write previous string of bad pixels */
+         {
+            fstelm = ii - nbad + first;  /* absolute element number */
+            fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+            fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+            if (ffpclu(fptr, colnum, fstrow, fstelm, nbad, status) > 0)
+                return(*status);
+
+            nbad=0;
+         }
+
+         ngood = ngood +1;  /* the consecutive number of good pixels */
+      }
+      else
+      {
+         if (ngood)  /* write previous string of good pixels */
+         {
+            fstelm = ii - ngood + first;  /* absolute element number */
+            fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+            fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+            if (tcode > 0) {  /* variable length arrays have already been written */
+              if (ffpclui(fptr, colnum, fstrow, fstelm, ngood, &array[ii-ngood],
+                status) > 0) {
+		if (*status == NUM_OVERFLOW) 
+		{
+		  overflow = 1;
+		  *status = 0;
+		} else { 
+                  return(*status);
+		}
+	      }
+	    }
+            ngood=0;
+         }
+
+         nbad = nbad +1;  /* the consecutive number of bad pixels */
+      }
+    }
+
+    /* finished loop;  now just write the last set of pixels */
+
+    if (ngood)  /* write last string of good pixels */
+    {
+      fstelm = ii - ngood + first;  /* absolute element number */
+      fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+      fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+      if (tcode > 0) {  /* variable length arrays have already been written */
+        ffpclui(fptr, colnum, fstrow, fstelm, ngood, &array[ii-ngood], status);
+      }
+    }
+    else if (nbad) /* write last string of bad pixels */
+    {
+      fstelm = ii - nbad + first;  /* absolute element number */
+      fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+      fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+      ffpclu(fptr, colnum, fstrow, fstelm, nbad, status);
+    }
+
+    if (*status <= 0) {
+      if (overflow) {
+        *status = NUM_OVERFLOW;
+      }
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffu2fi1(unsigned short *input, /* I - array of values to be converted  */
+            long ntodo,            /* I - number of elements in the array  */
+            double scale,          /* I - FITS TSCALn or BSCALE value      */
+            double zero,           /* I - FITS TZEROn or BZERO  value      */
+            unsigned char *output, /* O - output array of converted values */
+            int *status)           /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            if (input[ii] > UCHAR_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = UCHAR_MAX;
+            }
+            else
+                output[ii] = (unsigned char) input[ii];
+        }
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = ((double) input[ii] - zero) / scale;
+
+            if (dvalue < DUCHAR_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = 0;
+            }
+            else if (dvalue > DUCHAR_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = UCHAR_MAX;
+            }
+            else
+                output[ii] = (unsigned char) (dvalue + .5);
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffu2fi2(unsigned short *input, /* I - array of values to be converted */
+            long ntodo,         /* I - number of elements in the array  */
+            double scale,       /* I - FITS TSCALn or BSCALE value      */
+            double zero,        /* I - FITS TZEROn or BZERO  value      */
+            short *output,      /* O - output array of converted values */
+            int *status)        /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 32768.)
+    {
+        /* Instead of subtracting 32768, it is more efficient */
+        /* to just flip the sign bit with the XOR operator */
+
+        for (ii = 0; ii < ntodo; ii++)
+             output[ii] =  ( *(short *) &input[ii] ) ^ 0x8000;
+    }
+    else if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            if (input[ii] > SHRT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = SHRT_MAX;
+            }
+            else
+                output[ii] = input[ii];
+        }
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = ((double) input[ii] - zero) / scale;
+
+            if (dvalue < DSHRT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = SHRT_MIN;
+            }
+            else if (dvalue > DSHRT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = SHRT_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0)
+                    output[ii] = (short) (dvalue + .5);
+                else
+                    output[ii] = (short) (dvalue - .5);
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffu2fi4(unsigned short *input, /* I - array of values to be converted */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            INT32BIT *output,      /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+            output[ii] = (INT32BIT) input[ii];   /* copy input to output */
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = ((double) input[ii] - zero) / scale;
+
+            if (dvalue < DINT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = INT32_MIN;
+            }
+            else if (dvalue > DINT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = INT32_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0)
+                    output[ii] = (INT32BIT) (dvalue + .5);
+                else
+                    output[ii] = (INT32BIT) (dvalue - .5);
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffu2fi8(unsigned short *input,  /* I - array of values to be converted  */
+            long ntodo,             /* I - number of elements in the array  */
+            double scale,           /* I - FITS TSCALn or BSCALE value      */
+            double zero,            /* I - FITS TZEROn or BZERO  value      */
+            LONGLONG *output,       /* O - output array of converted values */
+            int *status)            /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+                output[ii] = input[ii];
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = (input[ii] - zero) / scale;
+
+            if (dvalue < DLONGLONG_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = LONGLONG_MIN;
+            }
+            else if (dvalue > DLONGLONG_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = LONGLONG_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0)
+                    output[ii] = (LONGLONG) (dvalue + .5);
+                else
+                    output[ii] = (LONGLONG) (dvalue - .5);
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffu2fr4(unsigned short *input, /* I - array of values to be converted */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            float *output,     /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required.
+*/
+{
+    long ii;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (float) input[ii];
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+            output[ii] = (float) (((double) input[ii] - zero) / scale);
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffu2fr8(unsigned short *input, /* I - array of values to be converted */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            double *output,    /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required.
+*/
+{
+    long ii;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (double) input[ii];
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+            output[ii] = ((double) input[ii] - zero) / scale;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffu2fstr(unsigned short *input, /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            char *cform,       /* I - format for output string values  */
+            long twidth,       /* I - width of each field, in chars    */
+            char *output,      /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do scaling if required.
+*/
+{
+    long ii;
+    double dvalue;
+    char *cptr;
+    
+    cptr = output;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+        {
+           sprintf(output, cform, (double) input[ii]);
+           output += twidth;
+
+           if (*output)  /* if this char != \0, then overflow occurred */
+              *status = OVERFLOW_ERR;
+        }
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+          dvalue = ((double) input[ii] - zero) / scale;
+          sprintf(output, cform, dvalue);
+          output += twidth;
+
+          if (*output)  /* if this char != \0, then overflow occurred */
+            *status = OVERFLOW_ERR;
+        }
+    }
+
+    /* replace any commas with periods (e.g., in French locale) */
+    while ((cptr = strchr(cptr, ','))) *cptr = '.';
+    
+    return(*status);
+}
diff --git a/src/plugins/cfitsio/putcoluj.c b/src/plugins/cfitsio/putcoluj.c
new file mode 100644
index 0000000..9f89f10
--- /dev/null
+++ b/src/plugins/cfitsio/putcoluj.c
@@ -0,0 +1,977 @@
+/*  This file, putcoluj.c, contains routines that write data elements to   */
+/*  a FITS image or table, with unsigned long datatype.                             */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+
+#include <limits.h>
+#include <string.h>
+#include <stdlib.h>
+#include "fitsio2.h"
+
+/*--------------------------------------------------------------------------*/
+int ffppruj( fitsfile *fptr,  /* I - FITS file pointer                       */
+            long  group,     /* I - group to write(1 = 1st group)           */
+            LONGLONG  firstelem, /* I - first vector element to write(1 = 1st)  */
+            LONGLONG  nelem,     /* I - number of values to write               */
+   unsigned long  *array,    /* I - array of values that are written        */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being written).
+*/
+{
+    long row;
+    unsigned long nullvalue;
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        fits_write_compressed_pixels(fptr, TULONG, firstelem, nelem,
+            0, array, &nullvalue, status);
+        return(*status);
+    }
+
+    row=maxvalue(1,group);
+
+    ffpcluj(fptr, 2, row, firstelem, nelem, array, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffppnuj( fitsfile *fptr,  /* I - FITS file pointer                       */
+            long  group,     /* I - group to write(1 = 1st group)           */
+            LONGLONG  firstelem, /* I - first vector element to write(1 = 1st)  */
+            LONGLONG  nelem,     /* I - number of values to write               */
+   unsigned long  *array,    /* I - array of values that are written        */
+   unsigned long  nulval,    /* I - undefined pixel value                   */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being written).  Any array values
+  that are equal to the value of nulval will be replaced with the null
+  pixel value that is appropriate for this column.
+*/
+{
+    long row;
+    unsigned long nullvalue;
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        nullvalue = nulval;  /* set local variable */
+        fits_write_compressed_pixels(fptr, TULONG, firstelem, nelem,
+            1, array, &nullvalue, status);
+        return(*status);
+    }
+
+    row=maxvalue(1,group);
+
+    ffpcnuj(fptr, 2, row, firstelem, nelem, array, nulval, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffp2duj(fitsfile *fptr,   /* I - FITS file pointer                    */
+           long  group,      /* I - group to write(1 = 1st group)         */
+           LONGLONG  ncols,      /* I - number of pixels in each row of array */
+           LONGLONG  naxis1,     /* I - FITS image NAXIS1 value               */
+           LONGLONG  naxis2,     /* I - FITS image NAXIS2 value               */
+  unsigned long  *array,     /* I - array to be written                   */
+           int  *status)     /* IO - error status                         */
+/*
+  Write an entire 2-D array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being written).
+*/
+{
+    /* call the 3D writing routine, with the 3rd dimension = 1 */
+
+    ffp3duj(fptr, group, ncols, naxis2, naxis1, naxis2, 1, array, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffp3duj(fitsfile *fptr,   /* I - FITS file pointer                    */
+           long  group,      /* I - group to write(1 = 1st group)         */
+           LONGLONG  ncols,      /* I - number of pixels in each row of array */
+           LONGLONG  nrows,      /* I - number of rows in each plane of array */
+           LONGLONG  naxis1,     /* I - FITS image NAXIS1 value               */
+           LONGLONG  naxis2,     /* I - FITS image NAXIS2 value               */
+           LONGLONG  naxis3,     /* I - FITS image NAXIS3 value               */
+  unsigned long  *array,     /* I - array to be written                   */
+           int  *status)     /* IO - error status                         */
+/*
+  Write an entire 3-D cube of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being written).
+*/
+{
+    long tablerow, ii, jj;
+    long fpixel[3]= {1,1,1}, lpixel[3];
+    LONGLONG nfits, narray;
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+           
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+        lpixel[0] = (long) ncols;
+        lpixel[1] = (long) nrows;
+        lpixel[2] = (long) naxis3;
+       
+        fits_write_compressed_img(fptr, TULONG, fpixel, lpixel,
+            0,  array, NULL, status);
+    
+        return(*status);
+    }
+
+    tablerow=maxvalue(1,group);
+
+    if (ncols == naxis1 && nrows == naxis2)  /* arrays have same size? */
+    {
+      /* all the image pixels are contiguous, so write all at once */
+      ffpcluj(fptr, 2, tablerow, 1L, naxis1 * naxis2 * naxis3, array, status);
+      return(*status);
+    }
+
+    if (ncols < naxis1 || nrows < naxis2)
+       return(*status = BAD_DIMEN);
+
+    nfits = 1;   /* next pixel in FITS image to write to */
+    narray = 0;  /* next pixel in input array to be written */
+
+    /* loop over naxis3 planes in the data cube */
+    for (jj = 0; jj < naxis3; jj++)
+    {
+      /* loop over the naxis2 rows in the FITS image, */
+      /* writing naxis1 pixels to each row            */
+
+      for (ii = 0; ii < naxis2; ii++)
+      {
+       if (ffpcluj(fptr, 2, tablerow, nfits, naxis1,&array[narray],status) > 0)
+         return(*status);
+
+       nfits += naxis1;
+       narray += ncols;
+      }
+      narray += (nrows - naxis2) * ncols;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpssuj(fitsfile *fptr,   /* I - FITS file pointer                       */
+           long  group,      /* I - group to write(1 = 1st group)           */
+           long  naxis,      /* I - number of data axes in array            */
+           long  *naxes,     /* I - size of each FITS axis                  */
+           long  *fpixel,    /* I - 1st pixel in each axis to write (1=1st) */
+           long  *lpixel,    /* I - last pixel in each axis to write        */
+  unsigned long *array,      /* I - array to be written                     */
+           int  *status)     /* IO - error status                           */
+/*
+  Write a subsection of pixels to the primary array or image.
+  A subsection is defined to be any contiguous rectangular
+  array of pixels within the n-dimensional FITS data file.
+  Data conversion and scaling will be performed if necessary 
+  (e.g, if the datatype of the FITS array is not the same as
+  the array being written).
+*/
+{
+    long tablerow;
+    LONGLONG fpix[7], dimen[7], astart, pstart;
+    LONGLONG off2, off3, off4, off5, off6, off7;
+    LONGLONG st10, st20, st30, st40, st50, st60, st70;
+    LONGLONG st1, st2, st3, st4, st5, st6, st7;
+    long ii, i1, i2, i3, i4, i5, i6, i7, irange[7];
+
+    if (*status > 0)
+        return(*status);
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        fits_write_compressed_img(fptr, TULONG, fpixel, lpixel,
+            0,  array, NULL, status);
+    
+        return(*status);
+    }
+
+    if (naxis < 1 || naxis > 7)
+      return(*status = BAD_DIMEN);
+
+    tablerow=maxvalue(1,group);
+
+     /* calculate the size and number of loops to perform in each dimension */
+    for (ii = 0; ii < 7; ii++)
+    {
+      fpix[ii]=1;
+      irange[ii]=1;
+      dimen[ii]=1;
+    }
+
+    for (ii = 0; ii < naxis; ii++)
+    {    
+      fpix[ii]=fpixel[ii];
+      irange[ii]=lpixel[ii]-fpixel[ii]+1;
+      dimen[ii]=naxes[ii];
+    }
+
+    i1=irange[0];
+
+    /* compute the pixel offset between each dimension */
+    off2 =     dimen[0];
+    off3 = off2 * dimen[1];
+    off4 = off3 * dimen[2];
+    off5 = off4 * dimen[3];
+    off6 = off5 * dimen[4];
+    off7 = off6 * dimen[5];
+
+    st10 = fpix[0];
+    st20 = (fpix[1] - 1) * off2;
+    st30 = (fpix[2] - 1) * off3;
+    st40 = (fpix[3] - 1) * off4;
+    st50 = (fpix[4] - 1) * off5;
+    st60 = (fpix[5] - 1) * off6;
+    st70 = (fpix[6] - 1) * off7;
+
+    /* store the initial offset in each dimension */
+    st1 = st10;
+    st2 = st20;
+    st3 = st30;
+    st4 = st40;
+    st5 = st50;
+    st6 = st60;
+    st7 = st70;
+
+    astart = 0;
+
+    for (i7 = 0; i7 < irange[6]; i7++)
+    {
+     for (i6 = 0; i6 < irange[5]; i6++)
+     {
+      for (i5 = 0; i5 < irange[4]; i5++)
+      {
+       for (i4 = 0; i4 < irange[3]; i4++)
+       {
+        for (i3 = 0; i3 < irange[2]; i3++)
+        {
+         pstart = st1 + st2 + st3 + st4 + st5 + st6 + st7;
+
+         for (i2 = 0; i2 < irange[1]; i2++)
+         {
+           if (ffpcluj(fptr, 2, tablerow, pstart, i1, &array[astart],
+              status) > 0)
+              return(*status);
+
+           astart += i1;
+           pstart += off2;
+         }
+         st2 = st20;
+         st3 = st3+off3;    
+        }
+        st3 = st30;
+        st4 = st4+off4;
+       }
+       st4 = st40;
+       st5 = st5+off5;
+      }
+      st5 = st50;
+      st6 = st6+off6;
+     }
+     st6 = st60;
+     st7 = st7+off7;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpgpuj( fitsfile *fptr,   /* I - FITS file pointer                      */
+            long  group,      /* I - group to write(1 = 1st group)          */
+            long  firstelem,  /* I - first vector element to write(1 = 1st) */
+            long  nelem,      /* I - number of values to write              */
+   unsigned long  *array,     /* I - array of values that are written       */
+            int  *status)     /* IO - error status                          */
+/*
+  Write an array of group parameters to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being written).
+*/
+{
+    long row;
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffpcluj(fptr, 1L, row, firstelem, nelem, array, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpcluj( fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  colnum,     /* I - number of column to write (1 = 1st col) */
+            LONGLONG  firstrow,  /* I - first row to write (1 = 1st row)        */
+            LONGLONG  firstelem, /* I - first vector element to write (1 = 1st) */
+            LONGLONG  nelem,     /* I - number of values to write               */
+   unsigned long  *array,    /* I - array of values to write                */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of values to a column in the current FITS HDU.
+  The column number may refer to a real column in an ASCII or binary table, 
+  or it may refer to a virtual column in a 1 or more grouped FITS primary
+  array.  FITSIO treats a primary array as a binary table
+  with 2 vector columns: the first column contains the group parameters (often
+  with length = 0) and the second column contains the array of image pixels.
+  Each row of the table represents a group in the case of multigroup FITS
+  images.
+
+  The input array of values will be converted to the datatype of the column 
+  and will be inverse-scaled by the FITS TSCALn and TZEROn values if necessary.
+*/
+{
+    int tcode, maxelem, hdutype;
+    long twidth, incre;
+    long ntodo;
+    LONGLONG repeat, startpos, elemnum, wrtptr, rowlen, rownum, remain, next, tnull;
+    double scale, zero;
+    char tform[20], cform[20];
+    char message[FLEN_ERRMSG];
+
+    char snull[20];   /*  the FITS null value  */
+
+    double cbuff[DBUFFSIZE / sizeof(double)]; /* align cbuff on word boundary */
+    void *buffer;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    buffer = cbuff;
+
+    /*---------------------------------------------------*/
+    /*  Check input and get parameters about the column: */
+    /*---------------------------------------------------*/
+    if (ffgcprll( fptr, colnum, firstrow, firstelem, nelem, 1, &scale, &zero,
+        tform, &twidth, &tcode, &maxelem, &startpos,  &elemnum, &incre,
+        &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0)
+        return(*status);
+
+    if (tcode == TSTRING)   
+         ffcfmt(tform, cform);     /* derive C format for writing strings */
+
+    /*---------------------------------------------------------------------*/
+    /*  Now write the pixels to the FITS column.                           */
+    /*  First call the ffXXfYY routine to  (1) convert the datatype        */
+    /*  if necessary, and (2) scale the values by the FITS TSCALn and      */
+    /*  TZEROn linear scaling parameters into a temporary buffer.          */
+    /*---------------------------------------------------------------------*/
+    remain = nelem;           /* remaining number of values to write  */
+    next = 0;                 /* next element in array to be written  */
+    rownum = 0;               /* row number, relative to firstrow     */
+
+    while (remain)
+    {
+        /* limit the number of pixels to process a one time to the number that
+           will fit in the buffer space or to the number of pixels that remain
+           in the current vector, which ever is smaller.
+        */
+        ntodo = (long) minvalue(remain, maxelem);      
+        ntodo = (long) minvalue(ntodo, (repeat - elemnum));
+
+        wrtptr = startpos + ((LONGLONG)rownum * rowlen) + (elemnum * incre);
+
+        ffmbyt(fptr, wrtptr, IGNORE_EOF, status); /* move to write position */
+
+        switch (tcode) 
+        {
+            case (TLONG):
+
+                ffu4fi4(&array[next], ntodo, scale, zero,
+                      (INT32BIT *) buffer, status);
+                ffpi4b(fptr, ntodo, incre, (INT32BIT *) buffer, status);
+                break;
+
+            case (TLONGLONG):
+
+                ffu4fi8(&array[next], ntodo, scale, zero,
+                        (LONGLONG *) buffer, status);
+                ffpi8b(fptr, ntodo, incre, (long *) buffer, status);
+                break;
+
+            case (TBYTE):
+ 
+                ffu4fi1(&array[next], ntodo, scale, zero,
+                        (unsigned char *) buffer, status);
+                ffpi1b(fptr, ntodo, incre, (unsigned char *) buffer, status);
+                break;
+
+            case (TSHORT):
+
+                ffu4fi2(&array[next], ntodo, scale, zero,
+                        (short *) buffer, status);
+                ffpi2b(fptr, ntodo, incre, (short *) buffer, status);
+                break;
+
+            case (TFLOAT):
+
+                ffu4fr4(&array[next], ntodo, scale, zero,
+                        (float *) buffer, status);
+                ffpr4b(fptr, ntodo, incre, (float *) buffer, status);
+                break;
+
+            case (TDOUBLE):
+                ffu4fr8(&array[next], ntodo, scale, zero,
+                       (double *) buffer, status);
+                ffpr8b(fptr, ntodo, incre, (double *) buffer, status);
+                break;
+
+            case (TSTRING):  /* numerical column in an ASCII table */
+
+                if (cform[1] != 's')  /*  "%s" format is a string */
+                {
+                  ffu4fstr(&array[next], ntodo, scale, zero, cform,
+                          twidth, (char *) buffer, status);
+
+                  if (incre == twidth)    /* contiguous bytes */
+                     ffpbyt(fptr, ntodo * twidth, buffer, status);
+                  else
+                     ffpbytoff(fptr, twidth, ntodo, incre - twidth, buffer,
+                            status);
+
+                  break;
+                }
+                /* can't write to string column, so fall thru to default: */
+
+            default:  /*  error trap  */
+                sprintf(message, 
+                     "Cannot write numbers to column %d which has format %s",
+                      colnum,tform);
+                ffpmsg(message);
+                if (hdutype == ASCII_TBL)
+                    return(*status = BAD_ATABLE_FORMAT);
+                else
+                    return(*status = BAD_BTABLE_FORMAT);
+
+        } /* End of switch block */
+
+        /*-------------------------*/
+        /*  Check for fatal error  */
+        /*-------------------------*/
+        if (*status > 0)  /* test for error during previous write operation */
+        {
+          sprintf(message,
+          "Error writing elements %.0f thru %.0f of input data array (ffpcluj).",
+              (double) (next+1), (double) (next+ntodo));
+          ffpmsg(message);
+          return(*status);
+        }
+
+        /*--------------------------------------------*/
+        /*  increment the counters for the next loop  */
+        /*--------------------------------------------*/
+        remain -= ntodo;
+        if (remain)
+        {
+            next += ntodo;
+            elemnum += ntodo;
+            if (elemnum == repeat)  /* completed a row; start on next row */
+            {
+                elemnum = 0;
+                rownum++;
+            }
+        }
+    }  /*  End of main while Loop  */
+
+
+    /*--------------------------------*/
+    /*  check for numerical overflow  */
+    /*--------------------------------*/
+    if (*status == OVERFLOW_ERR)
+    {
+        ffpmsg(
+        "Numerical overflow during type conversion while writing FITS data.");
+        *status = NUM_OVERFLOW;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpcnuj( fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  colnum,     /* I - number of column to write (1 = 1st col) */
+            LONGLONG  firstrow,  /* I - first row to write (1 = 1st row)        */
+            LONGLONG  firstelem, /* I - first vector element to write (1 = 1st) */
+            LONGLONG  nelem,     /* I - number of values to write               */
+   unsigned long  *array,    /* I - array of values to write                */
+   unsigned long   nulvalue, /* I - value used to flag undefined pixels     */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of elements to the specified column of a table.  Any input
+  pixels equal to the value of nulvalue will be replaced by the appropriate
+  null value in the output FITS file. 
+
+  The input array of values will be converted to the datatype of the column 
+  and will be inverse-scaled by the FITS TSCALn and TZEROn values if necessary
+*/
+{
+    tcolumn *colptr;
+    long  ngood = 0, nbad = 0, ii;
+    LONGLONG repeat, first, fstelm, fstrow;
+    int tcode, overflow = 0;
+
+    if (*status > 0)
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+    {
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    }
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+    {
+        if ( ffrdef(fptr, status) > 0)               /* rescan header */
+            return(*status);
+    }
+
+    colptr  = (fptr->Fptr)->tableptr;   /* point to first column */
+    colptr += (colnum - 1);     /* offset to correct column structure */
+
+    tcode  = colptr->tdatatype;
+
+    if (tcode > 0)
+       repeat = colptr->trepeat;  /* repeat count for this column */
+    else
+       repeat = firstelem -1 + nelem;  /* variable length arrays */
+
+    /* if variable length array, first write the whole input vector, 
+       then go back and fill in the nulls */
+    if (tcode < 0) {
+      if (ffpcluj(fptr, colnum, firstrow, firstelem, nelem, array, status) > 0) {
+        if (*status == NUM_OVERFLOW) 
+	{
+	  /* ignore overflows, which are possibly the null pixel values */
+	  /*  overflow = 1;   */
+	  *status = 0;
+	} else { 
+          return(*status);
+	}
+      }
+    }
+
+    /* absolute element number in the column */
+    first = (firstrow - 1) * repeat + firstelem;
+
+    for (ii = 0; ii < nelem; ii++)
+    {
+      if (array[ii] != nulvalue)  /* is this a good pixel? */
+      {
+         if (nbad)  /* write previous string of bad pixels */
+         {
+            fstelm = ii - nbad + first;  /* absolute element number */
+            fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+            fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+            if (ffpclu(fptr, colnum, fstrow, fstelm, nbad, status) > 0)
+                return(*status);
+
+            nbad=0;
+         }
+
+         ngood = ngood +1;  /* the consecutive number of good pixels */
+      }
+      else
+      {
+         if (ngood)  /* write previous string of good pixels */
+         {
+            fstelm = ii - ngood + first;  /* absolute element number */
+            fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+            fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+            if (tcode > 0) {  /* variable length arrays have already been written */
+              if (ffpcluj(fptr, colnum, fstrow, fstelm, ngood, &array[ii-ngood],
+                status) > 0) {
+		if (*status == NUM_OVERFLOW) 
+		{
+		  overflow = 1;
+		  *status = 0;
+		} else { 
+                  return(*status);
+		}
+	      }
+	    }
+            ngood=0;
+         }
+
+         nbad = nbad +1;  /* the consecutive number of bad pixels */
+      }
+    }
+
+    /* finished loop;  now just write the last set of pixels */
+
+    if (ngood)  /* write last string of good pixels */
+    {
+      fstelm = ii - ngood + first;  /* absolute element number */
+      fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+      fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+      if (tcode > 0) {  /* variable length arrays have already been written */
+        ffpcluj(fptr, colnum, fstrow, fstelm, ngood, &array[ii-ngood], status);
+      }
+    }
+    else if (nbad) /* write last string of bad pixels */
+    {
+      fstelm = ii - nbad + first;  /* absolute element number */
+      fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+      fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+      ffpclu(fptr, colnum, fstrow, fstelm, nbad, status);
+    }
+
+    if (*status <= 0) {
+      if (overflow) {
+        *status = NUM_OVERFLOW;
+      }
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffu4fi1(unsigned long *input,  /* I - array of values to be converted  */
+            long ntodo,            /* I - number of elements in the array  */
+            double scale,          /* I - FITS TSCALn or BSCALE value      */
+            double zero,           /* I - FITS TZEROn or BZERO  value      */
+            unsigned char *output, /* O - output array of converted values */
+            int *status)           /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required.
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            if (input[ii] > UCHAR_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = UCHAR_MAX;
+            }
+            else
+                output[ii] = (unsigned char) input[ii];
+        }
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = (input[ii] - zero) / scale;
+
+            if (dvalue < DUCHAR_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = 0;
+            }
+            else if (dvalue > DUCHAR_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = UCHAR_MAX;
+            }
+            else
+                output[ii] = (unsigned char) (dvalue + .5);
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffu4fi2(unsigned long *input, /* I - array of values to be converted */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            short *output,     /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required.
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            if (input[ii] > SHRT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = SHRT_MAX;
+            }
+            else
+                output[ii] = (short) input[ii];
+        }
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = (input[ii] - zero) / scale;
+
+            if (dvalue < DSHRT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = SHRT_MIN;
+            }
+            else if (dvalue > DSHRT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = SHRT_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0)
+                    output[ii] = (short) (dvalue + .5);
+                else
+                    output[ii] = (short) (dvalue - .5);
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffu4fi4(unsigned long *input, /* I - array of values to be converted */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            INT32BIT *output,  /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 2147483648. && sizeof(long) == 4)
+    {       
+        /* Instead of subtracting 2147483648, it is more efficient */
+        /* to just flip the sign bit with the XOR operator */
+
+        for (ii = 0; ii < ntodo; ii++)
+             output[ii] =  ( *(long *) &input[ii] ) ^ 0x80000000;
+    }
+    else if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            if (input[ii] > INT32_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = INT32_MAX;
+            }
+            else
+                output[ii] = input[ii];
+        }
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = (input[ii] - zero) / scale;
+
+            if (dvalue < DINT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = INT32_MIN;
+            }
+            else if (dvalue > DINT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = INT32_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0)
+                    output[ii] = (INT32BIT) (dvalue + .5);
+                else
+                    output[ii] = (INT32BIT) (dvalue - .5);
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffu4fi8(unsigned long *input,  /* I - array of values to be converted  */
+            long ntodo,             /* I - number of elements in the array  */
+            double scale,           /* I - FITS TSCALn or BSCALE value      */
+            double zero,            /* I - FITS TZEROn or BZERO  value      */
+            LONGLONG *output,       /* O - output array of converted values */
+            int *status)            /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+                output[ii] = input[ii];
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = (input[ii] - zero) / scale;
+
+            if (dvalue < DLONGLONG_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = LONGLONG_MIN;
+            }
+            else if (dvalue > DLONGLONG_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = LONGLONG_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0)
+                    output[ii] = (LONGLONG) (dvalue + .5);
+                else
+                    output[ii] = (LONGLONG) (dvalue - .5);
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffu4fr4(unsigned long *input, /* I - array of values to be converted */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            float *output,     /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required.
+*/
+{
+    long ii;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (float) input[ii];
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+            output[ii] = (float) ((input[ii] - zero) / scale);
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffu4fr8(unsigned long *input, /* I - array of values to be converted */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            double *output,    /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required.
+*/
+{
+    long ii;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (double) input[ii];
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+            output[ii] = (input[ii] - zero) / scale;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffu4fstr(unsigned long *input, /* I - array of values to be converted */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            char *cform,       /* I - format for output string values  */
+            long twidth,       /* I - width of each field, in chars    */
+            char *output,      /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do scaling if required.
+*/
+{
+    long ii;
+    double dvalue;
+    char *cptr;
+    
+    cptr = output;
+
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+        {
+           sprintf(output, cform, (double) input[ii]);
+           output += twidth;
+
+           if (*output)  /* if this char != \0, then overflow occurred */
+              *status = OVERFLOW_ERR;
+        }
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+          dvalue = (input[ii] - zero) / scale;
+          sprintf(output, cform, dvalue);
+          output += twidth;
+
+          if (*output)  /* if this char != \0, then overflow occurred */
+            *status = OVERFLOW_ERR;
+        }
+    }
+
+    /* replace any commas with periods (e.g., in French locale) */
+    while ((cptr = strchr(cptr, ','))) *cptr = '.';
+    
+    return(*status);
+}
diff --git a/src/plugins/cfitsio/putcoluk.c b/src/plugins/cfitsio/putcoluk.c
new file mode 100644
index 0000000..dd92fe3
--- /dev/null
+++ b/src/plugins/cfitsio/putcoluk.c
@@ -0,0 +1,993 @@
+/*  This file, putcolk.c, contains routines that write data elements to    */
+/*  a FITS image or table, with 'unsigned int' datatype.                   */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+
+#include <limits.h>
+#include <string.h>
+#include <stdlib.h>
+#include "fitsio2.h"
+
+/*--------------------------------------------------------------------------*/
+int ffppruk(fitsfile *fptr,  /* I - FITS file pointer                       */
+            long  group,     /* I - group to write(1 = 1st group)           */
+            LONGLONG  firstelem, /* I - first vector element to write(1 = 1st)  */
+            LONGLONG  nelem,     /* I - number of values to write               */
+   unsigned int   *array,    /* I - array of values that are written        */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being written).
+*/
+{
+    long row;
+    unsigned int nullvalue;
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        fits_write_compressed_pixels(fptr, TUINT, firstelem, nelem,
+            0, array, &nullvalue, status);
+        return(*status);
+    }
+
+    row=maxvalue(1,group);
+
+    ffpcluk(fptr, 2, row, firstelem, nelem, array, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffppnuk(fitsfile *fptr,  /* I - FITS file pointer                       */
+            long  group,     /* I - group to write(1 = 1st group)           */
+            LONGLONG  firstelem, /* I - first vector element to write(1 = 1st)  */
+            LONGLONG  nelem,     /* I - number of values to write               */
+   unsigned int   *array,    /* I - array of values that are written        */
+   unsigned int   nulval,    /* I - undefined pixel value                   */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being written).  Any array values
+  that are equal to the value of nulval will be replaced with the null
+  pixel value that is appropriate for this column.
+*/
+{
+    long row;
+    unsigned int nullvalue;
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        nullvalue = nulval;  /* set local variable */
+        fits_write_compressed_pixels(fptr, TUINT, firstelem, nelem,
+            1, array, &nullvalue, status);
+        return(*status);
+    }
+
+    row=maxvalue(1,group);
+
+    ffpcnuk(fptr, 2, row, firstelem, nelem, array, nulval, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffp2duk(fitsfile *fptr,  /* I - FITS file pointer                     */
+           long  group,      /* I - group to write(1 = 1st group)         */
+           LONGLONG  ncols,      /* I - number of pixels in each row of array */
+           LONGLONG  naxis1,     /* I - FITS image NAXIS1 value               */
+           LONGLONG  naxis2,     /* I - FITS image NAXIS2 value               */
+  unsigned int   *array,     /* I - array to be written                   */
+           int  *status)     /* IO - error status                         */
+/*
+  Write an entire 2-D array of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being written).
+*/
+{
+    /* call the 3D writing routine, with the 3rd dimension = 1 */
+
+    ffp3duk(fptr, group, ncols, naxis2, naxis1, naxis2, 1, array, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffp3duk(fitsfile *fptr,  /* I - FITS file pointer                     */
+           long  group,      /* I - group to write(1 = 1st group)         */
+           LONGLONG  ncols,      /* I - number of pixels in each row of array */
+           LONGLONG  nrows,      /* I - number of rows in each plane of array */
+           LONGLONG  naxis1,     /* I - FITS image NAXIS1 value               */
+           LONGLONG  naxis2,     /* I - FITS image NAXIS2 value               */
+           LONGLONG  naxis3,     /* I - FITS image NAXIS3 value               */
+  unsigned int   *array,     /* I - array to be written                   */
+           int  *status)     /* IO - error status                         */
+/*
+  Write an entire 3-D cube of values to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of the
+  FITS array is not the same as the array being written).
+*/
+{
+    long tablerow, ii, jj;
+    long fpixel[3]= {1,1,1}, lpixel[3];
+    LONGLONG nfits, narray;
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+           
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+        lpixel[0] = (long) ncols;
+        lpixel[1] = (long) nrows;
+        lpixel[2] = (long) naxis3;
+       
+        fits_write_compressed_img(fptr, TUINT, fpixel, lpixel,
+            0,  array, NULL, status);
+    
+        return(*status);
+    }
+
+    tablerow=maxvalue(1,group);
+
+    if (ncols == naxis1 && nrows == naxis2)  /* arrays have same size? */
+    {
+      /* all the image pixels are contiguous, so write all at once */
+      ffpcluk(fptr, 2, tablerow, 1L, naxis1 * naxis2 * naxis3, array, status);
+      return(*status);
+    }
+
+    if (ncols < naxis1 || nrows < naxis2)
+       return(*status = BAD_DIMEN);
+
+    nfits = 1;   /* next pixel in FITS image to write to */
+    narray = 0;  /* next pixel in input array to be written */
+
+    /* loop over naxis3 planes in the data cube */
+    for (jj = 0; jj < naxis3; jj++)
+    {
+      /* loop over the naxis2 rows in the FITS image, */
+      /* writing naxis1 pixels to each row            */
+
+      for (ii = 0; ii < naxis2; ii++)
+      {
+       if (ffpcluk(fptr, 2, tablerow, nfits, naxis1,&array[narray],status) > 0)
+         return(*status);
+
+       nfits += naxis1;
+       narray += ncols;
+      }
+      narray += (nrows - naxis2) * ncols;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpssuk(fitsfile *fptr,  /* I - FITS file pointer                       */
+           long  group,      /* I - group to write(1 = 1st group)           */
+           long  naxis,      /* I - number of data axes in array            */
+           long  *naxes,     /* I - size of each FITS axis                  */
+           long  *fpixel,    /* I - 1st pixel in each axis to write (1=1st) */
+           long  *lpixel,    /* I - last pixel in each axis to write        */
+  unsigned int  *array,      /* I - array to be written                     */
+           int  *status)     /* IO - error status                           */
+/*
+  Write a subsection of pixels to the primary array or image.
+  A subsection is defined to be any contiguous rectangular
+  array of pixels within the n-dimensional FITS data file.
+  Data conversion and scaling will be performed if necessary 
+  (e.g, if the datatype of the FITS array is not the same as
+  the array being written).
+*/
+{
+    long tablerow;
+    LONGLONG fpix[7], dimen[7], astart, pstart;
+    LONGLONG off2, off3, off4, off5, off6, off7;
+    LONGLONG st10, st20, st30, st40, st50, st60, st70;
+    LONGLONG st1, st2, st3, st4, st5, st6, st7;
+    long ii, i1, i2, i3, i4, i5, i6, i7, irange[7];
+
+    if (*status > 0)
+        return(*status);
+
+    if (fits_is_compressed_image(fptr, status))
+    {
+        /* this is a compressed image in a binary table */
+
+        fits_write_compressed_img(fptr, TUINT, fpixel, lpixel,
+            0,  array, NULL, status);
+    
+        return(*status);
+    }
+
+    if (naxis < 1 || naxis > 7)
+      return(*status = BAD_DIMEN);
+
+    tablerow=maxvalue(1,group);
+
+     /* calculate the size and number of loops to perform in each dimension */
+    for (ii = 0; ii < 7; ii++)
+    {
+      fpix[ii]=1;
+      irange[ii]=1;
+      dimen[ii]=1;
+    }
+
+    for (ii = 0; ii < naxis; ii++)
+    {    
+      fpix[ii]=fpixel[ii];
+      irange[ii]=lpixel[ii]-fpixel[ii]+1;
+      dimen[ii]=naxes[ii];
+    }
+
+    i1=irange[0];
+
+    /* compute the pixel offset between each dimension */
+    off2 =     dimen[0];
+    off3 = off2 * dimen[1];
+    off4 = off3 * dimen[2];
+    off5 = off4 * dimen[3];
+    off6 = off5 * dimen[4];
+    off7 = off6 * dimen[5];
+
+    st10 = fpix[0];
+    st20 = (fpix[1] - 1) * off2;
+    st30 = (fpix[2] - 1) * off3;
+    st40 = (fpix[3] - 1) * off4;
+    st50 = (fpix[4] - 1) * off5;
+    st60 = (fpix[5] - 1) * off6;
+    st70 = (fpix[6] - 1) * off7;
+
+    /* store the initial offset in each dimension */
+    st1 = st10;
+    st2 = st20;
+    st3 = st30;
+    st4 = st40;
+    st5 = st50;
+    st6 = st60;
+    st7 = st70;
+
+    astart = 0;
+
+    for (i7 = 0; i7 < irange[6]; i7++)
+    {
+     for (i6 = 0; i6 < irange[5]; i6++)
+     {
+      for (i5 = 0; i5 < irange[4]; i5++)
+      {
+       for (i4 = 0; i4 < irange[3]; i4++)
+       {
+        for (i3 = 0; i3 < irange[2]; i3++)
+        {
+         pstart = st1 + st2 + st3 + st4 + st5 + st6 + st7;
+
+         for (i2 = 0; i2 < irange[1]; i2++)
+         {
+           if (ffpcluk(fptr, 2, tablerow, pstart, i1, &array[astart],
+              status) > 0)
+              return(*status);
+
+           astart += i1;
+           pstart += off2;
+         }
+         st2 = st20;
+         st3 = st3+off3;    
+        }
+        st3 = st30;
+        st4 = st4+off4;
+       }
+       st4 = st40;
+       st5 = st5+off5;
+      }
+      st5 = st50;
+      st6 = st6+off6;
+     }
+     st6 = st60;
+     st7 = st7+off7;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpgpuk(fitsfile *fptr,   /* I - FITS file pointer                      */
+            long  group,      /* I - group to write(1 = 1st group)          */
+            long  firstelem,  /* I - first vector element to write(1 = 1st) */
+            long  nelem,      /* I - number of values to write              */
+   unsigned int   *array,     /* I - array of values that are written       */
+            int  *status)     /* IO - error status                          */
+/*
+  Write an array of group parameters to the primary array. Data conversion
+  and scaling will be performed if necessary (e.g, if the datatype of
+  the FITS array is not the same as the array being written).
+*/
+{
+    long row;
+
+    /*
+      the primary array is represented as a binary table:
+      each group of the primary array is a row in the table,
+      where the first column contains the group parameters
+      and the second column contains the image itself.
+    */
+
+    row=maxvalue(1,group);
+
+    ffpcluk(fptr, 1L, row, firstelem, nelem, array, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpcluk(fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  colnum,     /* I - number of column to write (1 = 1st col) */
+            LONGLONG  firstrow,  /* I - first row to write (1 = 1st row)        */
+            LONGLONG  firstelem, /* I - first vector element to write (1 = 1st) */
+            LONGLONG  nelem,     /* I - number of values to write               */
+   unsigned int   *array,    /* I - array of values to write                */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of values to a column in the current FITS HDU.
+  The column number may refer to a real column in an ASCII or binary table, 
+  or it may refer to a virtual column in a 1 or more grouped FITS primary
+  array.  FITSIO treats a primary array as a binary table
+  with 2 vector columns: the first column contains the group parameters (often
+  with length = 0) and the second column contains the array of image pixels.
+  Each row of the table represents a group in the case of multigroup FITS
+  images.
+
+  The input array of values will be converted to the datatype of the column 
+  and will be inverse-scaled by the FITS TSCALn and TZEROn values if necessary.
+*/
+{
+    int tcode, maxelem, hdutype;
+    long twidth, incre;
+    long ntodo;
+    LONGLONG repeat, startpos, elemnum, wrtptr, rowlen, rownum, remain, next, tnull;
+    double scale, zero;
+    char tform[20], cform[20];
+    char message[FLEN_ERRMSG];
+
+    char snull[20];   /*  the FITS null value  */
+
+    double cbuff[DBUFFSIZE / sizeof(double)]; /* align cbuff on word boundary */
+    void *buffer;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    /* call the 'short' or 'long' version of this routine, if possible */
+    if (sizeof(int) == sizeof(short))
+        ffpclui(fptr, colnum, firstrow, firstelem, nelem, 
+              (unsigned short *) array, status);
+    else if (sizeof(int) == sizeof(long))
+        ffpcluj(fptr, colnum, firstrow, firstelem, nelem, 
+              (unsigned long *) array, status);
+    else
+    {
+    /*
+      This is a special case: sizeof(int) is not equal to sizeof(short) or
+      sizeof(long).  This occurs on Alpha OSF systems where short = 2 bytes,
+      int = 4 bytes, and long = 8 bytes.
+    */
+
+    buffer = cbuff;
+
+    /*---------------------------------------------------*/
+    /*  Check input and get parameters about the column: */
+    /*---------------------------------------------------*/
+    if (ffgcprll( fptr, colnum, firstrow, firstelem, nelem, 1, &scale, &zero,
+        tform, &twidth, &tcode, &maxelem, &startpos,  &elemnum, &incre,
+        &repeat, &rowlen, &hdutype, &tnull, snull, status) > 0)
+        return(*status);
+
+    if (tcode == TSTRING)   
+         ffcfmt(tform, cform);     /* derive C format for writing strings */
+
+    /*---------------------------------------------------------------------*/
+    /*  Now write the pixels to the FITS column.                           */
+    /*  First call the ffXXfYY routine to  (1) convert the datatype        */
+    /*  if necessary, and (2) scale the values by the FITS TSCALn and      */
+    /*  TZEROn linear scaling parameters into a temporary buffer.          */
+    /*---------------------------------------------------------------------*/
+    remain = nelem;           /* remaining number of values to write  */
+    next = 0;                 /* next element in array to be written  */
+    rownum = 0;               /* row number, relative to firstrow     */
+
+    while (remain)
+    {
+        /* limit the number of pixels to process a one time to the number that
+           will fit in the buffer space or to the number of pixels that remain
+           in the current vector, which ever is smaller.
+        */
+        ntodo = (long) minvalue(remain, maxelem);      
+        ntodo = (long) minvalue(ntodo, (repeat - elemnum));
+
+        wrtptr = startpos + ((LONGLONG)rownum * rowlen) + (elemnum * incre);
+
+        ffmbyt(fptr, wrtptr, IGNORE_EOF, status); /* move to write position */
+
+        switch (tcode) 
+        {
+            case (TLONG):
+                /* convert the raw data before writing to FITS file */
+                ffuintfi4(&array[next], ntodo, scale, zero,
+                        (INT32BIT *) buffer, status);
+                ffpi4b(fptr, ntodo, incre, (INT32BIT *) buffer, status);
+                break;
+
+            case (TLONGLONG):
+
+                ffuintfi8(&array[next], ntodo, scale, zero,
+                        (LONGLONG *) buffer, status);
+                ffpi8b(fptr, ntodo, incre, (long *) buffer, status);
+                break;
+
+            case (TBYTE):
+ 
+                ffuintfi1(&array[next], ntodo, scale, zero,
+                        (unsigned char *) buffer, status);
+                ffpi1b(fptr, ntodo, incre, (unsigned char *) buffer, status);
+                break;
+
+            case (TSHORT):
+
+                ffuintfi2(&array[next], ntodo, scale, zero,
+                        (short *) buffer, status);
+                ffpi2b(fptr, ntodo, incre, (short *) buffer, status);
+                break;
+
+            case (TFLOAT):
+
+                ffuintfr4(&array[next], ntodo, scale, zero,
+                        (float *) buffer, status);
+                ffpr4b(fptr, ntodo, incre, (float *) buffer, status);
+                break;
+
+            case (TDOUBLE):
+                ffuintfr8(&array[next], ntodo, scale, zero,
+                       (double *) buffer, status);
+                ffpr8b(fptr, ntodo, incre, (double *) buffer, status);
+                break;
+
+            case (TSTRING):  /* numerical column in an ASCII table */
+
+                if (cform[1] != 's')  /*  "%s" format is a string */
+                {
+                  ffuintfstr(&array[next], ntodo, scale, zero, cform,
+                          twidth, (char *) buffer, status);
+
+                  if (incre == twidth)    /* contiguous bytes */
+                     ffpbyt(fptr, ntodo * twidth, buffer, status);
+                  else
+                     ffpbytoff(fptr, twidth, ntodo, incre - twidth, buffer,
+                            status);
+
+                  break;
+                }
+                /* can't write to string column, so fall thru to default: */
+
+            default:  /*  error trap  */
+                sprintf(message, 
+                     "Cannot write numbers to column %d which has format %s",
+                      colnum,tform);
+                ffpmsg(message);
+                if (hdutype == ASCII_TBL)
+                    return(*status = BAD_ATABLE_FORMAT);
+                else
+                    return(*status = BAD_BTABLE_FORMAT);
+
+        } /* End of switch block */
+
+        /*-------------------------*/
+        /*  Check for fatal error  */
+        /*-------------------------*/
+        if (*status > 0)  /* test for error during previous write operation */
+        {
+          sprintf(message,
+          "Error writing elements %.0f thru %.0f of input data array (ffpcluk).",
+              (double) (next+1), (double) (next+ntodo));
+          ffpmsg(message);
+          return(*status);
+        }
+
+        /*--------------------------------------------*/
+        /*  increment the counters for the next loop  */
+        /*--------------------------------------------*/
+        remain -= ntodo;
+        if (remain)
+        {
+            next += ntodo;
+            elemnum += ntodo;
+            if (elemnum == repeat)  /* completed a row; start on next row */
+            {
+                elemnum = 0;
+                rownum++;
+            }
+        }
+    }  /*  End of main while Loop  */
+
+
+    /*--------------------------------*/
+    /*  check for numerical overflow  */
+    /*--------------------------------*/
+    if (*status == OVERFLOW_ERR)
+    {
+        ffpmsg(
+        "Numerical overflow during type conversion while writing FITS data.");
+        *status = NUM_OVERFLOW;
+    }
+
+    }   /* end of Dec ALPHA special case */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpcnuk(fitsfile *fptr,  /* I - FITS file pointer                       */
+            int  colnum,     /* I - number of column to write (1 = 1st col) */
+            LONGLONG  firstrow,  /* I - first row to write (1 = 1st row)        */
+            LONGLONG  firstelem, /* I - first vector element to write (1 = 1st) */
+            LONGLONG  nelem,     /* I - number of values to write               */
+   unsigned int   *array,    /* I - array of values to write                */
+   unsigned int    nulvalue, /* I - value used to flag undefined pixels     */
+            int  *status)    /* IO - error status                           */
+/*
+  Write an array of elements to the specified column of a table.  Any input
+  pixels equal to the value of nulvalue will be replaced by the appropriate
+  null value in the output FITS file. 
+
+  The input array of values will be converted to the datatype of the column 
+  and will be inverse-scaled by the FITS TSCALn and TZEROn values if necessary
+*/
+{
+    tcolumn *colptr;
+    long  ngood = 0, nbad = 0, ii;
+    LONGLONG repeat, first, fstelm, fstrow;
+    int tcode, overflow = 0;
+
+    if (*status > 0)
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+    {
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    }
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+    {
+        if ( ffrdef(fptr, status) > 0)               /* rescan header */
+            return(*status);
+    }
+
+    colptr  = (fptr->Fptr)->tableptr;   /* point to first column */
+    colptr += (colnum - 1);     /* offset to correct column structure */
+
+    tcode  = colptr->tdatatype;
+
+    if (tcode > 0)
+       repeat = colptr->trepeat;  /* repeat count for this column */
+    else
+       repeat = firstelem -1 + nelem;  /* variable length arrays */
+
+    /* if variable length array, first write the whole input vector, 
+       then go back and fill in the nulls */
+    if (tcode < 0) {
+      if (ffpcluk(fptr, colnum, firstrow, firstelem, nelem, array, status) > 0) {
+        if (*status == NUM_OVERFLOW) 
+	{
+	  /* ignore overflows, which are possibly the null pixel values */
+	  /*  overflow = 1;   */
+	  *status = 0;
+	} else { 
+          return(*status);
+	}
+      }
+    }
+
+    /* absolute element number in the column */
+    first = (firstrow - 1) * repeat + firstelem;
+
+    for (ii = 0; ii < nelem; ii++)
+    {
+      if (array[ii] != nulvalue)  /* is this a good pixel? */
+      {
+         if (nbad)  /* write previous string of bad pixels */
+         {
+            fstelm = ii - nbad + first;  /* absolute element number */
+            fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+            fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+            if (ffpclu(fptr, colnum, fstrow, fstelm, nbad, status) > 0)
+                return(*status);
+
+            nbad=0;
+         }
+
+         ngood = ngood +1;  /* the consecutive number of good pixels */
+      }
+      else
+      {
+         if (ngood)  /* write previous string of good pixels */
+         {
+            fstelm = ii - ngood + first;  /* absolute element number */
+            fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+            fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+            if (tcode > 0) {  /* variable length arrays have already been written */
+              if (ffpcluk(fptr, colnum, fstrow, fstelm, ngood, &array[ii-ngood],
+                status) > 0) {
+		if (*status == NUM_OVERFLOW) 
+		{
+		  overflow = 1;
+		  *status = 0;
+		} else { 
+                  return(*status);
+		}
+	      }
+	    }
+            ngood=0;
+         }
+
+         nbad = nbad +1;  /* the consecutive number of bad pixels */
+      }
+    }
+
+    /* finished loop;  now just write the last set of pixels */
+
+    if (ngood)  /* write last string of good pixels */
+    {
+      fstelm = ii - ngood + first;  /* absolute element number */
+      fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+      fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+      if (tcode > 0) {  /* variable length arrays have already been written */
+        ffpcluk(fptr, colnum, fstrow, fstelm, ngood, &array[ii-ngood], status);
+      }
+    }
+    else if (nbad) /* write last string of bad pixels */
+    {
+      fstelm = ii - nbad + first;  /* absolute element number */
+      fstrow = (fstelm - 1) / repeat + 1;  /* starting row number */
+      fstelm = fstelm - (fstrow - 1) * repeat;  /* relative number */
+
+      ffpclu(fptr, colnum, fstrow, fstelm, nbad, status);
+    }
+
+    if (*status <= 0) {
+      if (overflow) {
+        *status = NUM_OVERFLOW;
+      }
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffuintfi1(unsigned int *input, /* I - array of values to be converted  */
+            long ntodo,            /* I - number of elements in the array  */
+            double scale,          /* I - FITS TSCALn or BSCALE value      */
+            double zero,           /* I - FITS TZEROn or BZERO  value      */
+            unsigned char *output, /* O - output array of converted values */
+            int *status)           /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required.
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            if (input[ii] > UCHAR_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = UCHAR_MAX;
+            }
+            else
+                output[ii] = input[ii];
+        }
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = (input[ii] - zero) / scale;
+
+            if (dvalue < DUCHAR_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = 0;
+            }
+            else if (dvalue > DUCHAR_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = UCHAR_MAX;
+            }
+            else
+                output[ii] = (unsigned char) (dvalue + .5);
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffuintfi2(unsigned int *input,  /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            short *output,     /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required.
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            if (input[ii] > SHRT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = SHRT_MAX;
+            }
+            else
+                output[ii] = input[ii];
+        }
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = (input[ii] - zero) / scale;
+
+            if (dvalue < DSHRT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = SHRT_MIN;
+            }
+            else if (dvalue > DSHRT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = SHRT_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0)
+                    output[ii] = (short) (dvalue + .5);
+                else
+                    output[ii] = (short) (dvalue - .5);
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffuintfi4(unsigned int *input,  /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            INT32BIT *output,  /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 2147483648.)
+    {       
+        /* Instead of subtracting 2147483648, it is more efficient */
+        /* to just flip the sign bit with the XOR operator */
+
+        for (ii = 0; ii < ntodo; ii++)
+             output[ii] =  ( *(int *) &input[ii] ) ^ 0x80000000;
+    }
+    else if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            if (input[ii] > INT32_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = INT32_MAX;
+            }
+            else
+                output[ii] = input[ii];
+        }
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = (input[ii] - zero) / scale;
+
+            if (dvalue < DINT_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = INT32_MIN;
+            }
+            else if (dvalue > DINT_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = INT32_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0)
+                    output[ii] = (INT32BIT) (dvalue + .5);
+                else
+                    output[ii] = (INT32BIT) (dvalue - .5);
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffuintfi8(unsigned int *input,  /* I - array of values to be converted  */
+            long ntodo,             /* I - number of elements in the array  */
+            double scale,           /* I - FITS TSCALn or BSCALE value      */
+            double zero,            /* I - FITS TZEROn or BZERO  value      */
+            LONGLONG *output,       /* O - output array of converted values */
+            int *status)            /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required
+*/
+{
+    long ii;
+    double dvalue;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+                output[ii] = input[ii];
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+            dvalue = (input[ii] - zero) / scale;
+
+            if (dvalue < DLONGLONG_MIN)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = LONGLONG_MIN;
+            }
+            else if (dvalue > DLONGLONG_MAX)
+            {
+                *status = OVERFLOW_ERR;
+                output[ii] = LONGLONG_MAX;
+            }
+            else
+            {
+                if (dvalue >= 0)
+                    output[ii] = (LONGLONG) (dvalue + .5);
+                else
+                    output[ii] = (LONGLONG) (dvalue - .5);
+            }
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffuintfr4(unsigned int *input,  /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            float *output,     /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required.
+*/
+{
+    long ii;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (float) input[ii];
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+            output[ii] = (float) ((input[ii] - zero) / scale);
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffuintfr8(unsigned int *input,  /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            double *output,    /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do datatype conversion and scaling if required.
+*/
+{
+    long ii;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+                output[ii] = (double) input[ii];
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+            output[ii] = (input[ii] - zero) / scale;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffuintfstr(unsigned int *input, /* I - array of values to be converted  */
+            long ntodo,        /* I - number of elements in the array  */
+            double scale,      /* I - FITS TSCALn or BSCALE value      */
+            double zero,       /* I - FITS TZEROn or BZERO  value      */
+            char *cform,       /* I - format for output string values  */
+            long twidth,       /* I - width of each field, in chars    */
+            char *output,      /* O - output array of converted values */
+            int *status)       /* IO - error status                    */
+/*
+  Copy input to output prior to writing output to a FITS file.
+  Do scaling if required.
+*/
+{
+    long ii;
+    double dvalue;
+    char *cptr;
+    
+    cptr = output;
+
+    if (scale == 1. && zero == 0.)
+    {       
+        for (ii = 0; ii < ntodo; ii++)
+        {
+           sprintf(output, cform, (double) input[ii]);
+           output += twidth;
+
+           if (*output)  /* if this char != \0, then overflow occurred */
+              *status = OVERFLOW_ERR;
+        }
+    }
+    else
+    {
+        for (ii = 0; ii < ntodo; ii++)
+        {
+          dvalue = (input[ii] - zero) / scale;
+          sprintf(output, cform, dvalue);
+          output += twidth;
+
+          if (*output)  /* if this char != \0, then overflow occurred */
+            *status = OVERFLOW_ERR;
+        }
+    }
+
+    /* replace any commas with periods (e.g., in French locale) */
+    while ((cptr = strchr(cptr, ','))) *cptr = '.';
+    
+    return(*status);
+}
diff --git a/src/plugins/cfitsio/putkey.c b/src/plugins/cfitsio/putkey.c
new file mode 100644
index 0000000..2a88096
--- /dev/null
+++ b/src/plugins/cfitsio/putkey.c
@@ -0,0 +1,3085 @@
+/*  This file, putkey.c, contains routines that write keywords to          */
+/*  a FITS header.                                                         */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <time.h>
+/* stddef.h is apparently needed to define size_t */
+#include <stddef.h>
+#include "fitsio2.h"
+
+/*--------------------------------------------------------------------------*/
+int ffcrim(fitsfile *fptr,      /* I - FITS file pointer           */
+           int bitpix,          /* I - bits per pixel              */
+           int naxis,           /* I - number of axes in the array */
+           long *naxes,         /* I - size of each axis           */
+           int *status)         /* IO - error status               */
+/*
+  create an IMAGE extension following the current HDU. If the
+  current HDU is empty (contains no header keywords), then simply
+  write the required image (or primary array) keywords to the current
+  HDU. 
+*/
+{
+    if (*status > 0)
+        return(*status);
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    /* create new extension if current header is not empty */
+    if ((fptr->Fptr)->headend != (fptr->Fptr)->headstart[(fptr->Fptr)->curhdu] )
+        ffcrhd(fptr, status);
+
+    /* write the required header keywords */
+    ffphpr(fptr, TRUE, bitpix, naxis, naxes, 0, 1, TRUE, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffcrimll(fitsfile *fptr,    /* I - FITS file pointer           */
+           int bitpix,          /* I - bits per pixel              */
+           int naxis,           /* I - number of axes in the array */
+           LONGLONG *naxes,     /* I - size of each axis           */
+           int *status)         /* IO - error status               */
+/*
+  create an IMAGE extension following the current HDU. If the
+  current HDU is empty (contains no header keywords), then simply
+  write the required image (or primary array) keywords to the current
+  HDU. 
+*/
+{
+    if (*status > 0)
+        return(*status);
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    /* create new extension if current header is not empty */
+    if ((fptr->Fptr)->headend != (fptr->Fptr)->headstart[(fptr->Fptr)->curhdu] )
+        ffcrhd(fptr, status);
+
+    /* write the required header keywords */
+    ffphprll(fptr, TRUE, bitpix, naxis, naxes, 0, 1, TRUE, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffcrtb(fitsfile *fptr,  /* I - FITS file pointer                        */
+           int tbltype,     /* I - type of table to create                  */
+           LONGLONG naxis2,     /* I - number of rows in the table              */
+           int tfields,     /* I - number of columns in the table           */
+           char **ttype,    /* I - name of each column                      */
+           char **tform,    /* I - value of TFORMn keyword for each column  */
+           char **tunit,    /* I - value of TUNITn keyword for each column  */
+           const char *extnm,     /* I - value of EXTNAME keyword, if any         */
+           int *status)     /* IO - error status                            */
+/*
+  Create a table extension in a FITS file. 
+*/
+{
+    LONGLONG naxis1 = 0;
+    long *tbcol = 0;
+
+    if (*status > 0)
+        return(*status);
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    /* create new extension if current header is not empty */
+    if ((fptr->Fptr)->headend != (fptr->Fptr)->headstart[(fptr->Fptr)->curhdu] )
+        ffcrhd(fptr, status);
+
+    if ((fptr->Fptr)->curhdu == 0)  /* have to create dummy primary array */
+    {
+       ffcrim(fptr, 16, 0, tbcol, status);
+       ffcrhd(fptr, status);
+    }
+    
+    if (tbltype == BINARY_TBL)
+    {
+      /* write the required header keywords. This will write PCOUNT = 0 */
+      ffphbn(fptr, naxis2, tfields, ttype, tform, tunit, extnm, 0, status);
+    }
+    else if (tbltype == ASCII_TBL)
+    {
+      /* write the required header keywords */
+      /* default values for naxis1 and tbcol will be calculated */
+      ffphtb(fptr, naxis1, naxis2, tfields, ttype, tbcol, tform, tunit,
+             extnm, status);
+    }
+    else
+      *status = NOT_TABLE;
+
+    return(*status);
+}
+/*-------------------------------------------------------------------------*/
+int ffpktp(fitsfile *fptr,       /* I - FITS file pointer       */
+           const char *filename, /* I - name of template file   */
+           int *status)          /* IO - error status           */
+/*
+  read keywords from template file and append to the FITS file
+*/
+{
+    FILE *diskfile;
+    char card[FLEN_CARD], template[161];
+    char keyname[FLEN_KEYWORD], newname[FLEN_KEYWORD];
+    int keytype;
+    size_t slen;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    diskfile = fopen(filename,"r"); 
+    if (!diskfile)          /* couldn't open file */
+    {
+            ffpmsg("ffpktp could not open the following template file:");
+            ffpmsg(filename);
+            return(*status = FILE_NOT_OPENED); 
+    }
+
+    while (fgets(template, 160, diskfile) )  /* get next template line */
+    {
+      template[160] = '\0';      /* make sure string is terminated */
+      slen = strlen(template);   /* get string length */
+      template[slen - 1] = '\0';  /* over write the 'newline' char */
+
+      if (ffgthd(template, card, &keytype, status) > 0) /* parse template */
+         break;
+
+      strncpy(keyname, card, 8);
+      keyname[8] = '\0';
+
+      if (keytype == -2)            /* rename the card */
+      {
+         strncpy(newname, &card[40], 8);
+         newname[8] = '\0';
+
+         ffmnam(fptr, keyname, newname, status); 
+      }
+      else if (keytype == -1)      /* delete the card */
+      {
+         ffdkey(fptr, keyname, status);
+      }
+      else if (keytype == 0)       /* update the card */
+      {
+         ffucrd(fptr, keyname, card, status);
+      }
+      else if (keytype == 1)      /* append the card */
+      {
+         ffprec(fptr, card, status);
+      }
+      else    /* END card; stop here */
+      {
+         break; 
+      }
+    }
+
+    fclose(diskfile);   /* close the template file */
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpky( fitsfile *fptr,     /* I - FITS file pointer        */
+           int  datatype,      /* I - datatype of the value    */
+           const char *keyname,      /* I - name of keyword to write */
+           void *value,        /* I - keyword value            */
+           const char *comm,         /* I - keyword comment          */
+           int  *status)       /* IO - error status            */
+/*
+  Write (put) the keyword, value and comment into the FITS header.
+  Writes a keyword value with the datatype specified by the 2nd argument.
+*/
+{
+    char errmsg[81];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (datatype == TSTRING)
+    {
+        ffpkys(fptr, keyname, (char *) value, comm, status);
+    }
+    else if (datatype == TBYTE)
+    {
+        ffpkyj(fptr, keyname, (LONGLONG) *(unsigned char *) value, comm, status);
+    }
+    else if (datatype == TSBYTE)
+    {
+        ffpkyj(fptr, keyname, (LONGLONG) *(signed char *) value, comm, status);
+    }
+    else if (datatype == TUSHORT)
+    {
+        ffpkyj(fptr, keyname, (LONGLONG) *(unsigned short *) value, comm, status);
+    }
+    else if (datatype == TSHORT)
+    {
+        ffpkyj(fptr, keyname, (LONGLONG) *(short *) value, comm, status);
+    }
+    else if (datatype == TUINT)
+    {
+        ffpkyg(fptr, keyname, (double) *(unsigned int *) value, 0,
+               comm, status);
+    }
+    else if (datatype == TINT)
+    {
+        ffpkyj(fptr, keyname, (LONGLONG) *(int *) value, comm, status);
+    }
+    else if (datatype == TLOGICAL)
+    {
+        ffpkyl(fptr, keyname, *(int *) value, comm, status);
+    }
+    else if (datatype == TULONG)
+    {
+        ffpkyg(fptr, keyname, (double) *(unsigned long *) value, 0,
+               comm, status);
+    }
+    else if (datatype == TLONG)
+    {
+        ffpkyj(fptr, keyname, (LONGLONG) *(long *) value, comm, status);
+    }
+    else if (datatype == TLONGLONG)
+    {
+        ffpkyj(fptr, keyname, *(LONGLONG *) value, comm, status);
+    }
+    else if (datatype == TFLOAT)
+    {
+        ffpkye(fptr, keyname, *(float *) value, -7, comm, status);
+    }
+    else if (datatype == TDOUBLE)
+    {
+        ffpkyd(fptr, keyname, *(double *) value, -15, comm, status);
+    }
+    else if (datatype == TCOMPLEX)
+    {
+        ffpkyc(fptr, keyname, (float *) value, -7, comm, status);
+    }
+    else if (datatype == TDBLCOMPLEX)
+    {
+        ffpkym(fptr, keyname, (double *) value, -15, comm, status);
+    }
+    else
+    {
+        sprintf(errmsg, "Bad keyword datatype code: %d (ffpky)", datatype);
+        ffpmsg(errmsg);
+        *status = BAD_DATATYPE;
+    }
+
+    return(*status);
+} 
+/*-------------------------------------------------------------------------*/
+int ffprec(fitsfile *fptr,     /* I - FITS file pointer        */
+           const char *card,   /* I - string to be written     */
+           int *status)        /* IO - error status            */
+/*
+  write a keyword record (80 bytes long) to the end of the header
+*/
+{
+    char tcard[FLEN_CARD];
+    size_t len, ii;
+    long nblocks;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    if ( ((fptr->Fptr)->datastart - (fptr->Fptr)->headend) == 80) /* no room */
+    {
+        nblocks = 1;
+        if (ffiblk(fptr, nblocks, 0, status) > 0) /* insert 2880-byte block */
+            return(*status);  
+    }
+
+    strncpy(tcard,card,80);
+    tcard[80] = '\0';
+
+    len = strlen(tcard);
+
+    /* silently replace any illegal characters with a space */
+    for (ii=0; ii < len; ii++)   
+        if (tcard[ii] < ' ' || tcard[ii] > 126) tcard[ii] = ' ';
+
+    for (ii=len; ii < 80; ii++)    /* fill card with spaces if necessary */
+        tcard[ii] = ' ';
+
+    for (ii=0; ii < 8; ii++)       /* make sure keyword name is uppercase */
+        tcard[ii] = toupper(tcard[ii]);
+
+    fftkey(tcard, status);        /* test keyword name contains legal chars */
+
+/*  no need to do this any more, since any illegal characters have been removed
+    fftrec(tcard, status);  */        /* test rest of keyword for legal chars */
+
+    ffmbyt(fptr, (fptr->Fptr)->headend, IGNORE_EOF, status); /* move to end */
+
+    ffpbyt(fptr, 80, tcard, status);   /* write the 80 byte card */
+
+    if (*status <= 0)
+       (fptr->Fptr)->headend += 80;    /* update end-of-header position */
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpkyu( fitsfile *fptr,     /* I - FITS file pointer        */
+            const char *keyname,      /* I - name of keyword to write */
+            const char *comm,         /* I - keyword comment          */
+            int  *status)       /* IO - error status            */
+/*
+  Write (put) a null-valued keyword and comment into the FITS header.  
+*/
+{
+    char valstring[FLEN_VALUE];
+    char card[FLEN_CARD];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    strcpy(valstring," ");  /* create a dummy value string */
+    ffmkky(keyname, valstring, comm, card, status);  /* construct the keyword */
+    ffprec(fptr, card, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpkys( fitsfile *fptr,     /* I - FITS file pointer        */
+            const char *keyname,      /* I - name of keyword to write */
+            char *value,        /* I - keyword value            */
+            const char *comm,         /* I - keyword comment          */
+            int  *status)       /* IO - error status            */
+/*
+  Write (put) the keyword, value and comment into the FITS header.
+  The value string will be truncated at 68 characters which is the
+  maximum length that will fit on a single FITS keyword.
+*/
+{
+    char valstring[FLEN_VALUE];
+    char card[FLEN_CARD];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    ffs2c(value, valstring, status);   /* put quotes around the string */
+    ffmkky(keyname, valstring, comm, card, status);  /* construct the keyword */
+    ffprec(fptr, card, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpkls( fitsfile *fptr,     /* I - FITS file pointer        */
+            const char *keyname,      /* I - name of keyword to write */
+            const char *value,        /* I - keyword value            */
+            const char *comm,         /* I - keyword comment          */
+            int  *status)       /* IO - error status            */
+/*
+  Write (put) the keyword, value and comment into the FITS header.
+  This routine is a modified version of ffpkys which supports the
+  HEASARC long string convention and can write arbitrarily long string
+  keyword values.  The value is continued over multiple keywords that
+  have the name COMTINUE without an equal sign in column 9 of the card.
+  This routine also supports simple string keywords which are less than
+  69 characters in length.
+*/
+{
+    char valstring[FLEN_CARD];
+    char card[FLEN_CARD], tmpkeyname[FLEN_CARD];
+    char tstring[FLEN_CARD], *cptr;
+    int next, remain, vlen, nquote, nchar, namelen, contin, tstatus = -1;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    remain = maxvalue(strlen(value), 1); /* no. of chars to write (at least 1) */    
+    /* count the number of single quote characters are in the string */
+    tstring[0] = '\0';
+    strncat(tstring, value, 68); /* copy 1st part of string to temp buff */
+    nquote = 0;
+    cptr = strchr(tstring, '\'');   /* search for quote character */
+    while (cptr)  /* search for quote character */
+    {
+        nquote++;            /*  increment no. of quote characters  */
+        cptr++;              /*  increment pointer to next character */
+        cptr = strchr(cptr, '\'');  /* search for another quote char */
+    }
+
+    strncpy(tmpkeyname, keyname, 80);
+    tmpkeyname[80] = '\0';
+    
+    cptr = tmpkeyname;
+    while(*cptr == ' ')   /* skip over leading spaces in name */
+        cptr++;
+
+    /* determine the number of characters that will fit on the line */
+    /* Note: each quote character is expanded to 2 quotes */
+
+    namelen = strlen(cptr);
+    if (namelen <= 8 && (fftkey(cptr, &tstatus) <= 0) )
+    {
+        /* This a normal 8-character FITS keyword */
+        nchar = 68 - nquote; /*  max of 68 chars fit in a FITS string value */
+    }
+    else
+    {
+        /* This a HIERARCH keyword */
+        if (FSTRNCMP(cptr, "HIERARCH ", 9) && 
+            FSTRNCMP(cptr, "hierarch ", 9))
+            nchar = 66 - nquote - namelen;
+        else
+            nchar = 75 - nquote - namelen;  /* don't count 'HIERARCH' twice */
+
+    }
+
+    contin = 0;
+    next = 0;                  /* pointer to next character to write */
+
+    while (remain > 0)
+    {
+        tstring[0] = '\0';
+        strncat(tstring, &value[next], nchar); /* copy string to temp buff */
+        ffs2c(tstring, valstring, status);  /* put quotes around the string */
+
+        if (remain > nchar)   /* if string is continued, put & as last char */
+        {
+            vlen = strlen(valstring);
+            nchar -= 1;        /* outputting one less character now */
+
+            if (valstring[vlen-2] != '\'')
+                valstring[vlen-2] = '&';  /*  over write last char with &  */
+            else
+            { /* last char was a pair of single quotes, so over write both */
+                valstring[vlen-3] = '&';
+                valstring[vlen-1] = '\0';
+            }
+        }
+
+        if (contin)           /* This is a CONTINUEd keyword */
+        {
+           ffmkky("CONTINUE", valstring, comm, card, status); /* make keyword */
+           strncpy(&card[8], "   ",  2);  /* overwrite the '=' */
+        }
+        else
+        {
+           ffmkky(keyname, valstring, comm, card, status);  /* make keyword */
+        }
+
+        ffprec(fptr, card, status);  /* write the keyword */
+
+        contin = 1;
+        remain -= nchar;
+        next  += nchar;
+
+        if (remain > 0) 
+        {
+           /* count the number of single quote characters in next section */
+           tstring[0] = '\0';
+           strncat(tstring, &value[next], 68); /* copy next part of string */
+           nquote = 0;
+           cptr = strchr(tstring, '\'');   /* search for quote character */
+           while (cptr)  /* search for quote character */
+           {
+               nquote++;            /*  increment no. of quote characters  */
+               cptr++;              /*  increment pointer to next character */
+               cptr = strchr(cptr, '\'');  /* search for another quote char */
+           }
+           nchar = 68 - nquote;  /* max number of chars to write this time */
+        }
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffplsw( fitsfile *fptr,     /* I - FITS file pointer  */
+            int  *status)       /* IO - error status       */
+/*
+  Write the LONGSTRN keyword and a series of related COMMENT keywords
+  which document that this FITS header may contain long string keyword
+  values which are continued over multiple keywords using the HEASARC
+  long string keyword convention.  If the LONGSTRN keyword already exists
+  then this routine simple returns without doing anything.
+*/
+{
+    char valstring[FLEN_VALUE], comm[FLEN_COMMENT];
+    int tstatus;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    tstatus = 0;
+    if (ffgkys(fptr, "LONGSTRN", valstring, comm, &tstatus) == 0)
+        return(*status);     /* keyword already exists, so just return */
+
+    ffpkys(fptr, "LONGSTRN", "OGIP 1.0", 
+       "The HEASARC Long String Convention may be used.", status);
+
+    ffpcom(fptr,
+    "  This FITS file may contain long string keyword values that are", status);
+
+    ffpcom(fptr,
+    "  continued over multiple keywords.  The HEASARC convention uses the &",
+    status);
+
+    ffpcom(fptr,
+    "  character at the end of each substring which is then continued", status);
+
+    ffpcom(fptr,
+    "  on the next keyword which has the name CONTINUE.", status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpkyl( fitsfile *fptr,     /* I - FITS file pointer        */
+            const char *keyname,      /* I - name of keyword to write */
+            int  value,         /* I - keyword value            */
+            const char *comm,         /* I - keyword comment          */
+            int  *status)       /* IO - error status            */
+/*
+  Write (put) the keyword, value and comment into the FITS header.
+  Values equal to 0 will result in a False FITS keyword; any other
+  non-zero value will result in a True FITS keyword.
+*/
+{
+    char valstring[FLEN_VALUE];
+    char card[FLEN_CARD];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    ffl2c(value, valstring, status);   /* convert to formatted string */
+    ffmkky(keyname, valstring, comm, card, status);  /* construct the keyword*/
+    ffprec(fptr, card, status);  /* write the keyword*/
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpkyj( fitsfile *fptr,     /* I - FITS file pointer        */
+            const char *keyname,      /* I - name of keyword to write */
+            LONGLONG value,     /* I - keyword value            */
+            const char *comm,         /* I - keyword comment          */
+            int  *status)       /* IO - error status            */
+/*
+  Write (put) the keyword, value and comment into the FITS header.
+  Writes an integer keyword value.
+*/
+{
+    char valstring[FLEN_VALUE];
+    char card[FLEN_CARD];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    ffi2c(value, valstring, status);   /* convert to formatted string */
+    ffmkky(keyname, valstring, comm, card, status);  /* construct the keyword*/
+    ffprec(fptr, card, status);  /* write the keyword*/
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpkyf( fitsfile *fptr,      /* I - FITS file pointer                   */
+            const char  *keyname,      /* I - name of keyword to write            */
+            float value,         /* I - keyword value                       */
+            int   decim,         /* I - number of decimal places to display */
+            const char  *comm,         /* I - keyword comment                     */
+            int   *status)       /* IO - error status                       */
+/*
+  Write (put) the keyword, value and comment into the FITS header.
+  Writes a fixed float keyword value.
+*/
+{
+    char valstring[FLEN_VALUE];
+    char card[FLEN_CARD];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    ffr2f(value, decim, valstring, status);   /* convert to formatted string */
+    ffmkky(keyname, valstring, comm, card, status);  /* construct the keyword*/
+    ffprec(fptr, card, status);  /* write the keyword*/
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpkye( fitsfile *fptr,      /* I - FITS file pointer                   */
+            const char  *keyname,      /* I - name of keyword to write            */
+            float value,         /* I - keyword value                       */
+            int   decim,         /* I - number of decimal places to display */
+            const char  *comm,         /* I - keyword comment                     */
+            int   *status)       /* IO - error status                       */
+/*
+  Write (put) the keyword, value and comment into the FITS header.
+  Writes an exponential float keyword value.
+*/
+{
+    char valstring[FLEN_VALUE];
+    char card[FLEN_CARD];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    ffr2e(value, decim, valstring, status);   /* convert to formatted string */
+    ffmkky(keyname, valstring, comm, card, status);  /* construct the keyword*/
+    ffprec(fptr, card, status);  /* write the keyword*/
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpkyg( fitsfile *fptr,      /* I - FITS file pointer                   */
+            const char  *keyname,      /* I - name of keyword to write            */
+            double value,        /* I - keyword value                       */
+            int   decim,         /* I - number of decimal places to display */
+            const char  *comm,         /* I - keyword comment                     */
+            int   *status)       /* IO - error status                       */
+/*
+  Write (put) the keyword, value and comment into the FITS header.
+  Writes a fixed double keyword value.*/
+{
+    char valstring[FLEN_VALUE];
+    char card[FLEN_CARD];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    ffd2f(value, decim, valstring, status);  /* convert to formatted string */
+    ffmkky(keyname, valstring, comm, card, status);  /* construct the keyword*/
+    ffprec(fptr, card, status);  /* write the keyword*/
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpkyd( fitsfile *fptr,      /* I - FITS file pointer                   */
+            const char  *keyname,      /* I - name of keyword to write            */
+            double value,        /* I - keyword value                       */
+            int   decim,         /* I - number of decimal places to display */
+            const char  *comm,         /* I - keyword comment                     */
+            int   *status)       /* IO - error status                       */
+/*
+  Write (put) the keyword, value and comment into the FITS header.
+  Writes an exponential double keyword value.*/
+{
+    char valstring[FLEN_VALUE];
+    char card[FLEN_CARD];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    ffd2e(value, decim, valstring, status);  /* convert to formatted string */
+    ffmkky(keyname, valstring, comm, card, status);  /* construct the keyword*/
+    ffprec(fptr, card, status);  /* write the keyword*/
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpkyc( fitsfile *fptr,      /* I - FITS file pointer                   */
+            const char  *keyname,      /* I - name of keyword to write            */
+            float *value,        /* I - keyword value (real, imaginary)     */
+            int   decim,         /* I - number of decimal places to display */
+            const char  *comm,         /* I - keyword comment                     */
+            int   *status)       /* IO - error status                       */
+/*
+  Write (put) the keyword, value and comment into the FITS header.
+  Writes an complex float keyword value. Format = (realvalue, imagvalue)
+*/
+{
+    char valstring[FLEN_VALUE], tmpstring[FLEN_VALUE];
+    char card[FLEN_CARD];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    strcpy(valstring, "(" );
+    ffr2e(value[0], decim, tmpstring, status); /* convert to string */
+    strcat(valstring, tmpstring);
+    strcat(valstring, ", ");
+    ffr2e(value[1], decim, tmpstring, status); /* convert to string */
+    strcat(valstring, tmpstring);
+    strcat(valstring, ")");
+
+    ffmkky(keyname, valstring, comm, card, status);  /* construct the keyword*/
+    ffprec(fptr, card, status);  /* write the keyword*/
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpkym( fitsfile *fptr,      /* I - FITS file pointer                   */
+            const char  *keyname,      /* I - name of keyword to write            */
+            double *value,       /* I - keyword value (real, imaginary)     */
+            int   decim,         /* I - number of decimal places to display */
+            const char  *comm,         /* I - keyword comment                     */
+            int   *status)       /* IO - error status                       */
+/*
+  Write (put) the keyword, value and comment into the FITS header.
+  Writes an complex double keyword value. Format = (realvalue, imagvalue)
+*/
+{
+    char valstring[FLEN_VALUE], tmpstring[FLEN_VALUE];
+    char card[FLEN_CARD];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    strcpy(valstring, "(" );
+    ffd2e(value[0], decim, tmpstring, status); /* convert to string */
+    strcat(valstring, tmpstring);
+    strcat(valstring, ", ");
+    ffd2e(value[1], decim, tmpstring, status); /* convert to string */
+    strcat(valstring, tmpstring);
+    strcat(valstring, ")");
+
+    ffmkky(keyname, valstring, comm, card, status);  /* construct the keyword*/
+    ffprec(fptr, card, status);  /* write the keyword*/
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpkfc( fitsfile *fptr,      /* I - FITS file pointer                   */
+            const char  *keyname,      /* I - name of keyword to write            */
+            float *value,        /* I - keyword value (real, imaginary)     */
+            int   decim,         /* I - number of decimal places to display */
+            const char  *comm,         /* I - keyword comment                     */
+            int   *status)       /* IO - error status                       */
+/*
+  Write (put) the keyword, value and comment into the FITS header.
+  Writes an complex float keyword value. Format = (realvalue, imagvalue)
+*/
+{
+    char valstring[FLEN_VALUE], tmpstring[FLEN_VALUE];
+    char card[FLEN_CARD];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    strcpy(valstring, "(" );
+    ffr2f(value[0], decim, tmpstring, status); /* convert to string */
+    strcat(valstring, tmpstring);
+    strcat(valstring, ", ");
+    ffr2f(value[1], decim, tmpstring, status); /* convert to string */
+    strcat(valstring, tmpstring);
+    strcat(valstring, ")");
+
+    ffmkky(keyname, valstring, comm, card, status);  /* construct the keyword*/
+    ffprec(fptr, card, status);  /* write the keyword*/
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpkfm( fitsfile *fptr,      /* I - FITS file pointer                   */
+            const char  *keyname,      /* I - name of keyword to write            */
+            double *value,       /* I - keyword value (real, imaginary)     */
+            int   decim,         /* I - number of decimal places to display */
+            const char  *comm,         /* I - keyword comment                     */
+            int   *status)       /* IO - error status                       */
+/*
+  Write (put) the keyword, value and comment into the FITS header.
+  Writes an complex double keyword value. Format = (realvalue, imagvalue)
+*/
+{
+    char valstring[FLEN_VALUE], tmpstring[FLEN_VALUE];
+    char card[FLEN_CARD];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    strcpy(valstring, "(" );
+    ffd2f(value[0], decim, tmpstring, status); /* convert to string */
+    strcat(valstring, tmpstring);
+    strcat(valstring, ", ");
+    ffd2f(value[1], decim, tmpstring, status); /* convert to string */
+    strcat(valstring, tmpstring);
+    strcat(valstring, ")");
+
+    ffmkky(keyname, valstring, comm, card, status);  /* construct the keyword*/
+    ffprec(fptr, card, status);  /* write the keyword*/
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpkyt( fitsfile *fptr,      /* I - FITS file pointer        */
+            const char  *keyname,      /* I - name of keyword to write */
+            long  intval,        /* I - integer part of value    */
+            double fraction,     /* I - fractional part of value */
+            const char  *comm,         /* I - keyword comment          */
+            int   *status)       /* IO - error status            */
+/*
+  Write (put) a 'triple' precision keyword where the integer and
+  fractional parts of the value are passed in separate parameters to
+  increase the total amount of numerical precision.
+*/
+{
+    char valstring[FLEN_VALUE];
+    char card[FLEN_CARD];
+    char fstring[20], *cptr;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (fraction > 1. || fraction < 0.)
+    {
+        ffpmsg("fraction must be between 0. and 1. (ffpkyt)");
+        return(*status = BAD_F2C);
+    }
+
+    ffi2c(intval, valstring, status);  /* convert integer to string */
+    ffd2f(fraction, 16, fstring, status);  /* convert to 16 decimal string */
+
+    cptr = strchr(fstring, '.');    /* find the decimal point */
+    strcat(valstring, cptr);    /* append the fraction to the integer */
+
+    ffmkky(keyname, valstring, comm, card, status);  /* construct the keyword*/
+    ffprec(fptr, card, status);  /* write the keyword*/
+
+    return(*status);
+}
+/*-----------------------------------------------------------------*/
+int ffpcom( fitsfile *fptr,      /* I - FITS file pointer   */
+            const char  *comm,   /* I - comment string      */
+            int   *status)       /* IO - error status       */
+/*
+  Write 1 or more COMMENT keywords.  If the comment string is too
+  long to fit on a single keyword (72 chars) then it will automatically
+  be continued on multiple CONTINUE keywords.
+*/
+{
+    char card[FLEN_CARD];
+    int len, ii;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    len = strlen(comm);
+    ii = 0;
+
+    for (; len > 0; len -= 72)
+    {
+        strcpy(card, "COMMENT ");
+        strncat(card, &comm[ii], 72);
+        ffprec(fptr, card, status);
+        ii += 72;
+    }
+
+    return(*status);
+}
+/*-----------------------------------------------------------------*/
+int ffphis( fitsfile *fptr,      /* I - FITS file pointer  */
+            const char *history, /* I - history string     */
+            int   *status)       /* IO - error status      */
+/*
+  Write 1 or more HISTORY keywords.  If the history string is too
+  long to fit on a single keyword (72 chars) then it will automatically
+  be continued on multiple HISTORY keywords.
+*/
+{
+    char card[FLEN_CARD];
+    int len, ii;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    len = strlen(history);
+    ii = 0;
+
+    for (; len > 0; len -= 72)
+    {
+        strcpy(card, "HISTORY ");
+        strncat(card, &history[ii], 72);
+        ffprec(fptr, card, status);
+        ii += 72;
+    }
+
+    return(*status);
+}
+/*-----------------------------------------------------------------*/
+int ffpdat( fitsfile *fptr,      /* I - FITS file pointer  */
+            int   *status)       /* IO - error status      */
+/*
+  Write the DATE keyword into the FITS header.  If the keyword already
+  exists then the date will simply be updated in the existing keyword.
+*/
+{
+    int timeref;
+    char date[30], tmzone[10], card[FLEN_CARD];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    ffgstm(date, &timeref, status);
+
+    if (timeref)           /* GMT not available on this machine */
+        strcpy(tmzone, " Local");    
+    else
+        strcpy(tmzone, " UT");    
+
+    strcpy(card, "DATE    = '");
+    strcat(card, date);
+    strcat(card, "' / file creation date (YYYY-MM-DDThh:mm:ss");
+    strcat(card, tmzone);
+    strcat(card, ")");
+
+    ffucrd(fptr, "DATE", card, status);
+
+    return(*status);
+}
+/*-------------------------------------------------------------------*/
+int ffverifydate(int year,          /* I - year (0 - 9999)           */
+                 int month,         /* I - month (1 - 12)            */
+                 int day,           /* I - day (1 - 31)              */
+                 int   *status)     /* IO - error status             */
+/*
+  Verify that the date is valid
+*/
+{
+    int ndays[] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
+    char errmsg[81];
+    
+
+    if (year < 0 || year > 9999)
+    {
+       sprintf(errmsg, 
+       "input year value = %d is out of range 0 - 9999", year);
+       ffpmsg(errmsg);
+       return(*status = BAD_DATE);
+    }
+    else if (month < 1 || month > 12)
+    {
+       sprintf(errmsg, 
+       "input month value = %d is out of range 1 - 12", month);
+       ffpmsg(errmsg);
+       return(*status = BAD_DATE);
+    }
+    
+    if (ndays[month] == 31) {
+        if (day < 1 || day > 31)
+        {
+           sprintf(errmsg, 
+           "input day value = %d is out of range 1 - 31 for month %d", day, month);
+           ffpmsg(errmsg);
+           return(*status = BAD_DATE);
+        }
+    } else if (ndays[month] == 30) {
+        if (day < 1 || day > 30)
+        {
+           sprintf(errmsg, 
+           "input day value = %d is out of range 1 - 30 for month %d", day, month);
+           ffpmsg(errmsg);
+           return(*status = BAD_DATE);
+        }
+    } else {
+        if (day < 1 || day > 28)
+        {
+            if (day == 29)
+            {
+	      /* year is a leap year if it is divisible by 4 but not by 100,
+	         except years divisible by 400 are leap years
+	      */
+	        if ((year % 4 == 0 && year % 100 != 0 ) || year % 400 == 0)
+		   return (*status);
+		   
+ 	        sprintf(errmsg, 
+           "input day value = %d is out of range 1 - 28 for February %d (not leap year)", day, year);
+                ffpmsg(errmsg);
+	    } else {
+                sprintf(errmsg, 
+                "input day value = %d is out of range 1 - 28 (or 29) for February", day);
+                ffpmsg(errmsg);
+	    }
+	    
+            return(*status = BAD_DATE);
+        }
+    }
+    return(*status);
+}
+/*-----------------------------------------------------------------*/
+int ffgstm( char *timestr,   /* O  - returned system date and time string  */
+            int  *timeref,   /* O - GMT = 0, Local time = 1  */
+            int   *status)   /* IO - error status      */
+/*
+  Returns the current date and time in format 'yyyy-mm-ddThh:mm:ss'.
+*/
+{
+    time_t tp;
+    struct tm *ptr;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    time(&tp);
+    ptr = gmtime(&tp);         /* get GMT (= UTC) time */
+
+    if (timeref)
+    {
+        if (ptr)
+            *timeref = 0;   /* returning GMT */
+        else
+            *timeref = 1;   /* returning local time */
+    }
+
+    if (!ptr)                  /* GMT not available on this machine */
+        ptr = localtime(&tp); 
+
+    strftime(timestr, 25, "%Y-%m-%dT%H:%M:%S", ptr);
+
+    return(*status);
+}
+/*-----------------------------------------------------------------*/
+int ffdt2s(int year,          /* I - year (0 - 9999)           */
+           int month,         /* I - month (1 - 12)            */
+           int day,           /* I - day (1 - 31)              */
+           char *datestr,     /* O - date string: "YYYY-MM-DD" */
+           int   *status)     /* IO - error status             */
+/*
+  Construct a date character string
+*/
+{
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    *datestr = '\0';
+    
+    if (ffverifydate(year, month, day, status) > 0)
+    {
+        ffpmsg("invalid date (ffdt2s)");
+        return(*status);
+    }
+
+    if (year >= 1900 && year <= 1998)  /* use old 'dd/mm/yy' format */
+        sprintf(datestr, "%.2d/%.2d/%.2d", day, month, year - 1900);
+
+    else  /* use the new 'YYYY-MM-DD' format */
+        sprintf(datestr, "%.4d-%.2d-%.2d", year, month, day);
+
+    return(*status);
+}
+/*-----------------------------------------------------------------*/
+int ffs2dt(char *datestr,   /* I - date string: "YYYY-MM-DD" or "dd/mm/yy" */
+           int *year,       /* O - year (0 - 9999)                         */
+           int *month,      /* O - month (1 - 12)                          */
+           int *day,        /* O - day (1 - 31)                            */
+           int   *status)   /* IO - error status                           */
+/*
+  Parse a date character string into year, month, and day values
+*/
+{
+    int slen, lyear, lmonth, lday;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (year)
+        *year = 0;
+    if (month)
+        *month = 0;
+    if (day)
+        *day   = 0;
+
+    if (!datestr)
+    {
+        ffpmsg("error: null input date string (ffs2dt)");
+        return(*status = BAD_DATE);   /* Null datestr pointer ??? */
+    }
+
+    slen = strlen(datestr);
+
+    if (slen == 8 && datestr[2] == '/' && datestr[5] == '/')
+    {
+        if (isdigit((int) datestr[0]) && isdigit((int) datestr[1])
+         && isdigit((int) datestr[3]) && isdigit((int) datestr[4])
+         && isdigit((int) datestr[6]) && isdigit((int) datestr[7]) )
+        {
+            /* this is an old format string: "dd/mm/yy" */
+            lyear  = atoi(&datestr[6]) + 1900;
+            lmonth = atoi(&datestr[3]);
+	    lday   = atoi(datestr);
+	    
+            if (year)
+                *year = lyear;
+            if (month)
+                *month = lmonth;
+            if (day)
+                *day   = lday;
+        }
+        else
+        {
+            ffpmsg("input date string has illegal format (ffs2dt):");
+            ffpmsg(datestr);
+            return(*status = BAD_DATE);
+        }
+    }
+    else if (slen >= 10 && datestr[4] == '-' && datestr[7] == '-')
+        {
+        if (isdigit((int) datestr[0]) && isdigit((int) datestr[1])
+         && isdigit((int) datestr[2]) && isdigit((int) datestr[3])
+         && isdigit((int) datestr[5]) && isdigit((int) datestr[6])
+         && isdigit((int) datestr[8]) && isdigit((int) datestr[9]) )
+        {
+            if (slen > 10 && datestr[10] != 'T')
+            {
+                ffpmsg("input date string has illegal format (ffs2dt):");
+                ffpmsg(datestr);
+                return(*status = BAD_DATE);
+            }
+
+            /* this is a new format string: "yyyy-mm-dd" */
+            lyear  = atoi(datestr);
+            lmonth = atoi(&datestr[5]);
+            lday   = atoi(&datestr[8]);
+
+            if (year)
+               *year  = lyear;
+            if (month)
+               *month = lmonth;
+            if (day)
+               *day   = lday;
+        }
+        else
+        {
+                ffpmsg("input date string has illegal format (ffs2dt):");
+                ffpmsg(datestr);
+                return(*status = BAD_DATE);
+        }
+    }
+    else
+    {
+                ffpmsg("input date string has illegal format (ffs2dt):");
+                ffpmsg(datestr);
+                return(*status = BAD_DATE);
+    }
+
+
+    if (ffverifydate(lyear, lmonth, lday, status) > 0)
+    {
+        ffpmsg("invalid date (ffs2dt)");
+    }
+
+    return(*status);
+}
+/*-----------------------------------------------------------------*/
+int fftm2s(int year,          /* I - year (0 - 9999)           */
+           int month,         /* I - month (1 - 12)            */
+           int day,           /* I - day (1 - 31)              */
+           int hour,          /* I - hour (0 - 23)             */
+           int minute,        /* I - minute (0 - 59)           */
+           double second,     /* I - second (0. - 60.9999999)  */
+           int decimals,      /* I - number of decimal points to write      */
+           char *datestr,     /* O - date string: "YYYY-MM-DDThh:mm:ss.ddd" */
+                              /*   or "hh:mm:ss.ddd" if year, month day = 0 */
+           int   *status)     /* IO - error status             */
+/*
+  Construct a date and time character string
+*/
+{
+    int width;
+    char errmsg[81];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    *datestr='\0';
+
+    if (year != 0 || month != 0 || day !=0)
+    { 
+        if (ffverifydate(year, month, day, status) > 0)
+	{
+            ffpmsg("invalid date (fftm2s)");
+            return(*status);
+        }
+    }
+
+    if (hour < 0 || hour > 23)
+    {
+       sprintf(errmsg, 
+       "input hour value is out of range 0 - 23: %d (fftm2s)", hour);
+       ffpmsg(errmsg);
+       return(*status = BAD_DATE);
+    }
+    else if (minute < 0 || minute > 59)
+    {
+       sprintf(errmsg, 
+       "input minute value is out of range 0 - 59: %d (fftm2s)", minute);
+       ffpmsg(errmsg);
+       return(*status = BAD_DATE);
+    }
+    else if (second < 0. || second >= 61)
+    {
+       sprintf(errmsg, 
+       "input second value is out of range 0 - 60.999: %f (fftm2s)", second);
+       ffpmsg(errmsg);
+       return(*status = BAD_DATE);
+    }
+    else if (decimals > 25)
+    {
+       sprintf(errmsg, 
+       "input decimals value is out of range 0 - 25: %d (fftm2s)", decimals);
+       ffpmsg(errmsg);
+       return(*status = BAD_DATE);
+    }
+
+    if (decimals == 0)
+       width = 2;
+    else
+       width = decimals + 3;
+
+    if (decimals < 0)
+    {
+        /* a negative decimals value means return only the date, not time */
+        sprintf(datestr, "%.4d-%.2d-%.2d", year, month, day);
+    }
+    else if (year == 0 && month == 0 && day == 0)
+    {
+        /* return only the time, not the date */
+        sprintf(datestr, "%.2d:%.2d:%0*.*f",
+            hour, minute, width, decimals, second);
+    }
+    else
+    {
+        /* return both the time and date */
+        sprintf(datestr, "%.4d-%.2d-%.2dT%.2d:%.2d:%0*.*f",
+            year, month, day, hour, minute, width, decimals, second);
+    }
+    return(*status);
+}
+/*-----------------------------------------------------------------*/
+int ffs2tm(char *datestr,     /* I - date string: "YYYY-MM-DD"    */
+                              /*     or "YYYY-MM-DDThh:mm:ss.ddd" */
+                              /*     or "dd/mm/yy"                */
+           int *year,         /* O - year (0 - 9999)              */
+           int *month,        /* O - month (1 - 12)               */
+           int *day,          /* O - day (1 - 31)                 */
+           int *hour,          /* I - hour (0 - 23)                */
+           int *minute,        /* I - minute (0 - 59)              */
+           double *second,     /* I - second (0. - 60.9999999)     */
+           int   *status)     /* IO - error status                */
+/*
+  Parse a date character string into date and time values
+*/
+{
+    int slen;
+    char errmsg[81];
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (year)
+       *year   = 0;
+    if (month)
+       *month  = 0;
+    if (day)
+       *day    = 0;
+    if (hour)
+       *hour   = 0;
+    if (minute)
+       *minute = 0;
+    if (second)
+       *second = 0.;
+
+    if (!datestr)
+    {
+        ffpmsg("error: null input date string (ffs2tm)");
+        return(*status = BAD_DATE);   /* Null datestr pointer ??? */
+    }
+
+    if (datestr[2] == '/' || datestr[4] == '-')
+    {
+        /*  Parse the year, month, and date */
+        if (ffs2dt(datestr, year, month, day, status) > 0)
+            return(*status);
+
+        slen = strlen(datestr);
+        if (slen == 8 || slen == 10)
+            return(*status);               /* OK, no time fields */
+        else if (slen < 19) 
+        {
+            ffpmsg("input date string has illegal format:");
+            ffpmsg(datestr);
+            return(*status = BAD_DATE);
+        }
+
+        else if (datestr[10] == 'T' && datestr[13] == ':' && datestr[16] == ':')
+        {
+          if (isdigit((int) datestr[11]) && isdigit((int) datestr[12])
+           && isdigit((int) datestr[14]) && isdigit((int) datestr[15])
+           && isdigit((int) datestr[17]) && isdigit((int) datestr[18]) )
+            {
+                if (slen > 19 && datestr[19] != '.')
+                {
+                  ffpmsg("input date string has illegal format:");
+                  ffpmsg(datestr);
+                  return(*status = BAD_DATE);
+                }
+
+                /* this is a new format string: "yyyy-mm-ddThh:mm:ss.dddd" */
+                if (hour)
+                    *hour   = atoi(&datestr[11]);
+
+                if (minute)
+                    *minute = atoi(&datestr[14]);
+
+                if (second)
+                    *second = atof(&datestr[17]);
+            }
+            else
+            {
+                  ffpmsg("input date string has illegal format:");
+                  ffpmsg(datestr);
+                  return(*status = BAD_DATE);
+            }
+
+        }
+    }
+    else   /* no date fields */
+    {
+        if (datestr[2] == ':' && datestr[5] == ':')   /* time string */
+        {
+            if (isdigit((int) datestr[0]) && isdigit((int) datestr[1])
+             && isdigit((int) datestr[3]) && isdigit((int) datestr[4])
+             && isdigit((int) datestr[6]) && isdigit((int) datestr[7]) )
+            {
+                 /* this is a time string: "hh:mm:ss.dddd" */
+                 if (hour)
+                    *hour   = atoi(&datestr[0]);
+
+                 if (minute)
+                    *minute = atoi(&datestr[3]);
+
+                if (second)
+                    *second = atof(&datestr[6]);
+            }
+            else
+            {
+                  ffpmsg("input date string has illegal format:");
+                  ffpmsg(datestr);
+                  return(*status = BAD_DATE);
+            }
+
+        }
+        else
+        {
+                  ffpmsg("input date string has illegal format:");
+                  ffpmsg(datestr);
+                  return(*status = BAD_DATE);
+        }
+
+    }
+
+    if (hour)
+       if (*hour < 0 || *hour > 23)
+       {
+          sprintf(errmsg, 
+          "hour value is out of range 0 - 23: %d (ffs2tm)", *hour);
+          ffpmsg(errmsg);
+          return(*status = BAD_DATE);
+       }
+
+    if (minute)
+       if (*minute < 0 || *minute > 59)
+       {
+          sprintf(errmsg, 
+          "minute value is out of range 0 - 59: %d (ffs2tm)", *minute);
+          ffpmsg(errmsg);
+          return(*status = BAD_DATE);
+       }
+
+    if (second)
+       if (*second < 0 || *second >= 61.)
+       {
+          sprintf(errmsg, 
+          "second value is out of range 0 - 60.9999: %f (ffs2tm)", *second);
+          ffpmsg(errmsg);
+          return(*status = BAD_DATE);
+       }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgsdt( int *day, int *month, int *year, int *status )
+{  
+/*
+      This routine is included for backward compatibility
+            with the Fortran FITSIO library.
+
+   ffgsdt : Get current System DaTe (GMT if available)
+
+      Return integer values of the day, month, and year
+
+         Function parameters:
+            day      Day of the month
+            month    Numerical month (1=Jan, etc.)
+            year     Year (1999, 2000, etc.)
+            status   output error status
+
+*/
+   time_t now;
+   struct tm *date;
+
+   now = time( NULL );
+   date = gmtime(&now);         /* get GMT (= UTC) time */
+
+   if (!date)                  /* GMT not available on this machine */
+   {
+       date = localtime(&now); 
+   }
+
+   *day = date->tm_mday;
+   *month = date->tm_mon + 1;
+   *year = date->tm_year + 1900;  /* tm_year is defined as years since 1900 */
+   return( *status );
+}
+/*--------------------------------------------------------------------------*/
+int ffpkns( fitsfile *fptr,     /* I - FITS file pointer                    */
+            const char *keyroot,      /* I - root name of keywords to write       */
+            int  nstart,        /* I - starting index number                */
+            int  nkey,          /* I - number of keywords to write          */
+            char *value[],      /* I - array of pointers to keyword values  */
+            char *comm[],       /* I - array of pointers to keyword comment */
+            int  *status)       /* IO - error status                        */
+/*
+  Write (put) an indexed array of keywords with index numbers between
+  NSTART and (NSTART + NKEY -1) inclusive.  Writes string keywords.
+  The value strings will be truncated at 68 characters, and the HEASARC
+  long string keyword convention is not supported by this routine.
+*/
+{
+    char keyname[FLEN_KEYWORD], tcomment[FLEN_COMMENT];
+    int ii, jj, repeat, len;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    /* check if first comment string is to be repeated for all the keywords */
+    /* by looking to see if the last non-blank character is a '&' char      */
+
+    repeat = 0;
+
+    if (comm)
+    {
+      len = strlen(comm[0]);
+
+      while (len > 0  && comm[0][len - 1] == ' ')
+        len--;                               /* ignore trailing blanks */
+
+      if (comm[0][len - 1] == '&')
+      {
+        len = minvalue(len, FLEN_COMMENT);
+        tcomment[0] = '\0';
+        strncat(tcomment, comm[0], len-1); /* don't copy the final '&' char */
+        repeat = 1;
+      }
+    }
+    else
+    {
+      repeat = 1;
+      tcomment[0] = '\0';
+    }
+
+    for (ii=0, jj=nstart; ii < nkey; ii++, jj++)
+    {
+        ffkeyn(keyroot, jj, keyname, status);
+        if (repeat)
+            ffpkys(fptr, keyname, value[ii], tcomment, status);
+        else
+            ffpkys(fptr, keyname, value[ii], comm[ii], status);
+
+        if (*status > 0)
+            return(*status);
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpknl( fitsfile *fptr,     /* I - FITS file pointer                    */
+            const char *keyroot,      /* I - root name of keywords to write       */
+            int  nstart,        /* I - starting index number                */
+            int  nkey,          /* I - number of keywords to write          */
+            int  *value,        /* I - array of keyword values              */
+            char *comm[],       /* I - array of pointers to keyword comment */
+            int  *status)       /* IO - error status                        */
+/*
+  Write (put) an indexed array of keywords with index numbers between
+  NSTART and (NSTART + NKEY -1) inclusive.  Writes logical keywords
+  Values equal to zero will be written as a False FITS keyword value; any
+  other non-zero value will result in a True FITS keyword.
+*/
+{
+    char keyname[FLEN_KEYWORD], tcomment[FLEN_COMMENT];
+    int ii, jj, repeat, len;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    /* check if first comment string is to be repeated for all the keywords */
+    /* by looking to see if the last non-blank character is a '&' char      */
+
+    repeat = 0;
+    if (comm)
+    {
+      len = strlen(comm[0]);
+
+      while (len > 0  && comm[0][len - 1] == ' ')
+        len--;                               /* ignore trailing blanks */
+
+      if (comm[0][len - 1] == '&')
+      {
+        len = minvalue(len, FLEN_COMMENT);
+        tcomment[0] = '\0';
+        strncat(tcomment, comm[0], len-1); /* don't copy the final '&' char */
+        repeat = 1;
+      }
+    }
+    else
+    {
+      repeat = 1;
+      tcomment[0] = '\0';
+    }
+
+
+    for (ii=0, jj=nstart; ii < nkey; ii++, jj++)
+    {
+        ffkeyn(keyroot, jj, keyname, status);
+
+        if (repeat)
+            ffpkyl(fptr, keyname, value[ii], tcomment, status);
+        else
+            ffpkyl(fptr, keyname, value[ii], comm[ii], status);
+
+        if (*status > 0)
+            return(*status);
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpknj( fitsfile *fptr,     /* I - FITS file pointer                    */
+            const char *keyroot,      /* I - root name of keywords to write       */
+            int  nstart,        /* I - starting index number                */
+            int  nkey,          /* I - number of keywords to write          */
+            long *value,        /* I - array of keyword values              */
+            char *comm[],       /* I - array of pointers to keyword comment */
+            int  *status)       /* IO - error status                        */
+/*
+  Write (put) an indexed array of keywords with index numbers between
+  NSTART and (NSTART + NKEY -1) inclusive.  Write integer keywords
+*/
+{
+    char keyname[FLEN_KEYWORD], tcomment[FLEN_COMMENT];
+    int ii, jj, repeat, len;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    /* check if first comment string is to be repeated for all the keywords */
+    /* by looking to see if the last non-blank character is a '&' char      */
+
+    repeat = 0;
+
+    if (comm)
+    {
+      len = strlen(comm[0]);
+
+      while (len > 0  && comm[0][len - 1] == ' ')
+        len--;                               /* ignore trailing blanks */
+
+      if (comm[0][len - 1] == '&')
+      {
+        len = minvalue(len, FLEN_COMMENT);
+        tcomment[0] = '\0';
+        strncat(tcomment, comm[0], len-1); /* don't copy the final '&' char */
+        repeat = 1;
+      }
+    }
+    else
+    {
+      repeat = 1;
+      tcomment[0] = '\0';
+    }
+
+    for (ii=0, jj=nstart; ii < nkey; ii++, jj++)
+    {
+        ffkeyn(keyroot, jj, keyname, status);
+        if (repeat)
+            ffpkyj(fptr, keyname, value[ii], tcomment, status);
+        else
+            ffpkyj(fptr, keyname, value[ii], comm[ii], status);
+
+        if (*status > 0)
+            return(*status);
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpknjj( fitsfile *fptr,    /* I - FITS file pointer                    */
+            const char *keyroot,      /* I - root name of keywords to write       */
+            int  nstart,        /* I - starting index number                */
+            int  nkey,          /* I - number of keywords to write          */
+            LONGLONG *value,    /* I - array of keyword values              */
+            char *comm[],       /* I - array of pointers to keyword comment */
+            int  *status)       /* IO - error status                        */
+/*
+  Write (put) an indexed array of keywords with index numbers between
+  NSTART and (NSTART + NKEY -1) inclusive.  Write integer keywords
+*/
+{
+    char keyname[FLEN_KEYWORD], tcomment[FLEN_COMMENT];
+    int ii, jj, repeat, len;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    /* check if first comment string is to be repeated for all the keywords */
+    /* by looking to see if the last non-blank character is a '&' char      */
+
+    repeat = 0;
+
+    if (comm)
+    {
+      len = strlen(comm[0]);
+
+      while (len > 0  && comm[0][len - 1] == ' ')
+        len--;                               /* ignore trailing blanks */
+
+      if (comm[0][len - 1] == '&')
+      {
+        len = minvalue(len, FLEN_COMMENT);
+        tcomment[0] = '\0';
+        strncat(tcomment, comm[0], len-1); /* don't copy the final '&' char */
+        repeat = 1;
+      }
+    }
+    else
+    {
+      repeat = 1;
+      tcomment[0] = '\0';
+    }
+
+    for (ii=0, jj=nstart; ii < nkey; ii++, jj++)
+    {
+        ffkeyn(keyroot, jj, keyname, status);
+        if (repeat)
+            ffpkyj(fptr, keyname, value[ii], tcomment, status);
+        else
+            ffpkyj(fptr, keyname, value[ii], comm[ii], status);
+
+        if (*status > 0)
+            return(*status);
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpknf( fitsfile *fptr,     /* I - FITS file pointer                    */
+            const char *keyroot,      /* I - root name of keywords to write       */
+            int  nstart,        /* I - starting index number                */
+            int  nkey,          /* I - number of keywords to write          */
+            float *value,       /* I - array of keyword values              */
+            int decim,          /* I - number of decimals to display        */
+            char *comm[],       /* I - array of pointers to keyword comment */
+            int  *status)       /* IO - error status                        */
+/*
+  Write (put) an indexed array of keywords with index numbers between
+  NSTART and (NSTART + NKEY -1) inclusive.  Writes fixed float values.
+*/
+{
+    char keyname[FLEN_KEYWORD], tcomment[FLEN_COMMENT];
+    int ii, jj, repeat, len;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    /* check if first comment string is to be repeated for all the keywords */
+    /* by looking to see if the last non-blank character is a '&' char      */
+
+    repeat = 0;
+
+    if (comm)
+    {
+      len = strlen(comm[0]);
+
+      while (len > 0  && comm[0][len - 1] == ' ')
+        len--;                               /* ignore trailing blanks */
+
+      if (comm[0][len - 1] == '&')
+      {
+        len = minvalue(len, FLEN_COMMENT);
+        tcomment[0] = '\0';
+        strncat(tcomment, comm[0], len-1); /* don't copy the final '&' char */
+        repeat = 1;
+      }
+    }
+    else
+    {
+      repeat = 1;
+      tcomment[0] = '\0';
+    }
+
+    for (ii=0, jj=nstart; ii < nkey; ii++, jj++)
+    {
+        ffkeyn(keyroot, jj, keyname, status);
+        if (repeat)
+            ffpkyf(fptr, keyname, value[ii], decim, tcomment, status);
+        else
+            ffpkyf(fptr, keyname, value[ii], decim, comm[ii], status);
+
+        if (*status > 0)
+            return(*status);
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpkne( fitsfile *fptr,     /* I - FITS file pointer                    */
+            const char *keyroot,      /* I - root name of keywords to write       */
+            int  nstart,        /* I - starting index number                */
+            int  nkey,          /* I - number of keywords to write          */
+            float *value,       /* I - array of keyword values              */
+            int decim,          /* I - number of decimals to display        */
+            char *comm[],       /* I - array of pointers to keyword comment */
+            int  *status)       /* IO - error status                        */
+/*
+  Write (put) an indexed array of keywords with index numbers between
+  NSTART and (NSTART + NKEY -1) inclusive.  Writes exponential float values.
+*/
+{
+    char keyname[FLEN_KEYWORD], tcomment[FLEN_COMMENT];
+    int ii, jj, repeat, len;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    /* check if first comment string is to be repeated for all the keywords */
+    /* by looking to see if the last non-blank character is a '&' char      */
+
+    repeat = 0;
+
+    if (comm)
+    {
+      len = strlen(comm[0]);
+
+      while (len > 0  && comm[0][len - 1] == ' ')
+        len--;                               /* ignore trailing blanks */
+
+      if (comm[0][len - 1] == '&')
+      {
+        len = minvalue(len, FLEN_COMMENT);
+        tcomment[0] = '\0';
+        strncat(tcomment, comm[0], len-1); /* don't copy the final '&' char */
+        repeat = 1;
+      }
+    }
+    else
+    {
+      repeat = 1;
+      tcomment[0] = '\0';
+    }
+
+    for (ii=0, jj=nstart; ii < nkey; ii++, jj++)
+    {
+        ffkeyn(keyroot, jj, keyname, status);
+        if (repeat)
+            ffpkye(fptr, keyname, value[ii], decim, tcomment, status);
+        else
+            ffpkye(fptr, keyname, value[ii], decim, comm[ii], status);
+
+        if (*status > 0)
+            return(*status);
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpkng( fitsfile *fptr,     /* I - FITS file pointer                    */
+            const char *keyroot,      /* I - root name of keywords to write       */
+            int  nstart,        /* I - starting index number                */
+            int  nkey,          /* I - number of keywords to write          */
+            double *value,      /* I - array of keyword values              */
+            int decim,          /* I - number of decimals to display        */
+            char *comm[],       /* I - array of pointers to keyword comment */
+            int  *status)       /* IO - error status                        */
+/*
+  Write (put) an indexed array of keywords with index numbers between
+  NSTART and (NSTART + NKEY -1) inclusive.  Writes fixed double values.
+*/
+{
+    char keyname[FLEN_KEYWORD], tcomment[FLEN_COMMENT];
+    int ii, jj, repeat, len;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    /* check if first comment string is to be repeated for all the keywords */
+    /* by looking to see if the last non-blank character is a '&' char      */
+
+    repeat = 0;
+
+    if (comm)
+    {
+      len = strlen(comm[0]);
+
+      while (len > 0  && comm[0][len - 1] == ' ')
+        len--;                               /* ignore trailing blanks */
+
+      if (comm[0][len - 1] == '&')
+      {
+        len = minvalue(len, FLEN_COMMENT);
+        tcomment[0] = '\0';
+        strncat(tcomment, comm[0], len-1); /* don't copy the final '&' char */
+        repeat = 1;
+      }
+    }
+    else
+    {
+      repeat = 1;
+      tcomment[0] = '\0';
+    }
+
+    for (ii=0, jj=nstart; ii < nkey; ii++, jj++)
+    {
+        ffkeyn(keyroot, jj, keyname, status);
+        if (repeat)
+            ffpkyg(fptr, keyname, value[ii], decim, tcomment, status);
+        else
+            ffpkyg(fptr, keyname, value[ii], decim, comm[ii], status);
+
+        if (*status > 0)
+            return(*status);
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpknd( fitsfile *fptr,     /* I - FITS file pointer                    */
+            const char *keyroot,      /* I - root name of keywords to write       */
+            int  nstart,        /* I - starting index number                */
+            int  nkey,          /* I - number of keywords to write          */
+            double *value,      /* I - array of keyword values              */
+            int decim,          /* I - number of decimals to display        */
+            char *comm[],       /* I - array of pointers to keyword comment */
+            int  *status)       /* IO - error status                        */
+/*
+  Write (put) an indexed array of keywords with index numbers between
+  NSTART and (NSTART + NKEY -1) inclusive.  Writes exponential double values.
+*/
+{
+    char keyname[FLEN_KEYWORD], tcomment[FLEN_COMMENT];
+    int ii, jj, repeat, len;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    /* check if first comment string is to be repeated for all the keywords */
+    /* by looking to see if the last non-blank character is a '&' char      */
+
+    repeat = 0;
+
+    if (comm)
+    {
+      len = strlen(comm[0]);
+
+      while (len > 0  && comm[0][len - 1] == ' ')
+        len--;                               /* ignore trailing blanks */
+
+      if (comm[0][len - 1] == '&')
+      {
+        len = minvalue(len, FLEN_COMMENT);
+        tcomment[0] = '\0';
+        strncat(tcomment, comm[0], len-1); /* don't copy the final '&' char */
+        repeat = 1;
+      }
+    }
+    else
+    {
+      repeat = 1;
+      tcomment[0] = '\0';
+    }
+
+    for (ii=0, jj=nstart; ii < nkey; ii++, jj++)
+    {
+        ffkeyn(keyroot, jj, keyname, status);
+        if (repeat)
+            ffpkyd(fptr, keyname, value[ii], decim, tcomment, status);
+        else
+            ffpkyd(fptr, keyname, value[ii], decim, comm[ii], status);
+
+        if (*status > 0)
+            return(*status);
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffptdm( fitsfile *fptr, /* I - FITS file pointer                        */
+            int colnum,     /* I - column number                            */
+            int naxis,      /* I - number of axes in the data array         */
+            long naxes[],   /* I - length of each data axis                 */
+            int *status)    /* IO - error status                            */
+/*
+  write the TDIMnnn keyword describing the dimensionality of a column
+*/
+{
+    char keyname[FLEN_KEYWORD], tdimstr[FLEN_VALUE], comm[FLEN_COMMENT];
+    char value[80], message[81];
+    int ii;
+    long totalpix = 1, repeat;
+    tcolumn *colptr;
+
+    if (*status > 0)
+        return(*status);
+
+    if (colnum < 1 || colnum > 999)
+    {
+        ffpmsg("column number is out of range 1 - 999 (ffptdm)");
+        return(*status = BAD_COL_NUM);
+    }
+
+    if (naxis < 1)
+    {
+        ffpmsg("naxis is less than 1 (ffptdm)");
+        return(*status = BAD_DIMEN);
+    }
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+        if ( ffrdef(fptr, status) > 0)               /* rescan header */
+            return(*status);
+
+    if ( (fptr->Fptr)->hdutype != BINARY_TBL)
+    {
+       ffpmsg(
+    "Error: The TDIMn keyword is only allowed in BINTABLE extensions (ffptdm)");
+       return(*status = NOT_BTABLE);
+    }
+
+    strcpy(tdimstr, "(");            /* start constructing the TDIM value */   
+
+    for (ii = 0; ii < naxis; ii++)
+    {
+        if (ii > 0)
+            strcat(tdimstr, ",");   /* append the comma separator */
+
+        if (naxes[ii] < 0)
+        {
+            ffpmsg("one or more TDIM values are less than 0 (ffptdm)");
+            return(*status = BAD_TDIM);
+        }
+
+        sprintf(value, "%ld", naxes[ii]);
+        strcat(tdimstr, value);     /* append the axis size */
+
+        totalpix *= naxes[ii];
+    }
+
+    colptr = (fptr->Fptr)->tableptr;  /* point to first column structure */
+    colptr += (colnum - 1);      /* point to the specified column number */
+
+    if ((long) colptr->trepeat != totalpix)
+    {
+      /* There is an apparent inconsistency between TDIMn and TFORMn. */
+      /* The colptr->trepeat value may be out of date, so re-read     */
+      /* the TFORMn keyword to be sure.                               */
+
+      ffkeyn("TFORM", colnum, keyname, status);   /* construct TFORMn name  */
+      ffgkys(fptr, keyname, value, NULL, status); /* read TFORMn keyword    */
+      ffbnfm(value, NULL, &repeat, NULL, status); /* parse the repeat count */
+
+      if (*status > 0 || repeat != totalpix)
+      {
+        sprintf(message,
+        "column vector length, %ld, does not equal TDIMn array size, %ld",
+        (long) colptr->trepeat, totalpix);
+        ffpmsg(message);
+        return(*status = BAD_TDIM);
+      }
+    }
+
+    strcat(tdimstr, ")" );            /* append the closing parenthesis */
+
+    strcpy(comm, "size of the multidimensional array");
+    ffkeyn("TDIM", colnum, keyname, status);      /* construct TDIMn name */
+    ffpkys(fptr, keyname, tdimstr, comm, status);  /* write the keyword */
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffptdmll( fitsfile *fptr, /* I - FITS file pointer                      */
+            int colnum,     /* I - column number                            */
+            int naxis,      /* I - number of axes in the data array         */
+            LONGLONG naxes[], /* I - length of each data axis               */
+            int *status)    /* IO - error status                            */
+/*
+  write the TDIMnnn keyword describing the dimensionality of a column
+*/
+{
+    char keyname[FLEN_KEYWORD], tdimstr[FLEN_VALUE], comm[FLEN_COMMENT];
+    char value[80], message[81];
+    int ii;
+    LONGLONG totalpix = 1, repeat;
+    tcolumn *colptr;
+
+    if (*status > 0)
+        return(*status);
+
+    if (colnum < 1 || colnum > 999)
+    {
+        ffpmsg("column number is out of range 1 - 999 (ffptdm)");
+        return(*status = BAD_COL_NUM);
+    }
+
+    if (naxis < 1)
+    {
+        ffpmsg("naxis is less than 1 (ffptdm)");
+        return(*status = BAD_DIMEN);
+    }
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+    else if ((fptr->Fptr)->datastart == DATA_UNDEFINED)
+        if ( ffrdef(fptr, status) > 0)               /* rescan header */
+            return(*status);
+
+    if ( (fptr->Fptr)->hdutype != BINARY_TBL)
+    {
+       ffpmsg(
+    "Error: The TDIMn keyword is only allowed in BINTABLE extensions (ffptdm)");
+       return(*status = NOT_BTABLE);
+    }
+
+    strcpy(tdimstr, "(");            /* start constructing the TDIM value */   
+
+    for (ii = 0; ii < naxis; ii++)
+    {
+        if (ii > 0)
+            strcat(tdimstr, ",");   /* append the comma separator */
+
+        if (naxes[ii] < 0)
+        {
+            ffpmsg("one or more TDIM values are less than 0 (ffptdm)");
+            return(*status = BAD_TDIM);
+        }
+
+        /* cast to double because the 64-bit int conversion character in */
+        /* sprintf is platform dependent ( %lld, %ld, %I64d )            */
+
+        sprintf(value, "%.0f", (double) naxes[ii]);
+
+        strcat(tdimstr, value);     /* append the axis size */
+
+        totalpix *= naxes[ii];
+    }
+
+    colptr = (fptr->Fptr)->tableptr;  /* point to first column structure */
+    colptr += (colnum - 1);      /* point to the specified column number */
+
+    if ( colptr->trepeat != totalpix)
+    {
+      /* There is an apparent inconsistency between TDIMn and TFORMn. */
+      /* The colptr->trepeat value may be out of date, so re-read     */
+      /* the TFORMn keyword to be sure.                               */
+
+      ffkeyn("TFORM", colnum, keyname, status);   /* construct TFORMn name  */
+      ffgkys(fptr, keyname, value, NULL, status); /* read TFORMn keyword    */
+      ffbnfmll(value, NULL, &repeat, NULL, status); /* parse the repeat count */
+
+      if (*status > 0 || repeat != totalpix)
+      {
+        sprintf(message,
+        "column vector length, %.0f, does not equal TDIMn array size, %.0f",
+        (double) (colptr->trepeat), (double) totalpix);
+        ffpmsg(message);
+        return(*status = BAD_TDIM);
+      }
+    }
+
+    strcat(tdimstr, ")" );            /* append the closing parenthesis */
+
+    strcpy(comm, "size of the multidimensional array");
+    ffkeyn("TDIM", colnum, keyname, status);      /* construct TDIMn name */
+    ffpkys(fptr, keyname, tdimstr, comm, status);  /* write the keyword */
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffphps( fitsfile *fptr, /* I - FITS file pointer                        */
+            int bitpix,     /* I - number of bits per data value pixel      */
+            int naxis,      /* I - number of axes in the data array         */
+            long naxes[],   /* I - length of each data axis                 */
+            int *status)    /* IO - error status                            */
+/*
+  write STANDARD set of required primary header keywords
+*/
+{
+    int simple = 1;     /* does file conform to FITS standard? 1/0  */
+    long pcount = 0;    /* number of group parameters (usually 0)   */
+    long gcount = 1;    /* number of random groups (usually 1 or 0) */
+    int extend = 1;     /* may FITS file have extensions?           */
+
+    ffphpr(fptr, simple, bitpix, naxis, naxes, pcount, gcount, extend, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffphpsll( fitsfile *fptr, /* I - FITS file pointer                        */
+            int bitpix,     /* I - number of bits per data value pixel      */
+            int naxis,      /* I - number of axes in the data array         */
+            LONGLONG naxes[],   /* I - length of each data axis                 */
+            int *status)    /* IO - error status                            */
+/*
+  write STANDARD set of required primary header keywords
+*/
+{
+    int simple = 1;     /* does file conform to FITS standard? 1/0  */
+    LONGLONG pcount = 0;    /* number of group parameters (usually 0)   */
+    LONGLONG gcount = 1;    /* number of random groups (usually 1 or 0) */
+    int extend = 1;     /* may FITS file have extensions?           */
+
+    ffphprll(fptr, simple, bitpix, naxis, naxes, pcount, gcount, extend, status);
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffphpr( fitsfile *fptr, /* I - FITS file pointer                        */
+            int simple,     /* I - does file conform to FITS standard? 1/0  */
+            int bitpix,     /* I - number of bits per data value pixel      */
+            int naxis,      /* I - number of axes in the data array         */
+            long naxes[],   /* I - length of each data axis                 */
+            LONGLONG pcount, /* I - number of group parameters (usually 0)   */
+            LONGLONG gcount, /* I - number of random groups (usually 1 or 0) */
+            int extend,     /* I - may FITS file have extensions?           */
+            int *status)    /* IO - error status                            */
+/*
+  write required primary header keywords
+*/
+{
+    int ii;
+    LONGLONG naxesll[20];
+   
+    for (ii = 0; (ii < naxis) && (ii < 20); ii++)
+       naxesll[ii] = naxes[ii];
+
+    ffphprll(fptr, simple, bitpix, naxis, naxesll, pcount, gcount,
+             extend, status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffphprll( fitsfile *fptr, /* I - FITS file pointer                        */
+            int simple,     /* I - does file conform to FITS standard? 1/0  */
+            int bitpix,     /* I - number of bits per data value pixel      */
+            int naxis,      /* I - number of axes in the data array         */
+            LONGLONG naxes[], /* I - length of each data axis                 */
+            LONGLONG pcount,  /* I - number of group parameters (usually 0)   */
+            LONGLONG gcount,  /* I - number of random groups (usually 1 or 0) */
+            int extend,     /* I - may FITS file have extensions?           */
+            int *status)    /* IO - error status                            */
+/*
+  write required primary header keywords
+*/
+{
+    int ii;
+    long longbitpix, tnaxes[20];
+    char name[FLEN_KEYWORD], comm[FLEN_COMMENT], message[FLEN_ERRMSG];
+
+    if (*status > 0)
+        return(*status);
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    if ((fptr->Fptr)->headend != (fptr->Fptr)->headstart[(fptr->Fptr)->curhdu] )
+        return(*status = HEADER_NOT_EMPTY);
+
+    if (naxis != 0)   /* never try to compress a null image */
+    {
+      if ( (fptr->Fptr)->request_compress_type )
+      {
+      
+       for (ii = 0; ii < naxis; ii++)
+           tnaxes[ii] = (long) naxes[ii];
+	   
+        /* write header for a compressed image */
+        imcomp_init_table(fptr, bitpix, naxis, tnaxes, 1, status);
+        return(*status);
+      }
+    }  
+
+    if ((fptr->Fptr)->curhdu == 0)
+    {                /* write primary array header */
+        if (simple)
+            strcpy(comm, "file does conform to FITS standard");
+        else
+            strcpy(comm, "file does not conform to FITS standard");
+
+        ffpkyl(fptr, "SIMPLE", simple, comm, status);
+    }
+    else
+    {               /* write IMAGE extension header */
+        strcpy(comm, "IMAGE extension");
+        ffpkys(fptr, "XTENSION", "IMAGE", comm, status);
+    }
+
+    longbitpix = bitpix;
+
+    /* test for the 3 special cases that represent unsigned integers */
+    if (longbitpix == USHORT_IMG)
+        longbitpix = SHORT_IMG;
+    else if (longbitpix == ULONG_IMG)
+        longbitpix = LONG_IMG;
+    else if (longbitpix == SBYTE_IMG)
+        longbitpix = BYTE_IMG;
+
+    if (longbitpix != BYTE_IMG && longbitpix != SHORT_IMG && 
+        longbitpix != LONG_IMG && longbitpix != LONGLONG_IMG &&
+        longbitpix != FLOAT_IMG && longbitpix != DOUBLE_IMG)
+    {
+        sprintf(message,
+        "Illegal value for BITPIX keyword: %d", bitpix);
+        ffpmsg(message);
+        return(*status = BAD_BITPIX);
+    }
+
+    strcpy(comm, "number of bits per data pixel");
+    if (ffpkyj(fptr, "BITPIX", longbitpix, comm, status) > 0)
+        return(*status);
+
+    if (naxis < 0 || naxis > 999)
+    {
+        sprintf(message,
+        "Illegal value for NAXIS keyword: %d", naxis);
+        ffpmsg(message);
+        return(*status = BAD_NAXIS);
+    }
+
+    strcpy(comm, "number of data axes");
+    ffpkyj(fptr, "NAXIS", naxis, comm, status);
+
+    strcpy(comm, "length of data axis ");
+    for (ii = 0; ii < naxis; ii++)
+    {
+        if (naxes[ii] < 0)
+        {
+            sprintf(message,
+            "Illegal negative value for NAXIS%d keyword: %.0f", ii + 1, (double) (naxes[ii]));
+            ffpmsg(message);
+            return(*status = BAD_NAXES);
+        }
+
+        sprintf(&comm[20], "%d", ii + 1);
+        ffkeyn("NAXIS", ii + 1, name, status);
+        ffpkyj(fptr, name, naxes[ii], comm, status);
+    }
+
+    if ((fptr->Fptr)->curhdu == 0)  /* the primary array */
+    {
+        if (extend)
+        {
+            /* only write EXTEND keyword if value = true */
+            strcpy(comm, "FITS dataset may contain extensions");
+            ffpkyl(fptr, "EXTEND", extend, comm, status);
+        }
+
+        if (pcount < 0)
+        {
+            ffpmsg("pcount value is less than 0");
+            return(*status = BAD_PCOUNT);
+        }
+
+        else if (gcount < 1)
+        {
+            ffpmsg("gcount value is less than 1");
+            return(*status = BAD_GCOUNT);
+        }
+
+        else if (pcount > 0 || gcount > 1)
+        {
+            /* only write these keyword if non-standard values */
+            strcpy(comm, "random group records are present");
+            ffpkyl(fptr, "GROUPS", 1, comm, status);
+
+            strcpy(comm, "number of random group parameters");
+            ffpkyj(fptr, "PCOUNT", pcount, comm, status);
+  
+            strcpy(comm, "number of random groups");
+            ffpkyj(fptr, "GCOUNT", gcount, comm, status);
+        }
+
+      /* write standard block of self-documentating comments */
+      ffprec(fptr,
+      "COMMENT   FITS (Flexible Image Transport System) format is defined in 'Astronomy",
+      status);
+      ffprec(fptr,
+      "COMMENT   and Astrophysics', volume 376, page 359; bibcode: 2001A&A...376..359H",
+      status);
+    }
+
+    else  /* an IMAGE extension */
+
+    {   /* image extension; cannot have random groups */
+        if (pcount != 0)
+        {
+            ffpmsg("image extensions must have pcount = 0");
+            *status = BAD_PCOUNT;
+        }
+
+        else if (gcount != 1)
+        {
+            ffpmsg("image extensions must have gcount = 1");
+            *status = BAD_GCOUNT;
+        }
+
+        else
+        {
+            strcpy(comm, "required keyword; must = 0");
+            ffpkyj(fptr, "PCOUNT", 0, comm, status);
+  
+            strcpy(comm, "required keyword; must = 1");
+            ffpkyj(fptr, "GCOUNT", 1, comm, status);
+        }
+    }
+
+    /* Write the BSCALE and BZERO keywords, if an unsigned integer image */
+    if (bitpix == USHORT_IMG)
+    {
+        strcpy(comm, "offset data range to that of unsigned short");
+        ffpkyg(fptr, "BZERO", 32768., 0, comm, status);
+        strcpy(comm, "default scaling factor");
+        ffpkyg(fptr, "BSCALE", 1.0, 0, comm, status);
+    }
+    else if (bitpix == ULONG_IMG)
+    {
+        strcpy(comm, "offset data range to that of unsigned long");
+        ffpkyg(fptr, "BZERO", 2147483648., 0, comm, status);
+        strcpy(comm, "default scaling factor");
+        ffpkyg(fptr, "BSCALE", 1.0, 0, comm, status);
+    }
+    else if (bitpix == SBYTE_IMG)
+    {
+        strcpy(comm, "offset data range to that of signed byte");
+        ffpkyg(fptr, "BZERO", -128., 0, comm, status);
+        strcpy(comm, "default scaling factor");
+        ffpkyg(fptr, "BSCALE", 1.0, 0, comm, status);
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffphtb(fitsfile *fptr,  /* I - FITS file pointer                        */
+           LONGLONG naxis1,     /* I - width of row in the table                */
+           LONGLONG naxis2,     /* I - number of rows in the table              */
+           int tfields,     /* I - number of columns in the table           */
+           char **ttype,    /* I - name of each column                      */
+           long *tbcol,     /* I - byte offset in row to each column        */
+           char **tform,    /* I - value of TFORMn keyword for each column  */
+           char **tunit,    /* I - value of TUNITn keyword for each column  */
+           const char *extnmx,   /* I - value of EXTNAME keyword, if any         */
+           int *status)     /* IO - error status                            */
+/*
+  Put required Header keywords into the ASCII TaBle:
+*/
+{
+    int ii, ncols, gotmem = 0;
+    long rowlen; /* must be 'long' because it is passed to ffgabc */
+    char tfmt[30], name[FLEN_KEYWORD], comm[FLEN_COMMENT], extnm[FLEN_VALUE];
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    if (*status > 0)
+        return(*status);
+    else if ((fptr->Fptr)->headend != (fptr->Fptr)->headstart[(fptr->Fptr)->curhdu] )
+        return(*status = HEADER_NOT_EMPTY);
+    else if (naxis1 < 0)
+        return(*status = NEG_WIDTH);
+    else if (naxis2 < 0)
+        return(*status = NEG_ROWS);
+    else if (tfields < 0 || tfields > 999)
+        return(*status = BAD_TFIELDS);
+    
+    extnm[0] = '\0';
+    if (extnmx)
+        strncat(extnm, extnmx, FLEN_VALUE-1);
+
+    rowlen = (long) naxis1;
+
+    if (!tbcol || !tbcol[0] || (!naxis1 && tfields)) /* spacing not defined? */
+    {
+      /* allocate mem for tbcol; malloc can have problems allocating small */
+      /* arrays, so allocate at least 20 bytes */
+
+      ncols = maxvalue(5, tfields);
+      tbcol = (long *) calloc(ncols, sizeof(long));
+
+      if (tbcol)
+      {
+        gotmem = 1;
+
+        /* calculate width of a row and starting position of each column. */
+        /* Each column will be separated by 1 blank space */
+        ffgabc(tfields, tform, 1, &rowlen, tbcol, status);
+      }
+    }
+    ffpkys(fptr, "XTENSION", "TABLE", "ASCII table extension", status);
+    ffpkyj(fptr, "BITPIX", 8, "8-bit ASCII characters", status);
+    ffpkyj(fptr, "NAXIS", 2, "2-dimensional ASCII table", status);
+    ffpkyj(fptr, "NAXIS1", rowlen, "width of table in characters", status);
+    ffpkyj(fptr, "NAXIS2", naxis2, "number of rows in table", status);
+    ffpkyj(fptr, "PCOUNT", 0, "no group parameters (required keyword)", status);
+    ffpkyj(fptr, "GCOUNT", 1, "one data group (required keyword)", status);
+    ffpkyj(fptr, "TFIELDS", tfields, "number of fields in each row", status);
+
+    for (ii = 0; ii < tfields; ii++) /* loop over every column */
+    {
+        if ( *(ttype[ii]) )  /* optional TTYPEn keyword */
+        {
+          sprintf(comm, "label for field %3d", ii + 1);
+          ffkeyn("TTYPE", ii + 1, name, status);
+          ffpkys(fptr, name, ttype[ii], comm, status);
+        }
+
+        if (tbcol[ii] < 1 || tbcol[ii] > rowlen)
+           *status = BAD_TBCOL;
+
+        sprintf(comm, "beginning column of field %3d", ii + 1);
+        ffkeyn("TBCOL", ii + 1, name, status);
+        ffpkyj(fptr, name, tbcol[ii], comm, status);
+
+        strcpy(tfmt, tform[ii]);  /* required TFORMn keyword */
+        ffupch(tfmt);
+        ffkeyn("TFORM", ii + 1, name, status);
+        ffpkys(fptr, name, tfmt, "Fortran-77 format of field", status);
+
+        if (tunit)
+        {
+         if (tunit[ii] && *(tunit[ii]) )  /* optional TUNITn keyword */
+         {
+          ffkeyn("TUNIT", ii + 1, name, status);
+          ffpkys(fptr, name, tunit[ii], "physical unit of field", status) ;
+         }
+        }
+
+        if (*status > 0)
+            break;       /* abort loop on error */
+    }
+
+    if (extnm)
+    {
+      if (extnm[0])       /* optional EXTNAME keyword */
+        ffpkys(fptr, "EXTNAME", extnm,
+               "name of this ASCII table extension", status);
+    }
+
+    if (*status > 0)
+        ffpmsg("Failed to write ASCII table header keywords (ffphtb)");
+
+    if (gotmem)
+        free(tbcol); 
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffphbn(fitsfile *fptr,  /* I - FITS file pointer                        */
+           LONGLONG naxis2,     /* I - number of rows in the table              */
+           int tfields,     /* I - number of columns in the table           */
+           char **ttype,    /* I - name of each column                      */
+           char **tform,    /* I - value of TFORMn keyword for each column  */
+           char **tunit,    /* I - value of TUNITn keyword for each column  */
+           const char *extnmx,   /* I - value of EXTNAME keyword, if any         */
+           LONGLONG pcount,     /* I - size of the variable length heap area    */
+           int *status)     /* IO - error status                            */
+/*
+  Put required Header keywords into the Binary Table:
+*/
+{
+    int ii, datatype, iread = 0;
+    long repeat, width;
+    LONGLONG naxis1;
+
+    char tfmt[30], name[FLEN_KEYWORD], comm[FLEN_COMMENT], extnm[FLEN_VALUE];
+    char *cptr;
+
+    if (*status > 0)
+        return(*status);
+
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    if ((fptr->Fptr)->headend != (fptr->Fptr)->headstart[(fptr->Fptr)->curhdu] )
+        return(*status = HEADER_NOT_EMPTY);
+    else if (naxis2 < 0)
+        return(*status = NEG_ROWS);
+    else if (pcount < 0)
+        return(*status = BAD_PCOUNT);
+    else if (tfields < 0 || tfields > 999)
+        return(*status = BAD_TFIELDS);
+
+    extnm[0] = '\0';
+    if (extnmx)
+        strncat(extnm, extnmx, FLEN_VALUE-1);
+
+    ffpkys(fptr, "XTENSION", "BINTABLE", "binary table extension", status);
+    ffpkyj(fptr, "BITPIX", 8, "8-bit bytes", status);
+    ffpkyj(fptr, "NAXIS", 2, "2-dimensional binary table", status);
+
+    naxis1 = 0;
+    for (ii = 0; ii < tfields; ii++)  /* sum the width of each field */
+    {
+        ffbnfm(tform[ii], &datatype, &repeat, &width, status);
+
+        if (datatype == TSTRING)
+            naxis1 += repeat;   /* one byte per char */
+        else if (datatype == TBIT)
+            naxis1 += (repeat + 7) / 8;
+        else if (datatype > 0)
+            naxis1 += repeat * (datatype / 10);
+        else if (tform[ii][0] == 'P' || tform[ii][1] == 'P')
+           /* this is a 'P' variable length descriptor (neg. datatype) */
+            naxis1 += 8;
+        else
+           /* this is a 'Q' variable length descriptor (neg. datatype) */
+            naxis1 += 16;
+
+        if (*status > 0)
+            break;       /* abort loop on error */
+    }
+
+    ffpkyj(fptr, "NAXIS1", naxis1, "width of table in bytes", status);
+    ffpkyj(fptr, "NAXIS2", naxis2, "number of rows in table", status);
+
+    /*
+      the initial value of PCOUNT (= size of the variable length array heap)
+      should always be zero.  If any variable length data is written, then
+      the value of PCOUNT will be updated when the HDU is closed
+    */
+    ffpkyj(fptr, "PCOUNT", 0, "size of special data area", status);
+    ffpkyj(fptr, "GCOUNT", 1, "one data group (required keyword)", status);
+    ffpkyj(fptr, "TFIELDS", tfields, "number of fields in each row", status);
+
+    for (ii = 0; ii < tfields; ii++) /* loop over every column */
+    {
+        if ( *(ttype[ii]) )  /* optional TTYPEn keyword */
+        {
+          sprintf(comm, "label for field %3d", ii + 1);
+          ffkeyn("TTYPE", ii + 1, name, status);
+          ffpkys(fptr, name, ttype[ii], comm, status);
+        }
+
+        strcpy(tfmt, tform[ii]);  /* required TFORMn keyword */
+        ffupch(tfmt);
+
+        ffkeyn("TFORM", ii + 1, name, status);
+        strcpy(comm, "data format of field");
+
+        ffbnfm(tfmt, &datatype, &repeat, &width, status);
+
+        if (datatype == TSTRING)
+        {
+            strcat(comm, ": ASCII Character");
+
+            /* Do sanity check to see if an ASCII table format was used,  */
+            /* e.g., 'A8' instead of '8A', or a bad unit width eg '8A9'.  */
+            /* Don't want to return an error status, so write error into  */
+            /* the keyword comment.  */
+
+            cptr = strchr(tfmt,'A');
+            cptr++;
+
+            if (cptr)
+               iread = sscanf(cptr,"%ld", &width);
+
+            if (iread == 1 && (width > repeat)) 
+            {
+              if (repeat == 1)
+                strcpy(comm, "ERROR??  USING ASCII TABLE SYNTAX BY MISTAKE??");
+              else
+                strcpy(comm, "rAw FORMAT ERROR! UNIT WIDTH w > COLUMN WIDTH r");
+            }
+        }
+        else if (datatype == TBIT)
+           strcat(comm, ": BIT");
+        else if (datatype == TBYTE)
+           strcat(comm, ": BYTE");
+        else if (datatype == TLOGICAL)
+           strcat(comm, ": 1-byte LOGICAL");
+        else if (datatype == TSHORT)
+           strcat(comm, ": 2-byte INTEGER");
+        else if (datatype == TUSHORT)
+           strcat(comm, ": 2-byte INTEGER");
+        else if (datatype == TLONG)
+           strcat(comm, ": 4-byte INTEGER");
+        else if (datatype == TLONGLONG)
+           strcat(comm, ": 8-byte INTEGER");
+        else if (datatype == TULONG)
+           strcat(comm, ": 4-byte INTEGER");
+        else if (datatype == TFLOAT)
+           strcat(comm, ": 4-byte REAL");
+        else if (datatype == TDOUBLE)
+           strcat(comm, ": 8-byte DOUBLE");
+        else if (datatype == TCOMPLEX)
+           strcat(comm, ": COMPLEX");
+        else if (datatype == TDBLCOMPLEX)
+           strcat(comm, ": DOUBLE COMPLEX");
+        else if (datatype < 0)
+           strcat(comm, ": variable length array");
+
+        if (abs(datatype) == TSBYTE) /* signed bytes */
+        {
+           /* Replace the 'S' with an 'B' in the TFORMn code */
+           cptr = tfmt;
+           while (*cptr != 'S') 
+              cptr++;
+
+           *cptr = 'B';
+           ffpkys(fptr, name, tfmt, comm, status);
+
+           /* write the TZEROn and TSCALn keywords */
+           ffkeyn("TZERO", ii + 1, name, status);
+           strcpy(comm, "offset for signed bytes");
+
+           ffpkyg(fptr, name, -128., 0, comm, status);
+
+           ffkeyn("TSCAL", ii + 1, name, status);
+           strcpy(comm, "data are not scaled");
+           ffpkyg(fptr, name, 1., 0, comm, status);
+        }
+        else if (abs(datatype) == TUSHORT) 
+        {
+           /* Replace the 'U' with an 'I' in the TFORMn code */
+           cptr = tfmt;
+           while (*cptr != 'U') 
+              cptr++;
+
+           *cptr = 'I';
+           ffpkys(fptr, name, tfmt, comm, status);
+
+           /* write the TZEROn and TSCALn keywords */
+           ffkeyn("TZERO", ii + 1, name, status);
+           strcpy(comm, "offset for unsigned integers");
+
+           ffpkyg(fptr, name, 32768., 0, comm, status);
+
+           ffkeyn("TSCAL", ii + 1, name, status);
+           strcpy(comm, "data are not scaled");
+           ffpkyg(fptr, name, 1., 0, comm, status);
+        }
+        else if (abs(datatype) == TULONG) 
+        {
+           /* Replace the 'V' with an 'J' in the TFORMn code */
+           cptr = tfmt;
+           while (*cptr != 'V') 
+              cptr++;
+
+           *cptr = 'J';
+           ffpkys(fptr, name, tfmt, comm, status);
+
+           /* write the TZEROn and TSCALn keywords */
+           ffkeyn("TZERO", ii + 1, name, status);
+           strcpy(comm, "offset for unsigned integers");
+
+           ffpkyg(fptr, name, 2147483648., 0, comm, status);
+
+           ffkeyn("TSCAL", ii + 1, name, status);
+           strcpy(comm, "data are not scaled");
+           ffpkyg(fptr, name, 1., 0, comm, status);
+        }
+        else
+        {
+           ffpkys(fptr, name, tfmt, comm, status);
+        }
+
+        if (tunit)
+        {
+         if (tunit[ii] && *(tunit[ii]) ) /* optional TUNITn keyword */
+         {
+          ffkeyn("TUNIT", ii + 1, name, status);
+          ffpkys(fptr, name, tunit[ii],
+             "physical unit of field", status);
+         }
+        }
+
+        if (*status > 0)
+            break;       /* abort loop on error */
+    }
+
+    if (extnm)
+    {
+      if (extnm[0])       /* optional EXTNAME keyword */
+        ffpkys(fptr, "EXTNAME", extnm,
+               "name of this binary table extension", status);
+    }
+
+    if (*status > 0)
+        ffpmsg("Failed to write binary table header keywords (ffphbn)");
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffphext(fitsfile *fptr,  /* I - FITS file pointer                       */
+           const char *xtensionx,   /* I - value for the XTENSION keyword          */
+           int bitpix,       /* I - value for the BIXPIX keyword            */
+           int naxis,        /* I - value for the NAXIS keyword             */
+           long naxes[],     /* I - value for the NAXISn keywords           */
+           LONGLONG pcount,  /* I - value for the PCOUNT keyword            */
+           LONGLONG gcount,  /* I - value for the GCOUNT keyword            */
+           int *status)      /* IO - error status                           */
+/*
+  Put required Header keywords into a conforming extension:
+*/
+{
+    char message[FLEN_ERRMSG],comm[81], name[20], xtension[FLEN_VALUE];
+    int ii;
+ 
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    if (*status > 0)
+        return(*status);
+    else if ((fptr->Fptr)->headend != (fptr->Fptr)->headstart[(fptr->Fptr)->curhdu] )
+        return(*status = HEADER_NOT_EMPTY);
+
+    if (naxis < 0 || naxis > 999)
+    {
+        sprintf(message,
+        "Illegal value for NAXIS keyword: %d", naxis);
+        ffpmsg(message);
+        return(*status = BAD_NAXIS);
+    }
+
+    xtension[0] = '\0';
+    strncat(xtension, xtensionx, FLEN_VALUE-1);
+
+    ffpkys(fptr, "XTENSION", xtension, "extension type", status);
+    ffpkyj(fptr, "BITPIX",   bitpix,   "number of bits per data pixel", status);
+    ffpkyj(fptr, "NAXIS",    naxis,    "number of data axes", status);
+
+    strcpy(comm, "length of data axis ");
+    for (ii = 0; ii < naxis; ii++)
+    {
+        if (naxes[ii] < 0)
+        {
+            sprintf(message,
+            "Illegal negative value for NAXIS%d keyword: %.0f", ii + 1, (double) (naxes[ii]));
+            ffpmsg(message);
+            return(*status = BAD_NAXES);
+        }
+
+        sprintf(&comm[20], "%d", ii + 1);
+        ffkeyn("NAXIS", ii + 1, name, status);
+        ffpkyj(fptr, name, naxes[ii], comm, status);
+    }
+
+
+    ffpkyj(fptr, "PCOUNT", pcount, " ", status);
+    ffpkyj(fptr, "GCOUNT", gcount, " ", status);
+
+    if (*status > 0)
+        ffpmsg("Failed to write extension header keywords (ffphext)");
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffi2c(LONGLONG ival,  /* I - value to be converted to a string */
+          char *cval,     /* O - character string representation of the value */
+          int *status)    /* IO - error status */
+/*
+  convert  value to a null-terminated formatted string.
+*/
+{
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    cval[0] = '\0';
+
+#if defined(_MSC_VER)
+    /* Microsoft Visual C++ 6.0 uses '%I64d' syntax  for 8-byte integers */
+    if (sprintf(cval, "%I64d", ival) < 0)
+
+#elif (USE_LL_SUFFIX == 1)
+    if (sprintf(cval, "%lld", ival) < 0)
+#else
+    if (sprintf(cval, "%ld", ival) < 0)
+#endif
+    {
+        ffpmsg("Error in ffi2c converting integer to string");
+        *status = BAD_I2C;
+    }
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffl2c(int lval,    /* I - value to be converted to a string */
+          char *cval,  /* O - character string representation of the value */
+          int *status) /* IO - error status ) */
+/*
+  convert logical value to a null-terminated formatted string.  If the
+  input value == 0, then the output character is the letter F, else
+  the output character is the letter T.  The output string is null terminated.
+*/
+{
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (lval)
+        strcpy(cval,"T");
+    else
+        strcpy(cval,"F");
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffs2c(char *instr,   /* I - null terminated input string  */
+          char *outstr,  /* O - null terminated quoted output string */
+          int *status)   /* IO - error status */
+/*
+  convert an input string to a quoted string. Leading spaces 
+  are significant.  FITS string keyword values must be at least 
+  8 chars long so pad out string with spaces if necessary.
+      Example:   km/s ==> 'km/s    '
+  Single quote characters in the input string will be replace by
+  two single quote characters. e.g., o'brian ==> 'o''brian'
+*/
+{
+    size_t len, ii, jj;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    if (!instr)            /* a null input pointer?? */
+    {
+       strcpy(outstr, "''");   /* a null FITS string */
+       return(*status);
+    }
+
+    outstr[0] = '\'';      /* start output string with a quote */
+
+    len = strlen(instr);
+    if (len > 68)
+        len = 68;    /* limit input string to 68 chars */
+
+    for (ii=0, jj=1; ii < len && jj < 69; ii++, jj++)
+    {
+        outstr[jj] = instr[ii];  /* copy each char from input to output */
+        if (instr[ii] == '\'')
+        {
+            jj++;
+            outstr[jj]='\'';   /* duplicate any apostrophies in the input */
+        }
+    }
+
+    for (; jj < 9; jj++)       /* pad string so it is at least 8 chars long */
+        outstr[jj] = ' ';
+
+    if (jj == 70)   /* only occurs if the last char of string was a quote */
+        outstr[69] = '\0';
+    else
+    {
+        outstr[jj] = '\'';         /* append closing quote character */
+        outstr[jj+1] = '\0';          /* terminate the string */
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffr2f(float fval,   /* I - value to be converted to a string */
+          int  decim,   /* I - number of decimal places to display */
+          char *cval,   /* O - character string representation of the value */
+          int  *status) /* IO - error status */
+/*
+  convert float value to a null-terminated F format string
+*/
+{
+    char *cptr;
+        
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    cval[0] = '\0';
+
+    if (decim < 0)
+    {
+        ffpmsg("Error in ffr2f:  no. of decimal places < 0");
+        return(*status = BAD_DECIM);
+    }
+
+    if (sprintf(cval, "%.*f", decim, fval) < 0)
+    {
+        ffpmsg("Error in ffr2f converting float to string");
+        *status = BAD_F2C;
+    }
+
+    /* replace comma with a period (e.g. in French locale) */
+    if ( (cptr = strchr(cval, ','))) *cptr = '.';
+
+    /* test if output string is 'NaN', 'INDEF', or 'INF' */
+    if (strchr(cval, 'N'))
+    {
+        ffpmsg("Error in ffr2f: float value is a NaN or INDEF");
+        *status = BAD_F2C;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffr2e(float fval,  /* I - value to be converted to a string */
+         int decim,    /* I - number of decimal places to display */
+         char *cval,   /* O - character string representation of the value */
+         int *status)  /* IO - error status */
+/*
+  convert float value to a null-terminated exponential format string
+*/
+{
+    char *cptr;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    cval[0] = '\0';
+
+    if (decim < 0)
+    {   /* use G format if decim is negative */
+        if ( sprintf(cval, "%.*G", -decim, fval) < 0)
+        {
+            ffpmsg("Error in ffr2e converting float to string");
+            *status = BAD_F2C;
+        }
+        else
+        {
+            /* test if E format was used, and there is no displayed decimal */
+            if ( !strchr(cval, '.') && strchr(cval,'E') )
+            {
+                /* reformat value with a decimal point and single zero */
+                if ( sprintf(cval, "%.1E", fval) < 0)
+                {
+                    ffpmsg("Error in ffr2e converting float to string");
+                    *status = BAD_F2C;
+                }
+
+                return(*status);  
+            }
+        }
+    }
+    else
+    {
+        if ( sprintf(cval, "%.*E", decim, fval) < 0)
+        {
+            ffpmsg("Error in ffr2e converting float to string");
+            *status = BAD_F2C;
+        }
+    }
+
+    if (*status <= 0)
+    {
+        /* replace comma with a period (e.g. in French locale) */
+        if ( (cptr = strchr(cval, ','))) *cptr = '.';
+
+        /* test if output string is 'NaN', 'INDEF', or 'INF' */
+        if (strchr(cval, 'N'))
+        {
+            ffpmsg("Error in ffr2e: float value is a NaN or INDEF");
+            *status = BAD_F2C;
+        }
+        else if ( !strchr(cval, '.') && !strchr(cval,'E') )
+        {
+            /* add decimal point if necessary to distinquish from integer */
+            strcat(cval, ".");
+        }
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffd2f(double dval,  /* I - value to be converted to a string */
+          int decim,    /* I - number of decimal places to display */
+          char *cval,   /* O - character string representation of the value */
+          int *status)  /* IO - error status */
+/*
+  convert double value to a null-terminated F format string
+*/
+{
+    char *cptr;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    cval[0] = '\0';
+
+    if (decim < 0)
+    {
+        ffpmsg("Error in ffd2f:  no. of decimal places < 0");
+        return(*status = BAD_DECIM);
+    }
+
+    if (sprintf(cval, "%.*f", decim, dval) < 0)
+    {
+        ffpmsg("Error in ffd2f converting double to string");
+        *status = BAD_F2C;
+    }
+
+    /* replace comma with a period (e.g. in French locale) */
+    if ( (cptr = strchr(cval, ','))) *cptr = '.';
+
+    /* test if output string is 'NaN', 'INDEF', or 'INF' */
+    if (strchr(cval, 'N'))
+    {
+        ffpmsg("Error in ffd2f: double value is a NaN or INDEF");
+        *status = BAD_F2C;
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffd2e(double dval,  /* I - value to be converted to a string */
+          int decim,    /* I - number of decimal places to display */
+          char *cval,   /* O - character string representation of the value */
+          int *status)  /* IO - error status */
+/*
+  convert double value to a null-terminated exponential format string.
+*/
+{
+    char *cptr;
+
+    if (*status > 0)           /* inherit input status value if > 0 */
+        return(*status);
+
+    cval[0] = '\0';
+
+    if (decim < 0)
+    {   /* use G format if decim is negative */
+        if ( sprintf(cval, "%.*G", -decim, dval) < 0)
+        {
+            ffpmsg("Error in ffd2e converting float to string");
+            *status = BAD_F2C;
+        }
+        else
+        {
+            /* test if E format was used, and there is no displayed decimal */
+            if ( !strchr(cval, '.') && strchr(cval,'E') )
+            {
+                /* reformat value with a decimal point and single zero */
+                if ( sprintf(cval, "%.1E", dval) < 0)
+                {
+                    ffpmsg("Error in ffd2e converting float to string");
+                    *status = BAD_F2C;
+                }
+
+                return(*status);  
+            }
+        }
+    }
+    else
+    {
+        if ( sprintf(cval, "%.*E", decim, dval) < 0)
+        {
+            ffpmsg("Error in ffd2e converting float to string");
+            *status = BAD_F2C;
+        }
+    }
+
+    if (*status <= 0)
+    {
+        /* replace comma with a period (e.g. in French locale) */
+        if ( (cptr = strchr(cval, ','))) *cptr = '.';
+
+        /* test if output string is 'NaN', 'INDEF', or 'INF' */
+        if (strchr(cval, 'N'))
+        {
+            ffpmsg("Error in ffd2e: double value is a NaN or INDEF");
+            *status = BAD_F2C;
+        }
+        else if ( !strchr(cval, '.') && !strchr(cval,'E') )
+        {
+            /* add decimal point if necessary to distinquish from integer */
+            strcat(cval, ".");
+        }
+    }
+
+    return(*status);
+}
+
diff --git a/src/plugins/cfitsio/quantize.c b/src/plugins/cfitsio/quantize.c
new file mode 100644
index 0000000..8fe6acc
--- /dev/null
+++ b/src/plugins/cfitsio/quantize.c
@@ -0,0 +1,3888 @@
+/*
+  The following code is based on algorithms written by Richard White at STScI and made
+  available for use in CFITSIO in July 1999 and updated in January 2008. 
+*/
+
+# include <stdio.h>
+# include <stdlib.h>
+# include <math.h>
+# include <limits.h>
+# include <float.h>
+
+#include "fitsio2.h"
+
+/* nearest integer function */
+# define NINT(x)  ((x >= 0.) ? (int) (x + 0.5) : (int) (x - 0.5))
+
+#define NULL_VALUE -2147483647 /* value used to represent undefined pixels */
+#define N_RESERVED_VALUES 10   /* number of reserved values, starting with */
+                               /* and including NULL_VALUE.  These values */
+                               /* may not be used to represent the quantized */
+                               /* and scaled floating point pixel values */
+			       /* If lossy Hcompression is used, and the */
+			       /* array contains null values, then it is also */
+			       /* possible for the compressed values to slightly */
+			       /* exceed the range of the actual (lossless) values */
+			       /* so we must reserve a little more space */
+			       
+/* more than this many standard deviations from the mean is an outlier */
+# define SIGMA_CLIP     5.
+# define NITER          3	/* number of sigma-clipping iterations */
+
+static int FnMeanSigma_short(short *array, long npix, int nullcheck, 
+  short nullvalue, long *ngoodpix, double *mean, double *sigma, int *status);       
+static int FnMeanSigma_int(int *array, long npix, int nullcheck,
+  int nullvalue, long *ngoodpix, double *mean, double *sigma, int *status);       
+static int FnMeanSigma_float(float *array, long npix, int nullcheck,
+  float nullvalue, long *ngoodpix, double *mean, double *sigma, int *status);       
+static int FnMeanSigma_double(double *array, long npix, int nullcheck,
+  double nullvalue, long *ngoodpix, double *mean, double *sigma, int *status);       
+
+static int FnNoise5_short(short *array, long nx, long ny, int nullcheck, 
+   short nullvalue, long *ngood, short *minval, short *maxval, 
+   double *n2, double *n3, double *n5, int *status);   
+static int FnNoise5_int(int *array, long nx, long ny, int nullcheck, 
+   int nullvalue, long *ngood, int *minval, int *maxval, 
+   double *n2, double *n3, double *n5, int *status);   
+static int FnNoise5_float(float *array, long nx, long ny, int nullcheck, 
+   float nullvalue, long *ngood, float *minval, float *maxval, 
+   double *n2, double *n3, double *n5, int *status);   
+static int FnNoise5_double(double *array, long nx, long ny, int nullcheck, 
+   double nullvalue, long *ngood, double *minval, double *maxval, 
+   double *n2, double *n3, double *n5, int *status);   
+
+static int FnNoise3_short(short *array, long nx, long ny, int nullcheck, 
+   short nullvalue, long *ngood, short *minval, short *maxval, double *noise, int *status);       
+static int FnNoise3_int(int *array, long nx, long ny, int nullcheck, 
+   int nullvalue, long *ngood, int *minval, int *maxval, double *noise, int *status);          
+static int FnNoise3_float(float *array, long nx, long ny, int nullcheck, 
+   float nullvalue, long *ngood, float *minval, float *maxval, double *noise, int *status);        
+static int FnNoise3_double(double *array, long nx, long ny, int nullcheck, 
+   double nullvalue, long *ngood, double *minval, double *maxval, double *noise, int *status);        
+
+static int FnNoise1_short(short *array, long nx, long ny, 
+   int nullcheck, short nullvalue, double *noise, int *status);       
+static int FnNoise1_int(int *array, long nx, long ny, 
+   int nullcheck, int nullvalue, double *noise, int *status);       
+static int FnNoise1_float(float *array, long nx, long ny, 
+   int nullcheck, float nullvalue, double *noise, int *status);       
+static int FnNoise1_double(double *array, long nx, long ny, 
+   int nullcheck, double nullvalue, double *noise, int *status);       
+
+static int FnCompare_short (const void *, const void *);
+static int FnCompare_int (const void *, const void *);
+static int FnCompare_float (const void *, const void *);
+static int FnCompare_double (const void *, const void *);
+static float quick_select_float(float arr[], int n);
+static short quick_select_short(short arr[], int n);
+static int quick_select_int(int arr[], int n);
+static LONGLONG quick_select_longlong(LONGLONG arr[], int n);
+static double quick_select_double(double arr[], int n);
+
+/*---------------------------------------------------------------------------*/
+int fits_quantize_float (long row, float fdata[], long nxpix, long nypix, int nullcheck, 
+	float in_null_value, float qlevel, int idata[], double *bscale,
+	double *bzero, int *iminval, int *imaxval) {
+
+/* arguments:
+long row            i: if positive, tile number = row number in the binary table
+                       (this is only used when dithering the quantized values)
+float fdata[]       i: array of image pixels to be compressed
+long nxpix          i: number of pixels in each row of fdata
+long nypix          i: number of rows in fdata
+nullcheck           i: check for nullvalues in fdata?
+float in_null_value i: value used to represent undefined pixels in fdata
+float qlevel        i: quantization level
+int idata[]         o: values of fdata after applying bzero and bscale
+double bscale       o: scale factor
+double bzero        o: zero offset
+int iminval         o: minimum quantized value that is returned
+int imaxval         o: maximum quantized value that is returned
+
+The function value will be one if the input fdata were copied to idata;
+in this case the parameters bscale and bzero can be used to convert back to
+nearly the original floating point values:  fdata ~= idata * bscale + bzero.
+If the function value is zero, the data were not copied to idata.
+*/
+
+	int status, anynulls = 0, iseed;
+	long i, nx, ngood = 0;
+	double stdev, noise2, noise3, noise5;	/* MAD 2nd, 3rd, and 5th order noise values */
+	float minval = 0., maxval = 0.;  /* min & max of fdata */
+	double delta;		/* bscale, 1 in idata = delta in fdata */
+	double zeropt;	        /* bzero */
+	double temp;
+        int nextrand = 0;
+	extern float *fits_rand_value; /* this is defined in imcompress.c */
+	LONGLONG iqfactor;
+
+	nx = nxpix * nypix;
+	if (nx <= 1) {
+	    *bscale = 1.;
+	    *bzero  = 0.;
+	    return (0);
+	}
+
+        if (qlevel >= 0.) {
+
+	    /* estimate background noise using MAD pixel differences */
+	    FnNoise5_float(fdata, nxpix, nypix, nullcheck, in_null_value, &ngood,
+	        &minval, &maxval, &noise2, &noise3, &noise5, &status);      
+
+	    if (nullcheck && ngood == 0) {   /* special case of an image filled with Nulls */
+	        /* set parameters to dummy values, which are not used */
+		minval = 0.;
+		maxval = 1.;
+		stdev = 1;
+	    } else {
+
+	        /* use the minimum of noise2, noise3, and noise5 as the best noise value */
+	        stdev = noise3;
+	        if (noise2 != 0. && noise2 < stdev) stdev = noise2;
+	        if (noise5 != 0. && noise5 < stdev) stdev = noise5;
+            }
+
+	    if (qlevel == 0.)
+	        delta = stdev / 4.;  /* default quantization */
+	    else
+	        delta = stdev / qlevel;
+
+	    if (delta == 0.) 
+	        return (0);			/* don't quantize */
+
+	} else {
+	    /* negative value represents the absolute quantization level */
+	    delta = -qlevel;
+
+	    /* only nned to calculate the min and max values */
+	    FnNoise3_float(fdata, nxpix, nypix, nullcheck, in_null_value, &ngood,
+	        &minval, &maxval, 0, &status);      
+ 	}
+
+        /* check that the range of quantized levels is not > range of int */
+	if ((maxval - minval) / delta > 2. * 2147483647. - N_RESERVED_VALUES )
+	    return (0);			/* don't quantize */
+
+        if (row > 0) { /* we need to dither the quantized values */
+            if (!fits_rand_value) 
+	        if (fits_init_randoms()) return(MEMORY_ALLOCATION);
+
+	    /* initialize the index to the next random number in the list */
+            iseed = (int) ((row - 1) % N_RANDOM);
+	    nextrand = (int) (fits_rand_value[iseed] * 500.);
+	}
+
+        if (ngood == nx) {   /* don't have to check for nulls */
+            /* return all positive values, if possible since some */
+            /* compression algorithms either only work for positive integers, */
+            /* or are more efficient.  */
+
+            if ((maxval - minval) / delta < 2147483647. - N_RESERVED_VALUES )
+            {
+                zeropt = minval;
+		/* fudge the zero point so it is an integer multiple of delta */
+		/* This helps to ensure the same scaling will be performed if the */
+		/* file undergoes multiple fpack/funpack cycles */
+		iqfactor = (LONGLONG) (zeropt/delta  + 0.5);
+		zeropt = iqfactor * delta;               
+            }
+            else
+            {
+                /* center the quantized levels around zero */
+                zeropt = (minval + maxval) / 2.;
+            }
+
+            if (row > 0) {  /* dither the values when quantizing */
+              for (i = 0;  i < nx;  i++) {
+	    
+		idata[i] =  NINT((((double) fdata[i] - zeropt) / delta) + fits_rand_value[nextrand] - 0.5);
+
+                nextrand++;
+		if (nextrand == N_RANDOM) {
+                    iseed++;
+		    if (iseed == N_RANDOM) iseed = 0;
+	            nextrand = (int) (fits_rand_value[iseed] * 500);
+                }
+              }
+            } else {  /* do not dither the values */
+
+       	        for (i = 0;  i < nx;  i++) {
+	            idata[i] = NINT ((fdata[i] - zeropt) / delta);
+                }
+            } 
+        }
+        else {
+            /* data contains null values; shift the range to be */
+            /* close to the value used to represent null values */
+            zeropt = minval - delta * (NULL_VALUE + N_RESERVED_VALUES);
+
+            if (row > 0) {  /* dither the values */
+	      for (i = 0;  i < nx;  i++) {
+                if (fdata[i] != in_null_value) {
+		    idata[i] =  NINT((((double) fdata[i] - zeropt) / delta) + fits_rand_value[nextrand] - 0.5);
+                } else {
+                    idata[i] = NULL_VALUE;
+                }
+
+                /* increment the random number index, regardless */
+                nextrand++;
+		if (nextrand == N_RANDOM) {
+                      iseed++;
+		      if (iseed == N_RANDOM) iseed = 0;
+	              nextrand = (int) (fits_rand_value[iseed] * 500);
+                }
+              }
+            } else {  /* do not dither the values */
+	       for (i = 0;  i < nx;  i++) {
+                 if (fdata[i] != in_null_value)
+		    idata[i] =  NINT((fdata[i] - zeropt) / delta);
+                 else 
+                    idata[i] = NULL_VALUE;
+               }
+            }
+	}
+
+        /* calc min and max values */
+        temp = (minval - zeropt) / delta;
+        *iminval =  NINT (temp);
+        temp = (maxval - zeropt) / delta;
+        *imaxval =  NINT (temp);
+
+	*bscale = delta;
+	*bzero = zeropt;
+	return (1);			/* yes, data have been quantized */
+}
+/*---------------------------------------------------------------------------*/
+int fits_quantize_double (long row, double fdata[], long nxpix, long nypix, int nullcheck, 
+	double in_null_value, float qlevel, int idata[], double *bscale,
+	double *bzero, int *iminval, int *imaxval) {
+
+/* arguments:
+long row            i: tile number = row number in the binary table
+double fdata[]      i: array of image pixels to be compressed
+long nxpix          i: number of pixels in each row of fdata
+long nypix          i: number of rows in fdata
+nullcheck           i: check for nullvalues in fdata?
+double in_null_value i: value used to represent undefined pixels in fdata
+int noise_bits      i: quantization level (number of bits)
+int idata[]         o: values of fdata after applying bzero and bscale
+double bscale       o: scale factor
+double bzero        o: zero offset
+int iminval         o: minimum quantized value that is returned
+int imaxval         o: maximum quantized value that is returned
+
+The function value will be one if the input fdata were copied to idata;
+in this case the parameters bscale and bzero can be used to convert back to
+nearly the original floating point values:  fdata ~= idata * bscale + bzero.
+If the function value is zero, the data were not copied to idata.
+*/
+
+	int status, anynulls = 0, iseed;
+	long i, nx, ngood = 0;
+	double stdev, noise2, noise3, noise5;	/* MAD 2nd, 3rd, and 5th order noise values */
+	double minval = 0., maxval = 0.;  /* min & max of fdata */
+	double delta;		/* bscale, 1 in idata = delta in fdata */
+	double zeropt;	        /* bzero */
+	double temp;
+        int nextrand = 0;
+	extern float *fits_rand_value;
+	LONGLONG iqfactor;
+
+	nx = nxpix * nypix;
+	if (nx <= 1) {
+	    *bscale = 1.;
+	    *bzero  = 0.;
+	    return (0);
+	}
+
+        if (qlevel >= 0.) {
+
+	    /* estimate background noise using MAD pixel differences */
+	    FnNoise5_double(fdata, nxpix, nypix, nullcheck, in_null_value, &ngood,
+	        &minval, &maxval, &noise2, &noise3, &noise5, &status);      
+
+	    if (nullcheck && ngood == 0) {   /* special case of an image filled with Nulls */
+	        /* set parameters to dummy values, which are not used */
+		minval = 0.;
+		maxval = 1.;
+		stdev = 1;
+	    } else {
+
+	        /* use the minimum of noise2, noise3, and noise5 as the best noise value */
+	        stdev = noise3;
+	        if (noise2 != 0. && noise2 < stdev) stdev = noise2;
+	        if (noise5 != 0. && noise5 < stdev) stdev = noise5;
+            }
+
+	    if (qlevel == 0.)
+	        delta = stdev / 4.;  /* default quantization */
+	    else
+	        delta = stdev / qlevel;
+
+	    if (delta == 0.) 
+	        return (0);			/* don't quantize */
+
+	} else {
+	    /* negative value represents the absolute quantization level */
+	    delta = -qlevel;
+
+	    /* only nned to calculate the min and max values */
+	    FnNoise3_double(fdata, nxpix, nypix, nullcheck, in_null_value, &ngood,
+	        &minval, &maxval, 0, &status);      
+ 	}
+
+        /* check that the range of quantized levels is not > range of int */
+	if ((maxval - minval) / delta > 2. * 2147483647. - N_RESERVED_VALUES )
+	    return (0);			/* don't quantize */
+
+        if (row > 0) { /* we need to dither the quantized values */
+            if (!fits_rand_value) 
+	       if (fits_init_randoms()) return(MEMORY_ALLOCATION);
+
+	    /* initialize the index to the next random number in the list */
+            iseed = (int) ((row - 1) % N_RANDOM);
+	    nextrand = (int) (fits_rand_value[iseed] * 500);
+	}
+
+        if (ngood == nx) {   /* don't have to check for nulls */
+            /* return all positive values, if possible since some */
+            /* compression algorithms either only work for positive integers, */
+            /* or are more efficient.  */
+            if ((maxval - minval) / delta < 2147483647. - N_RESERVED_VALUES )
+            {
+                zeropt = minval;
+		/* fudge the zero point so it is an integer multiple of delta */
+		/* This helps to ensure the same scaling will be performed if the */
+		/* file undergoes multiple fpack/funpack cycles */
+		iqfactor = (LONGLONG) (zeropt/delta  + 0.5);
+		zeropt = iqfactor * delta;               
+            }
+            else
+            {
+                /* center the quantized levels around zero */
+                zeropt = (minval + maxval) / 2.;
+            }
+
+            if (row > 0) {  /* dither the values when quantizing */
+       	      for (i = 0;  i < nx;  i++) {
+
+		idata[i] =  NINT((((double) fdata[i] - zeropt) / delta) + fits_rand_value[nextrand] - 0.5);
+
+                nextrand++;
+		if (nextrand == N_RANDOM) {
+                    iseed++;
+	            nextrand = (int) (fits_rand_value[iseed] * 500);
+                }
+              }
+            } else {  /* do not dither the values */
+
+       	        for (i = 0;  i < nx;  i++) {
+	            idata[i] = NINT ((fdata[i] - zeropt) / delta);
+                }
+            } 
+        }
+        else {
+            /* data contains null values; shift the range to be */
+            /* close to the value used to represent null values */
+            zeropt = minval - delta * (NULL_VALUE + N_RESERVED_VALUES);
+
+            if (row > 0) {  /* dither the values */
+	      for (i = 0;  i < nx;  i++) {
+                if (fdata[i] != in_null_value) {
+		    idata[i] =  NINT((((double) fdata[i] - zeropt) / delta) + fits_rand_value[nextrand] - 0.5);
+                } else {
+                    idata[i] = NULL_VALUE;
+                }
+
+                /* increment the random number index, regardless */
+                nextrand++;
+		if (nextrand == N_RANDOM) {
+                        iseed++;
+	                nextrand = (int) (fits_rand_value[iseed] * 500);
+                } 
+              }
+            } else {  /* do not dither the values */
+	       for (i = 0;  i < nx;  i++) {
+                 if (fdata[i] != in_null_value)
+		    idata[i] =  NINT((fdata[i] - zeropt) / delta);
+                 else 
+                    idata[i] = NULL_VALUE;
+               }
+            }
+	}
+
+        /* calc min and max values */
+        temp = (minval - zeropt) / delta;
+        *iminval =  NINT (temp);
+        temp = (maxval - zeropt) / delta;
+        *imaxval =  NINT (temp);
+
+	*bscale = delta;
+	*bzero = zeropt;
+
+	return (1);			/* yes, data have been quantized */
+}
+/*--------------------------------------------------------------------------*/
+int fits_img_stats_short(short *array, /*  2 dimensional array of image pixels */
+        long nx,            /* number of pixels in each row of the image */
+	long ny,            /* number of rows in the image */
+	                    /* (if this is a 3D image, then ny should be the */
+			    /* product of the no. of rows times the no. of planes) */
+	int nullcheck,      /* check for null values, if true */
+	short nullvalue,    /* value of null pixels, if nullcheck is true */
+
+   /* returned parameters (if the pointer is not null)  */
+	long *ngoodpix,     /* number of non-null pixels in the image */
+	short *minvalue,    /* returned minimum non-null value in the array */
+	short *maxvalue,    /* returned maximum non-null value in the array */
+	double *mean,       /* returned mean value of all non-null pixels */
+	double *sigma,      /* returned R.M.S. value of all non-null pixels */
+	double *noise1,     /* 1st order estimate of noise in image background level */
+	double *noise2,     /* 2nd order estimate of noise in image background level */
+	double *noise3,     /* 3rd order estimate of noise in image background level */
+	double *noise5,     /* 5th order estimate of noise in image background level */
+	int *status)        /* error status */
+
+/*
+    Compute statistics of the input short integer image.
+*/
+{
+	long ngood;
+	short minval, maxval;
+	double xmean = 0., xsigma = 0., xnoise = 0., xnoise2 = 0., xnoise3 = 0., xnoise5 = 0.;
+
+	/* need to calculate mean and/or sigma and/or limits? */
+	if (mean || sigma ) {
+		FnMeanSigma_short(array, nx * ny, nullcheck, nullvalue, 
+			&ngood, &xmean, &xsigma, status);
+
+	    if (ngoodpix) *ngoodpix = ngood;
+	    if (mean)     *mean = xmean;
+	    if (sigma)    *sigma = xsigma;
+	}
+
+	if (noise1) {
+		FnNoise1_short(array, nx, ny, nullcheck, nullvalue, 
+		  &xnoise, status);
+
+		*noise1  = xnoise;
+	}
+
+	if (minvalue || maxvalue || noise3) {
+		FnNoise5_short(array, nx, ny, nullcheck, nullvalue, 
+			&ngood, &minval, &maxval, &xnoise2, &xnoise3, &xnoise5, status);
+
+		if (ngoodpix) *ngoodpix = ngood;
+		if (minvalue) *minvalue= minval;
+		if (maxvalue) *maxvalue = maxval;
+		if (noise2) *noise2  = xnoise2;
+		if (noise3) *noise3  = xnoise3;
+		if (noise5) *noise5  = xnoise5;
+	}
+	return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_img_stats_int(int *array, /*  2 dimensional array of image pixels */
+        long nx,            /* number of pixels in each row of the image */
+	long ny,            /* number of rows in the image */
+	                    /* (if this is a 3D image, then ny should be the */
+			    /* product of the no. of rows times the no. of planes) */
+	int nullcheck,      /* check for null values, if true */
+	int nullvalue,    /* value of null pixels, if nullcheck is true */
+
+   /* returned parameters (if the pointer is not null)  */
+	long *ngoodpix,     /* number of non-null pixels in the image */
+	int *minvalue,    /* returned minimum non-null value in the array */
+	int *maxvalue,    /* returned maximum non-null value in the array */
+	double *mean,       /* returned mean value of all non-null pixels */
+	double *sigma,      /* returned R.M.S. value of all non-null pixels */
+	double *noise1,     /* 1st order estimate of noise in image background level */
+	double *noise2,     /* 2nd order estimate of noise in image background level */
+	double *noise3,     /* 3rd order estimate of noise in image background level */
+	double *noise5,     /* 5th order estimate of noise in image background level */
+	int *status)        /* error status */
+
+/*
+    Compute statistics of the input integer image.
+*/
+{
+	long ngood;
+	int minval, maxval;
+	double xmean = 0., xsigma = 0., xnoise = 0., xnoise2 = 0., xnoise3 = 0., xnoise5 = 0.;
+
+	/* need to calculate mean and/or sigma and/or limits? */
+	if (mean || sigma ) {
+		FnMeanSigma_int(array, nx * ny, nullcheck, nullvalue, 
+			&ngood, &xmean, &xsigma, status);
+
+	    if (ngoodpix) *ngoodpix = ngood;
+	    if (mean)     *mean = xmean;
+	    if (sigma)    *sigma = xsigma;
+	}
+
+	if (noise1) {
+		FnNoise1_int(array, nx, ny, nullcheck, nullvalue, 
+		  &xnoise, status);
+
+		*noise1  = xnoise;
+	}
+
+	if (minvalue || maxvalue || noise3) {
+		FnNoise5_int(array, nx, ny, nullcheck, nullvalue, 
+			&ngood, &minval, &maxval, &xnoise2, &xnoise3, &xnoise5, status);
+
+		if (ngoodpix) *ngoodpix = ngood;
+		if (minvalue) *minvalue= minval;
+		if (maxvalue) *maxvalue = maxval;
+		if (noise2) *noise2  = xnoise2;
+		if (noise3) *noise3  = xnoise3;
+		if (noise5) *noise5  = xnoise5;
+	}
+	return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fits_img_stats_float(float *array, /*  2 dimensional array of image pixels */
+        long nx,            /* number of pixels in each row of the image */
+	long ny,            /* number of rows in the image */
+	                    /* (if this is a 3D image, then ny should be the */
+			    /* product of the no. of rows times the no. of planes) */
+	int nullcheck,      /* check for null values, if true */
+	float nullvalue,    /* value of null pixels, if nullcheck is true */
+
+   /* returned parameters (if the pointer is not null)  */
+	long *ngoodpix,     /* number of non-null pixels in the image */
+	float *minvalue,    /* returned minimum non-null value in the array */
+	float *maxvalue,    /* returned maximum non-null value in the array */
+	double *mean,       /* returned mean value of all non-null pixels */
+	double *sigma,      /* returned R.M.S. value of all non-null pixels */
+	double *noise1,     /* 1st order estimate of noise in image background level */
+	double *noise2,     /* 2nd order estimate of noise in image background level */
+	double *noise3,     /* 3rd order estimate of noise in image background level */
+	double *noise5,     /* 5th order estimate of noise in image background level */
+	int *status)        /* error status */
+
+/*
+    Compute statistics of the input float image.
+*/
+{
+	long ngood;
+	float minval, maxval;
+	double xmean = 0., xsigma = 0., xnoise = 0., xnoise2 = 0., xnoise3 = 0., xnoise5 = 0.;
+
+	/* need to calculate mean and/or sigma and/or limits? */
+	if (mean || sigma ) {
+		FnMeanSigma_float(array, nx * ny, nullcheck, nullvalue, 
+			&ngood, &xmean, &xsigma, status);
+
+	    if (ngoodpix) *ngoodpix = ngood;
+	    if (mean)     *mean = xmean;
+	    if (sigma)    *sigma = xsigma;
+	}
+
+	if (noise1) {
+		FnNoise1_float(array, nx, ny, nullcheck, nullvalue, 
+		  &xnoise, status);
+
+		*noise1  = xnoise;
+	}
+
+	if (minvalue || maxvalue || noise3) {
+		FnNoise5_float(array, nx, ny, nullcheck, nullvalue, 
+			&ngood, &minval, &maxval, &xnoise2, &xnoise3, &xnoise5, status);
+
+		if (ngoodpix) *ngoodpix = ngood;
+		if (minvalue) *minvalue= minval;
+		if (maxvalue) *maxvalue = maxval;
+		if (noise2) *noise2  = xnoise2;
+		if (noise3) *noise3  = xnoise3;
+		if (noise5) *noise5  = xnoise5;
+	}
+	return(*status);
+}
+/*--------------------------------------------------------------------------*/
+static int FnMeanSigma_short
+       (short *array,       /*  2 dimensional array of image pixels */
+        long npix,          /* number of pixels in the image */
+	int nullcheck,      /* check for null values, if true */
+	short nullvalue,    /* value of null pixels, if nullcheck is true */
+
+   /* returned parameters */
+   
+	long *ngoodpix,     /* number of non-null pixels in the image */
+	double *mean,       /* returned mean value of all non-null pixels */
+	double *sigma,      /* returned R.M.S. value of all non-null pixels */
+	int *status)        /* error status */
+
+/*
+Compute mean and RMS sigma of the non-null pixels in the input array.
+*/
+{
+	long ii, ngood = 0;
+	short *value;
+	double sum = 0., sum2 = 0., xtemp;
+
+	value = array;
+	    
+	if (nullcheck) {
+	        for (ii = 0; ii < npix; ii++, value++) {
+		    if (*value != nullvalue) {
+		        ngood++;
+		        xtemp = (double) *value;
+		        sum += xtemp;
+		        sum2 += (xtemp * xtemp);
+		    }
+		}
+	} else {
+	        ngood = npix;
+	        for (ii = 0; ii < npix; ii++, value++) {
+		        xtemp = (double) *value;
+		        sum += xtemp;
+		        sum2 += (xtemp * xtemp);
+		}
+	}
+
+	if (ngood > 1) {
+		if (ngoodpix) *ngoodpix = ngood;
+		xtemp = sum / ngood;
+		if (mean)     *mean = xtemp;
+		if (sigma)    *sigma = sqrt((sum2 / ngood) - (xtemp * xtemp));
+	} else if (ngood == 1){
+		if (ngoodpix) *ngoodpix = 1;
+		if (mean)     *mean = sum;
+		if (sigma)    *sigma = 0.0;
+	} else {
+		if (ngoodpix) *ngoodpix = 0;
+	        if (mean)     *mean = 0.;
+		if (sigma)    *sigma = 0.;
+	}	    
+	return(*status);
+}
+/*--------------------------------------------------------------------------*/
+static int FnMeanSigma_int
+       (int *array,       /*  2 dimensional array of image pixels */
+        long npix,          /* number of pixels in the image */
+	int nullcheck,      /* check for null values, if true */
+	int nullvalue,    /* value of null pixels, if nullcheck is true */
+
+   /* returned parameters */
+   
+	long *ngoodpix,     /* number of non-null pixels in the image */
+	double *mean,       /* returned mean value of all non-null pixels */
+	double *sigma,      /* returned R.M.S. value of all non-null pixels */
+	int *status)        /* error status */
+
+/*
+Compute mean and RMS sigma of the non-null pixels in the input array.
+*/
+{
+	long ii, ngood = 0;
+	int *value;
+	double sum = 0., sum2 = 0., xtemp;
+
+	value = array;
+	    
+	if (nullcheck) {
+	        for (ii = 0; ii < npix; ii++, value++) {
+		    if (*value != nullvalue) {
+		        ngood++;
+		        xtemp = (double) *value;
+		        sum += xtemp;
+		        sum2 += (xtemp * xtemp);
+		    }
+		}
+	} else {
+	        ngood = npix;
+	        for (ii = 0; ii < npix; ii++, value++) {
+		        xtemp = (double) *value;
+		        sum += xtemp;
+		        sum2 += (xtemp * xtemp);
+		}
+	}
+
+	if (ngood > 1) {
+		if (ngoodpix) *ngoodpix = ngood;
+		xtemp = sum / ngood;
+		if (mean)     *mean = xtemp;
+		if (sigma)    *sigma = sqrt((sum2 / ngood) - (xtemp * xtemp));
+	} else if (ngood == 1){
+		if (ngoodpix) *ngoodpix = 1;
+		if (mean)     *mean = sum;
+		if (sigma)    *sigma = 0.0;
+	} else {
+		if (ngoodpix) *ngoodpix = 0;
+	        if (mean)     *mean = 0.;
+		if (sigma)    *sigma = 0.;
+	}	    
+	return(*status);
+}
+/*--------------------------------------------------------------------------*/
+static int FnMeanSigma_float
+       (float *array,       /*  2 dimensional array of image pixels */
+        long npix,          /* number of pixels in the image */
+	int nullcheck,      /* check for null values, if true */
+	float nullvalue,    /* value of null pixels, if nullcheck is true */
+
+   /* returned parameters */
+   
+	long *ngoodpix,     /* number of non-null pixels in the image */
+	double *mean,       /* returned mean value of all non-null pixels */
+	double *sigma,      /* returned R.M.S. value of all non-null pixels */
+	int *status)        /* error status */
+
+/*
+Compute mean and RMS sigma of the non-null pixels in the input array.
+*/
+{
+	long ii, ngood = 0;
+	float *value;
+	double sum = 0., sum2 = 0., xtemp;
+
+	value = array;
+	    
+	if (nullcheck) {
+	        for (ii = 0; ii < npix; ii++, value++) {
+		    if (*value != nullvalue) {
+		        ngood++;
+		        xtemp = (double) *value;
+		        sum += xtemp;
+		        sum2 += (xtemp * xtemp);
+		    }
+		}
+	} else {
+	        ngood = npix;
+	        for (ii = 0; ii < npix; ii++, value++) {
+		        xtemp = (double) *value;
+		        sum += xtemp;
+		        sum2 += (xtemp * xtemp);
+		}
+	}
+
+	if (ngood > 1) {
+		if (ngoodpix) *ngoodpix = ngood;
+		xtemp = sum / ngood;
+		if (mean)     *mean = xtemp;
+		if (sigma)    *sigma = sqrt((sum2 / ngood) - (xtemp * xtemp));
+	} else if (ngood == 1){
+		if (ngoodpix) *ngoodpix = 1;
+		if (mean)     *mean = sum;
+		if (sigma)    *sigma = 0.0;
+	} else {
+		if (ngoodpix) *ngoodpix = 0;
+	        if (mean)     *mean = 0.;
+		if (sigma)    *sigma = 0.;
+	}	    
+	return(*status);
+}
+/*--------------------------------------------------------------------------*/
+static int FnMeanSigma_double
+       (double *array,       /*  2 dimensional array of image pixels */
+        long npix,          /* number of pixels in the image */
+	int nullcheck,      /* check for null values, if true */
+	double nullvalue,    /* value of null pixels, if nullcheck is true */
+
+   /* returned parameters */
+   
+	long *ngoodpix,     /* number of non-null pixels in the image */
+	double *mean,       /* returned mean value of all non-null pixels */
+	double *sigma,      /* returned R.M.S. value of all non-null pixels */
+	int *status)        /* error status */
+
+/*
+Compute mean and RMS sigma of the non-null pixels in the input array.
+*/
+{
+	long ii, ngood = 0;
+	double *value;
+	double sum = 0., sum2 = 0., xtemp;
+
+	value = array;
+	    
+	if (nullcheck) {
+	        for (ii = 0; ii < npix; ii++, value++) {
+		    if (*value != nullvalue) {
+		        ngood++;
+		        xtemp = *value;
+		        sum += xtemp;
+		        sum2 += (xtemp * xtemp);
+		    }
+		}
+	} else {
+	        ngood = npix;
+	        for (ii = 0; ii < npix; ii++, value++) {
+		        xtemp = *value;
+		        sum += xtemp;
+		        sum2 += (xtemp * xtemp);
+		}
+	}
+
+	if (ngood > 1) {
+		if (ngoodpix) *ngoodpix = ngood;
+		xtemp = sum / ngood;
+		if (mean)     *mean = xtemp;
+		if (sigma)    *sigma = sqrt((sum2 / ngood) - (xtemp * xtemp));
+	} else if (ngood == 1){
+		if (ngoodpix) *ngoodpix = 1;
+		if (mean)     *mean = sum;
+		if (sigma)    *sigma = 0.0;
+	} else {
+		if (ngoodpix) *ngoodpix = 0;
+	        if (mean)     *mean = 0.;
+		if (sigma)    *sigma = 0.;
+	}	    
+	return(*status);
+}
+/*--------------------------------------------------------------------------*/
+static int FnNoise5_short
+       (short *array,       /*  2 dimensional array of image pixels */
+        long nx,            /* number of pixels in each row of the image */
+        long ny,            /* number of rows in the image */
+	int nullcheck,      /* check for null values, if true */
+	short nullvalue,    /* value of null pixels, if nullcheck is true */
+   /* returned parameters */   
+	long *ngood,        /* number of good, non-null pixels? */
+	short *minval,    /* minimum non-null value */
+	short *maxval,    /* maximum non-null value */
+	double *noise2,      /* returned 2nd order MAD of all non-null pixels */
+	double *noise3,      /* returned 3rd order MAD of all non-null pixels */
+	double *noise5,      /* returned 5th order MAD of all non-null pixels */
+	int *status)        /* error status */
+
+/*
+Estimate the median and background noise in the input image using 2nd, 3rd and 5th
+order Median Absolute Differences.
+
+The noise in the background of the image is calculated using the MAD algorithms 
+developed for deriving the signal to noise ratio in spectra
+(see issue #42 of the ST-ECF newsletter, http://www.stecf.org/documents/newsletter/)
+
+3rd order:  noise = 1.482602 / sqrt(6) * median (abs(2*flux(i) - flux(i-2) - flux(i+2)))
+
+The returned estimates are the median of the values that are computed for each 
+row of the image.
+*/
+{
+	long ii, jj, nrows = 0, nrows2 = 0, nvals, nvals2, ngoodpix = 0;
+	int *differences2, *differences3, *differences5;
+	short *rowpix, v1, v2, v3, v4, v5, v6, v7, v8, v9;
+	short xminval = SHRT_MAX, xmaxval = SHRT_MIN;
+	int do_range = 0;
+	double *diffs2, *diffs3, *diffs5; 
+	double xnoise2 = 0, xnoise3 = 0, xnoise5 = 0;
+	
+	if (nx < 5) {
+		/* treat entire array as an image with a single row */
+		nx = nx * ny;
+		ny = 1;
+	}
+
+	/* rows must have at least 9 pixels */
+	if (nx < 9) {
+
+		for (ii = 0; ii < nx; ii++) {
+		    if (nullcheck && array[ii] == nullvalue)
+		        continue;
+		    else {
+			if (array[ii] < xminval) xminval = array[ii];
+			if (array[ii] > xmaxval) xmaxval = array[ii];
+			ngoodpix++;
+		    }
+		}
+		if (minval) *minval = xminval;
+		if (maxval) *maxval = xmaxval;
+		if (ngood) *ngood = ngoodpix;
+		if (noise2) *noise2 = 0.;
+		if (noise3) *noise3 = 0.;
+		if (noise5) *noise5 = 0.;
+		return(*status);
+	}
+
+	/* do we need to compute the min and max value? */
+	if (minval || maxval) do_range = 1;
+	
+        /* allocate arrays used to compute the median and noise estimates */
+	differences2 = calloc(nx, sizeof(int));
+	if (!differences2) {
+        	*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+	differences3 = calloc(nx, sizeof(int));
+	if (!differences3) {
+		free(differences2);
+        	*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+	differences5 = calloc(nx, sizeof(int));
+	if (!differences5) {
+		free(differences2);
+		free(differences3);
+        	*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+
+	diffs2 = calloc(ny, sizeof(double));
+	if (!diffs2) {
+		free(differences2);
+		free(differences3);
+		free(differences5);
+        	*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+
+	diffs3 = calloc(ny, sizeof(double));
+	if (!diffs3) {
+		free(differences2);
+		free(differences3);
+		free(differences5);
+		free(diffs2);
+        	*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+
+	diffs5 = calloc(ny, sizeof(double));
+	if (!diffs5) {
+		free(differences2);
+		free(differences3);
+		free(differences5);
+		free(diffs2);
+		free(diffs3);
+        	*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+
+	/* loop over each row of the image */
+	for (jj=0; jj < ny; jj++) {
+
+                rowpix = array + (jj * nx); /* point to first pixel in the row */
+
+		/***** find the first valid pixel in row */
+		ii = 0;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v1 = rowpix[ii];  /* store the good pixel value */
+
+		if (do_range) {
+			if (v1 < xminval) xminval = v1;
+			if (v1 > xmaxval) xmaxval = v1;
+		}
+
+		/***** find the 2nd valid pixel in row (which we will skip over) */
+		ii++;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v2 = rowpix[ii];  /* store the good pixel value */
+		
+		if (do_range) {
+			if (v2 < xminval) xminval = v2;
+			if (v2 > xmaxval) xmaxval = v2;
+		}
+
+		/***** find the 3rd valid pixel in row */
+		ii++;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v3 = rowpix[ii];  /* store the good pixel value */
+
+		if (do_range) {
+			if (v3 < xminval) xminval = v3;
+			if (v3 > xmaxval) xmaxval = v3;
+		}
+				
+		/* find the 4nd valid pixel in row (to be skipped) */
+		ii++;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v4 = rowpix[ii];  /* store the good pixel value */
+
+		if (do_range) {
+			if (v4 < xminval) xminval = v4;
+			if (v4 > xmaxval) xmaxval = v4;
+		}
+			
+		/* find the 5th valid pixel in row (to be skipped) */
+		ii++;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v5 = rowpix[ii];  /* store the good pixel value */
+
+		if (do_range) {
+			if (v5 < xminval) xminval = v5;
+			if (v5 > xmaxval) xmaxval = v5;
+		}
+				
+		/* find the 6th valid pixel in row (to be skipped) */
+		ii++;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v6 = rowpix[ii];  /* store the good pixel value */
+
+		if (do_range) {
+			if (v6 < xminval) xminval = v6;
+			if (v6 > xmaxval) xmaxval = v6;
+		}
+				
+		/* find the 7th valid pixel in row (to be skipped) */
+		ii++;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v7 = rowpix[ii];  /* store the good pixel value */
+
+		if (do_range) {
+			if (v7 < xminval) xminval = v7;
+			if (v7 > xmaxval) xmaxval = v7;
+		}
+				
+		/* find the 8th valid pixel in row (to be skipped) */
+		ii++;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v8 = rowpix[ii];  /* store the good pixel value */
+
+		if (do_range) {
+			if (v8 < xminval) xminval = v8;
+			if (v8 > xmaxval) xmaxval = v8;
+		}
+		/* now populate the differences arrays */
+		/* for the remaining pixels in the row */
+		nvals = 0;
+		nvals2 = 0;
+		for (ii++; ii < nx; ii++) {
+
+		    /* find the next valid pixel in row */
+                    if (nullcheck)
+		        while (ii < nx && rowpix[ii] == nullvalue) ii++;
+		     
+		    if (ii == nx) break;  /* hit end of row */
+		    v9 = rowpix[ii];  /* store the good pixel value */
+
+		    if (do_range) {
+			if (v9 < xminval) xminval = v9;
+			if (v9 > xmaxval) xmaxval = v9;
+		    }
+
+		    /* construct array of absolute differences */
+
+		    if (!(v5 == v6 && v6 == v7) ) {
+		        differences2[nvals2] =  abs((int) v5 - (int) v7);
+			nvals2++;
+		    }
+
+		    if (!(v3 == v4 && v4 == v5 && v5 == v6 && v6 == v7) ) {
+		        differences3[nvals] =  abs((2 * (int) v5) - (int) v3 - (int) v7);
+		        differences5[nvals] =  abs((6 * (int) v5) - (4 * (int) v3) - (4 * (int) v7) + (int) v1 + (int) v9);
+		        nvals++;  
+		    } else {
+		        /* ignore constant background regions */
+			ngoodpix++;
+		    }
+
+		    /* shift over 1 pixel */
+		    v1 = v2;
+		    v2 = v3;
+		    v3 = v4;
+		    v4 = v5;
+		    v5 = v6;
+		    v6 = v7;
+		    v7 = v8;
+		    v8 = v9;
+	        }  /* end of loop over pixels in the row */
+
+		/* compute the median diffs */
+		/* Note that there are 8 more pixel values than there are diffs values. */
+		ngoodpix += (nvals + 8);
+
+		if (nvals == 0) {
+		    continue;  /* cannot compute medians on this row */
+		} else if (nvals == 1) {
+		    if (nvals2 == 1) {
+		        diffs2[nrows2] = differences2[0];
+			nrows2++;
+		    }
+		        
+		    diffs3[nrows] = differences3[0];
+		    diffs5[nrows] = differences5[0];
+		} else {
+                    /* quick_select returns the median MUCH faster than using qsort */
+		    if (nvals2 > 1) {
+                        diffs2[nrows2] = quick_select_int(differences2, nvals);
+			nrows2++;
+		    }
+
+                    diffs3[nrows] = quick_select_int(differences3, nvals);
+                    diffs5[nrows] = quick_select_int(differences5, nvals);
+		}
+
+		nrows++;
+	}  /* end of loop over rows */
+
+	    /* compute median of the values for each row */
+	if (nrows == 0) { 
+	       xnoise3 = 0;
+	       xnoise5 = 0;
+	} else if (nrows == 1) {
+	       xnoise3 = diffs3[0];
+	       xnoise5 = diffs5[0];
+	} else {	    
+	       qsort(diffs3, nrows, sizeof(double), FnCompare_double);
+	       qsort(diffs5, nrows, sizeof(double), FnCompare_double);
+	       xnoise3 =  (diffs3[(nrows - 1)/2] + diffs3[nrows/2]) / 2.;
+	       xnoise5 =  (diffs5[(nrows - 1)/2] + diffs5[nrows/2]) / 2.;
+	}
+
+	if (nrows2 == 0) { 
+	       xnoise2 = 0;
+	} else if (nrows2 == 1) {
+	       xnoise2 = diffs2[0];
+	} else {	    
+	       qsort(diffs2, nrows2, sizeof(double), FnCompare_double);
+	       xnoise2 =  (diffs2[(nrows2 - 1)/2] + diffs2[nrows2/2]) / 2.;
+	}
+
+	if (ngood)  *ngood  = ngoodpix;
+	if (minval) *minval = xminval;
+	if (maxval) *maxval = xmaxval;
+	if (noise2)  *noise2  = 1.0483579 * xnoise2;
+	if (noise3)  *noise3  = 0.6052697 * xnoise3;
+	if (noise5)  *noise5  = 0.1772048 * xnoise5;
+
+	free(diffs5);
+	free(diffs3);
+	free(diffs2);
+	free(differences5);
+	free(differences3);
+	free(differences2);
+
+	return(*status);
+}
+/*--------------------------------------------------------------------------*/
+static int FnNoise5_int
+       (int *array,       /*  2 dimensional array of image pixels */
+        long nx,            /* number of pixels in each row of the image */
+        long ny,            /* number of rows in the image */
+	int nullcheck,      /* check for null values, if true */
+	int nullvalue,    /* value of null pixels, if nullcheck is true */
+   /* returned parameters */   
+	long *ngood,        /* number of good, non-null pixels? */
+	int *minval,    /* minimum non-null value */
+	int *maxval,    /* maximum non-null value */
+	double *noise2,      /* returned 2nd order MAD of all non-null pixels */
+	double *noise3,      /* returned 3rd order MAD of all non-null pixels */
+	double *noise5,      /* returned 5th order MAD of all non-null pixels */
+	int *status)        /* error status */
+
+/*
+Estimate the median and background noise in the input image using 2nd, 3rd and 5th
+order Median Absolute Differences.
+
+The noise in the background of the image is calculated using the MAD algorithms 
+developed for deriving the signal to noise ratio in spectra
+(see issue #42 of the ST-ECF newsletter, http://www.stecf.org/documents/newsletter/)
+
+3rd order:  noise = 1.482602 / sqrt(6) * median (abs(2*flux(i) - flux(i-2) - flux(i+2)))
+
+The returned estimates are the median of the values that are computed for each 
+row of the image.
+*/
+{
+	long ii, jj, nrows = 0, nrows2 = 0, nvals, nvals2, ngoodpix = 0;
+	LONGLONG *differences2, *differences3, *differences5, tdiff;
+	int *rowpix, v1, v2, v3, v4, v5, v6, v7, v8, v9;
+	int xminval = INT_MAX, xmaxval = INT_MIN;
+	int do_range = 0;
+	double *diffs2, *diffs3, *diffs5; 
+	double xnoise2 = 0, xnoise3 = 0, xnoise5 = 0;
+	
+	if (nx < 5) {
+		/* treat entire array as an image with a single row */
+		nx = nx * ny;
+		ny = 1;
+	}
+
+	/* rows must have at least 9 pixels */
+	if (nx < 9) {
+
+		for (ii = 0; ii < nx; ii++) {
+		    if (nullcheck && array[ii] == nullvalue)
+		        continue;
+		    else {
+			if (array[ii] < xminval) xminval = array[ii];
+			if (array[ii] > xmaxval) xmaxval = array[ii];
+			ngoodpix++;
+		    }
+		}
+		if (minval) *minval = xminval;
+		if (maxval) *maxval = xmaxval;
+		if (ngood) *ngood = ngoodpix;
+		if (noise2) *noise2 = 0.;
+		if (noise3) *noise3 = 0.;
+		if (noise5) *noise5 = 0.;
+		return(*status);
+	}
+
+	/* do we need to compute the min and max value? */
+	if (minval || maxval) do_range = 1;
+	
+        /* allocate arrays used to compute the median and noise estimates */
+	differences2 = calloc(nx, sizeof(LONGLONG));
+	if (!differences2) {
+        	*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+	differences3 = calloc(nx, sizeof(LONGLONG));
+	if (!differences3) {
+		free(differences2);
+        	*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+	differences5 = calloc(nx, sizeof(LONGLONG));
+	if (!differences5) {
+		free(differences2);
+		free(differences3);
+        	*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+
+	diffs2 = calloc(ny, sizeof(double));
+	if (!diffs2) {
+		free(differences2);
+		free(differences3);
+		free(differences5);
+        	*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+
+	diffs3 = calloc(ny, sizeof(double));
+	if (!diffs3) {
+		free(differences2);
+		free(differences3);
+		free(differences5);
+		free(diffs2);
+        	*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+
+	diffs5 = calloc(ny, sizeof(double));
+	if (!diffs5) {
+		free(differences2);
+		free(differences3);
+		free(differences5);
+		free(diffs2);
+		free(diffs3);
+        	*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+
+	/* loop over each row of the image */
+	for (jj=0; jj < ny; jj++) {
+
+                rowpix = array + (jj * nx); /* point to first pixel in the row */
+
+		/***** find the first valid pixel in row */
+		ii = 0;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v1 = rowpix[ii];  /* store the good pixel value */
+
+		if (do_range) {
+			if (v1 < xminval) xminval = v1;
+			if (v1 > xmaxval) xmaxval = v1;
+		}
+
+		/***** find the 2nd valid pixel in row (which we will skip over) */
+		ii++;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v2 = rowpix[ii];  /* store the good pixel value */
+		
+		if (do_range) {
+			if (v2 < xminval) xminval = v2;
+			if (v2 > xmaxval) xmaxval = v2;
+		}
+
+		/***** find the 3rd valid pixel in row */
+		ii++;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v3 = rowpix[ii];  /* store the good pixel value */
+
+		if (do_range) {
+			if (v3 < xminval) xminval = v3;
+			if (v3 > xmaxval) xmaxval = v3;
+		}
+				
+		/* find the 4nd valid pixel in row (to be skipped) */
+		ii++;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v4 = rowpix[ii];  /* store the good pixel value */
+
+		if (do_range) {
+			if (v4 < xminval) xminval = v4;
+			if (v4 > xmaxval) xmaxval = v4;
+		}
+			
+		/* find the 5th valid pixel in row (to be skipped) */
+		ii++;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v5 = rowpix[ii];  /* store the good pixel value */
+
+		if (do_range) {
+			if (v5 < xminval) xminval = v5;
+			if (v5 > xmaxval) xmaxval = v5;
+		}
+				
+		/* find the 6th valid pixel in row (to be skipped) */
+		ii++;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v6 = rowpix[ii];  /* store the good pixel value */
+
+		if (do_range) {
+			if (v6 < xminval) xminval = v6;
+			if (v6 > xmaxval) xmaxval = v6;
+		}
+				
+		/* find the 7th valid pixel in row (to be skipped) */
+		ii++;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v7 = rowpix[ii];  /* store the good pixel value */
+
+		if (do_range) {
+			if (v7 < xminval) xminval = v7;
+			if (v7 > xmaxval) xmaxval = v7;
+		}
+				
+		/* find the 8th valid pixel in row (to be skipped) */
+		ii++;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v8 = rowpix[ii];  /* store the good pixel value */
+
+		if (do_range) {
+			if (v8 < xminval) xminval = v8;
+			if (v8 > xmaxval) xmaxval = v8;
+		}
+		/* now populate the differences arrays */
+		/* for the remaining pixels in the row */
+		nvals = 0;
+		nvals2 = 0;
+		for (ii++; ii < nx; ii++) {
+
+		    /* find the next valid pixel in row */
+                    if (nullcheck)
+		        while (ii < nx && rowpix[ii] == nullvalue) ii++;
+		     
+		    if (ii == nx) break;  /* hit end of row */
+		    v9 = rowpix[ii];  /* store the good pixel value */
+
+		    if (do_range) {
+			if (v9 < xminval) xminval = v9;
+			if (v9 > xmaxval) xmaxval = v9;
+		    }
+
+		    /* construct array of absolute differences */
+
+		    if (!(v5 == v6 && v6 == v7) ) {
+		        tdiff =  (LONGLONG) v5 - (LONGLONG) v7;
+			if (tdiff < 0)
+		            differences2[nvals2] =  -1 * tdiff;
+			else
+		            differences2[nvals2] =  tdiff;
+
+			nvals2++;
+		    }
+
+		    if (!(v3 == v4 && v4 == v5 && v5 == v6 && v6 == v7) ) {
+		        tdiff =  (2 * (LONGLONG) v5) - (LONGLONG) v3 - (LONGLONG) v7;
+			if (tdiff < 0)
+		            differences3[nvals] =  -1 * tdiff;
+			else
+		            differences3[nvals] =  tdiff;
+
+		        tdiff =  (6 * (LONGLONG) v5) - (4 * (LONGLONG) v3) - (4 * (LONGLONG) v7) + (LONGLONG) v1 + (LONGLONG) v9;
+			if (tdiff < 0)
+		            differences5[nvals] =  -1 * tdiff;
+			else
+		            differences5[nvals] =  tdiff;
+
+		        nvals++;  
+		    } else {
+		        /* ignore constant background regions */
+			ngoodpix++;
+		    }
+
+		    /* shift over 1 pixel */
+		    v1 = v2;
+		    v2 = v3;
+		    v3 = v4;
+		    v4 = v5;
+		    v5 = v6;
+		    v6 = v7;
+		    v7 = v8;
+		    v8 = v9;
+	        }  /* end of loop over pixels in the row */
+
+		/* compute the median diffs */
+		/* Note that there are 8 more pixel values than there are diffs values. */
+		ngoodpix += (nvals + 8);
+
+		if (nvals == 0) {
+		    continue;  /* cannot compute medians on this row */
+		} else if (nvals == 1) {
+		    if (nvals2 == 1) {
+		        diffs2[nrows2] = (double) differences2[0];
+			nrows2++;
+		    }
+		        
+		    diffs3[nrows] = (double) differences3[0];
+		    diffs5[nrows] = (double) differences5[0];
+		} else {
+                    /* quick_select returns the median MUCH faster than using qsort */
+		    if (nvals2 > 1) {
+                        diffs2[nrows2] = (double) quick_select_longlong(differences2, nvals);
+			nrows2++;
+		    }
+
+                    diffs3[nrows] = (double) quick_select_longlong(differences3, nvals);
+                    diffs5[nrows] = (double) quick_select_longlong(differences5, nvals);
+		}
+
+		nrows++;
+	}  /* end of loop over rows */
+
+	    /* compute median of the values for each row */
+	if (nrows == 0) { 
+	       xnoise3 = 0;
+	       xnoise5 = 0;
+	} else if (nrows == 1) {
+	       xnoise3 = diffs3[0];
+	       xnoise5 = diffs5[0];
+	} else {	    
+	       qsort(diffs3, nrows, sizeof(double), FnCompare_double);
+	       qsort(diffs5, nrows, sizeof(double), FnCompare_double);
+	       xnoise3 =  (diffs3[(nrows - 1)/2] + diffs3[nrows/2]) / 2.;
+	       xnoise5 =  (diffs5[(nrows - 1)/2] + diffs5[nrows/2]) / 2.;
+	}
+
+	if (nrows2 == 0) { 
+	       xnoise2 = 0;
+	} else if (nrows2 == 1) {
+	       xnoise2 = diffs2[0];
+	} else {	    
+	       qsort(diffs2, nrows2, sizeof(double), FnCompare_double);
+	       xnoise2 =  (diffs2[(nrows2 - 1)/2] + diffs2[nrows2/2]) / 2.;
+	}
+
+	if (ngood)  *ngood  = ngoodpix;
+	if (minval) *minval = xminval;
+	if (maxval) *maxval = xmaxval;
+	if (noise2)  *noise2  = 1.0483579 * xnoise2;
+	if (noise3)  *noise3  = 0.6052697 * xnoise3;
+	if (noise5)  *noise5  = 0.1772048 * xnoise5;
+
+	free(diffs5);
+	free(diffs3);
+	free(diffs2);
+	free(differences5);
+	free(differences3);
+	free(differences2);
+
+	return(*status);
+}
+/*--------------------------------------------------------------------------*/
+static int FnNoise5_float
+       (float *array,       /*  2 dimensional array of image pixels */
+        long nx,            /* number of pixels in each row of the image */
+        long ny,            /* number of rows in the image */
+	int nullcheck,      /* check for null values, if true */
+	float nullvalue,    /* value of null pixels, if nullcheck is true */
+   /* returned parameters */   
+	long *ngood,        /* number of good, non-null pixels? */
+	float *minval,    /* minimum non-null value */
+	float *maxval,    /* maximum non-null value */
+	double *noise2,      /* returned 2nd order MAD of all non-null pixels */
+	double *noise3,      /* returned 3rd order MAD of all non-null pixels */
+	double *noise5,      /* returned 5th order MAD of all non-null pixels */
+	int *status)        /* error status */
+
+/*
+Estimate the median and background noise in the input image using 2nd, 3rd and 5th
+order Median Absolute Differences.
+
+The noise in the background of the image is calculated using the MAD algorithms 
+developed for deriving the signal to noise ratio in spectra
+(see issue #42 of the ST-ECF newsletter, http://www.stecf.org/documents/newsletter/)
+
+3rd order:  noise = 1.482602 / sqrt(6) * median (abs(2*flux(i) - flux(i-2) - flux(i+2)))
+
+The returned estimates are the median of the values that are computed for each 
+row of the image.
+*/
+{
+	long ii, jj, nrows = 0, nrows2 = 0, nvals, nvals2, ngoodpix = 0;
+	float *differences2, *differences3, *differences5;
+	float *rowpix, v1, v2, v3, v4, v5, v6, v7, v8, v9;
+	float xminval = FLT_MAX, xmaxval = -FLT_MAX;
+	int do_range = 0;
+	double *diffs2, *diffs3, *diffs5; 
+	double xnoise2 = 0, xnoise3 = 0, xnoise5 = 0;
+	
+	if (nx < 5) {
+		/* treat entire array as an image with a single row */
+		nx = nx * ny;
+		ny = 1;
+	}
+
+	/* rows must have at least 9 pixels */
+	if (nx < 9) {
+
+		for (ii = 0; ii < nx; ii++) {
+		    if (nullcheck && array[ii] == nullvalue)
+		        continue;
+		    else {
+			if (array[ii] < xminval) xminval = array[ii];
+			if (array[ii] > xmaxval) xmaxval = array[ii];
+			ngoodpix++;
+		    }
+		}
+		if (minval) *minval = xminval;
+		if (maxval) *maxval = xmaxval;
+		if (ngood) *ngood = ngoodpix;
+		if (noise2) *noise2 = 0.;
+		if (noise3) *noise3 = 0.;
+		if (noise5) *noise5 = 0.;
+		return(*status);
+	}
+
+	/* do we need to compute the min and max value? */
+	if (minval || maxval) do_range = 1;
+	
+        /* allocate arrays used to compute the median and noise estimates */
+	differences2 = calloc(nx, sizeof(float));
+	if (!differences2) {
+        	*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+	differences3 = calloc(nx, sizeof(float));
+	if (!differences3) {
+		free(differences2);
+        	*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+	differences5 = calloc(nx, sizeof(float));
+	if (!differences5) {
+		free(differences2);
+		free(differences3);
+        	*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+
+	diffs2 = calloc(ny, sizeof(double));
+	if (!diffs2) {
+		free(differences2);
+		free(differences3);
+		free(differences5);
+        	*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+
+	diffs3 = calloc(ny, sizeof(double));
+	if (!diffs3) {
+		free(differences2);
+		free(differences3);
+		free(differences5);
+		free(diffs2);
+        	*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+
+	diffs5 = calloc(ny, sizeof(double));
+	if (!diffs5) {
+		free(differences2);
+		free(differences3);
+		free(differences5);
+		free(diffs2);
+		free(diffs3);
+        	*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+
+	/* loop over each row of the image */
+	for (jj=0; jj < ny; jj++) {
+
+                rowpix = array + (jj * nx); /* point to first pixel in the row */
+
+		/***** find the first valid pixel in row */
+		ii = 0;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v1 = rowpix[ii];  /* store the good pixel value */
+
+		if (do_range) {
+			if (v1 < xminval) xminval = v1;
+			if (v1 > xmaxval) xmaxval = v1;
+		}
+
+		/***** find the 2nd valid pixel in row (which we will skip over) */
+		ii++;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v2 = rowpix[ii];  /* store the good pixel value */
+		
+		if (do_range) {
+			if (v2 < xminval) xminval = v2;
+			if (v2 > xmaxval) xmaxval = v2;
+		}
+
+		/***** find the 3rd valid pixel in row */
+		ii++;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v3 = rowpix[ii];  /* store the good pixel value */
+
+		if (do_range) {
+			if (v3 < xminval) xminval = v3;
+			if (v3 > xmaxval) xmaxval = v3;
+		}
+				
+		/* find the 4nd valid pixel in row (to be skipped) */
+		ii++;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v4 = rowpix[ii];  /* store the good pixel value */
+
+		if (do_range) {
+			if (v4 < xminval) xminval = v4;
+			if (v4 > xmaxval) xmaxval = v4;
+		}
+			
+		/* find the 5th valid pixel in row (to be skipped) */
+		ii++;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v5 = rowpix[ii];  /* store the good pixel value */
+
+		if (do_range) {
+			if (v5 < xminval) xminval = v5;
+			if (v5 > xmaxval) xmaxval = v5;
+		}
+				
+		/* find the 6th valid pixel in row (to be skipped) */
+		ii++;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v6 = rowpix[ii];  /* store the good pixel value */
+
+		if (do_range) {
+			if (v6 < xminval) xminval = v6;
+			if (v6 > xmaxval) xmaxval = v6;
+		}
+				
+		/* find the 7th valid pixel in row (to be skipped) */
+		ii++;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v7 = rowpix[ii];  /* store the good pixel value */
+
+		if (do_range) {
+			if (v7 < xminval) xminval = v7;
+			if (v7 > xmaxval) xmaxval = v7;
+		}
+				
+		/* find the 8th valid pixel in row (to be skipped) */
+		ii++;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v8 = rowpix[ii];  /* store the good pixel value */
+
+		if (do_range) {
+			if (v8 < xminval) xminval = v8;
+			if (v8 > xmaxval) xmaxval = v8;
+		}
+		/* now populate the differences arrays */
+		/* for the remaining pixels in the row */
+		nvals = 0;
+		nvals2 = 0;
+		for (ii++; ii < nx; ii++) {
+
+		    /* find the next valid pixel in row */
+                    if (nullcheck)
+		        while (ii < nx && rowpix[ii] == nullvalue) ii++;
+		     
+		    if (ii == nx) break;  /* hit end of row */
+		    v9 = rowpix[ii];  /* store the good pixel value */
+
+		    if (do_range) {
+			if (v9 < xminval) xminval = v9;
+			if (v9 > xmaxval) xmaxval = v9;
+		    }
+
+		    /* construct array of absolute differences */
+
+		    if (!(v5 == v6 && v6 == v7) ) {
+		        differences2[nvals2] = (float) fabs(v5 - v7);
+			nvals2++;
+		    }
+
+		    if (!(v3 == v4 && v4 == v5 && v5 == v6 && v6 == v7) ) {
+		        differences3[nvals] = (float) fabs((2 * v5) - v3 - v7);
+		        differences5[nvals] = (float) fabs((6 * v5) - (4 * v3) - (4 * v7) + v1 + v9);
+		        nvals++;  
+		    } else {
+		        /* ignore constant background regions */
+			ngoodpix++;
+		    }
+
+		    /* shift over 1 pixel */
+		    v1 = v2;
+		    v2 = v3;
+		    v3 = v4;
+		    v4 = v5;
+		    v5 = v6;
+		    v6 = v7;
+		    v7 = v8;
+		    v8 = v9;
+	        }  /* end of loop over pixels in the row */
+
+		/* compute the median diffs */
+		/* Note that there are 8 more pixel values than there are diffs values. */
+		ngoodpix += (nvals + 8);
+
+		if (nvals == 0) {
+		    continue;  /* cannot compute medians on this row */
+		} else if (nvals == 1) {
+		    if (nvals2 == 1) {
+		        diffs2[nrows2] = differences2[0];
+			nrows2++;
+		    }
+		        
+		    diffs3[nrows] = differences3[0];
+		    diffs5[nrows] = differences5[0];
+		} else {
+                    /* quick_select returns the median MUCH faster than using qsort */
+		    if (nvals2 > 1) {
+                        diffs2[nrows2] = quick_select_float(differences2, nvals);
+			nrows2++;
+		    }
+
+                    diffs3[nrows] = quick_select_float(differences3, nvals);
+                    diffs5[nrows] = quick_select_float(differences5, nvals);
+		}
+
+		nrows++;
+	}  /* end of loop over rows */
+
+	    /* compute median of the values for each row */
+	if (nrows == 0) { 
+	       xnoise3 = 0;
+	       xnoise5 = 0;
+	} else if (nrows == 1) {
+	       xnoise3 = diffs3[0];
+	       xnoise5 = diffs5[0];
+	} else {	    
+	       qsort(diffs3, nrows, sizeof(double), FnCompare_double);
+	       qsort(diffs5, nrows, sizeof(double), FnCompare_double);
+	       xnoise3 =  (diffs3[(nrows - 1)/2] + diffs3[nrows/2]) / 2.;
+	       xnoise5 =  (diffs5[(nrows - 1)/2] + diffs5[nrows/2]) / 2.;
+	}
+
+	if (nrows2 == 0) { 
+	       xnoise2 = 0;
+	} else if (nrows2 == 1) {
+	       xnoise2 = diffs2[0];
+	} else {	    
+	       qsort(diffs2, nrows2, sizeof(double), FnCompare_double);
+	       xnoise2 =  (diffs2[(nrows2 - 1)/2] + diffs2[nrows2/2]) / 2.;
+	}
+
+	if (ngood)  *ngood  = ngoodpix;
+	if (minval) *minval = xminval;
+	if (maxval) *maxval = xmaxval;
+	if (noise2)  *noise2  = 1.0483579 * xnoise2;
+	if (noise3)  *noise3  = 0.6052697 * xnoise3;
+	if (noise5)  *noise5  = 0.1772048 * xnoise5;
+
+	free(diffs5);
+	free(diffs3);
+	free(diffs2);
+	free(differences5);
+	free(differences3);
+	free(differences2);
+
+	return(*status);
+}
+/*--------------------------------------------------------------------------*/
+static int FnNoise5_double
+       (double *array,       /*  2 dimensional array of image pixels */
+        long nx,            /* number of pixels in each row of the image */
+        long ny,            /* number of rows in the image */
+	int nullcheck,      /* check for null values, if true */
+	double nullvalue,    /* value of null pixels, if nullcheck is true */
+   /* returned parameters */   
+	long *ngood,        /* number of good, non-null pixels? */
+	double *minval,    /* minimum non-null value */
+	double *maxval,    /* maximum non-null value */
+	double *noise2,      /* returned 2nd order MAD of all non-null pixels */
+	double *noise3,      /* returned 3rd order MAD of all non-null pixels */
+	double *noise5,      /* returned 5th order MAD of all non-null pixels */
+	int *status)        /* error status */
+
+/*
+Estimate the median and background noise in the input image using 2nd, 3rd and 5th
+order Median Absolute Differences.
+
+The noise in the background of the image is calculated using the MAD algorithms 
+developed for deriving the signal to noise ratio in spectra
+(see issue #42 of the ST-ECF newsletter, http://www.stecf.org/documents/newsletter/)
+
+3rd order:  noise = 1.482602 / sqrt(6) * median (abs(2*flux(i) - flux(i-2) - flux(i+2)))
+
+The returned estimates are the median of the values that are computed for each 
+row of the image.
+*/
+{
+	long ii, jj, nrows = 0, nrows2 = 0, nvals, nvals2, ngoodpix = 0;
+	double *differences2, *differences3, *differences5;
+	double *rowpix, v1, v2, v3, v4, v5, v6, v7, v8, v9;
+	double xminval = DBL_MAX, xmaxval = -DBL_MAX;
+	int do_range = 0;
+	double *diffs2, *diffs3, *diffs5; 
+	double xnoise2 = 0, xnoise3 = 0, xnoise5 = 0;
+	
+	if (nx < 5) {
+		/* treat entire array as an image with a single row */
+		nx = nx * ny;
+		ny = 1;
+	}
+
+	/* rows must have at least 9 pixels */
+	if (nx < 9) {
+
+		for (ii = 0; ii < nx; ii++) {
+		    if (nullcheck && array[ii] == nullvalue)
+		        continue;
+		    else {
+			if (array[ii] < xminval) xminval = array[ii];
+			if (array[ii] > xmaxval) xmaxval = array[ii];
+			ngoodpix++;
+		    }
+		}
+		if (minval) *minval = xminval;
+		if (maxval) *maxval = xmaxval;
+		if (ngood) *ngood = ngoodpix;
+		if (noise2) *noise2 = 0.;
+		if (noise3) *noise3 = 0.;
+		if (noise5) *noise5 = 0.;
+		return(*status);
+	}
+
+	/* do we need to compute the min and max value? */
+	if (minval || maxval) do_range = 1;
+	
+        /* allocate arrays used to compute the median and noise estimates */
+	differences2 = calloc(nx, sizeof(double));
+	if (!differences2) {
+        	*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+	differences3 = calloc(nx, sizeof(double));
+	if (!differences3) {
+		free(differences2);
+        	*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+	differences5 = calloc(nx, sizeof(double));
+	if (!differences5) {
+		free(differences2);
+		free(differences3);
+        	*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+
+	diffs2 = calloc(ny, sizeof(double));
+	if (!diffs2) {
+		free(differences2);
+		free(differences3);
+		free(differences5);
+        	*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+
+	diffs3 = calloc(ny, sizeof(double));
+	if (!diffs3) {
+		free(differences2);
+		free(differences3);
+		free(differences5);
+		free(diffs2);
+        	*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+
+	diffs5 = calloc(ny, sizeof(double));
+	if (!diffs5) {
+		free(differences2);
+		free(differences3);
+		free(differences5);
+		free(diffs2);
+		free(diffs3);
+        	*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+
+	/* loop over each row of the image */
+	for (jj=0; jj < ny; jj++) {
+
+                rowpix = array + (jj * nx); /* point to first pixel in the row */
+
+		/***** find the first valid pixel in row */
+		ii = 0;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v1 = rowpix[ii];  /* store the good pixel value */
+
+		if (do_range) {
+			if (v1 < xminval) xminval = v1;
+			if (v1 > xmaxval) xmaxval = v1;
+		}
+
+		/***** find the 2nd valid pixel in row (which we will skip over) */
+		ii++;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v2 = rowpix[ii];  /* store the good pixel value */
+		
+		if (do_range) {
+			if (v2 < xminval) xminval = v2;
+			if (v2 > xmaxval) xmaxval = v2;
+		}
+
+		/***** find the 3rd valid pixel in row */
+		ii++;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v3 = rowpix[ii];  /* store the good pixel value */
+
+		if (do_range) {
+			if (v3 < xminval) xminval = v3;
+			if (v3 > xmaxval) xmaxval = v3;
+		}
+				
+		/* find the 4nd valid pixel in row (to be skipped) */
+		ii++;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v4 = rowpix[ii];  /* store the good pixel value */
+
+		if (do_range) {
+			if (v4 < xminval) xminval = v4;
+			if (v4 > xmaxval) xmaxval = v4;
+		}
+			
+		/* find the 5th valid pixel in row (to be skipped) */
+		ii++;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v5 = rowpix[ii];  /* store the good pixel value */
+
+		if (do_range) {
+			if (v5 < xminval) xminval = v5;
+			if (v5 > xmaxval) xmaxval = v5;
+		}
+				
+		/* find the 6th valid pixel in row (to be skipped) */
+		ii++;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v6 = rowpix[ii];  /* store the good pixel value */
+
+		if (do_range) {
+			if (v6 < xminval) xminval = v6;
+			if (v6 > xmaxval) xmaxval = v6;
+		}
+				
+		/* find the 7th valid pixel in row (to be skipped) */
+		ii++;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v7 = rowpix[ii];  /* store the good pixel value */
+
+		if (do_range) {
+			if (v7 < xminval) xminval = v7;
+			if (v7 > xmaxval) xmaxval = v7;
+		}
+				
+		/* find the 8th valid pixel in row (to be skipped) */
+		ii++;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v8 = rowpix[ii];  /* store the good pixel value */
+
+		if (do_range) {
+			if (v8 < xminval) xminval = v8;
+			if (v8 > xmaxval) xmaxval = v8;
+		}
+		/* now populate the differences arrays */
+		/* for the remaining pixels in the row */
+		nvals = 0;
+		nvals2 = 0;
+		for (ii++; ii < nx; ii++) {
+
+		    /* find the next valid pixel in row */
+                    if (nullcheck)
+		        while (ii < nx && rowpix[ii] == nullvalue) ii++;
+		     
+		    if (ii == nx) break;  /* hit end of row */
+		    v9 = rowpix[ii];  /* store the good pixel value */
+
+		    if (do_range) {
+			if (v9 < xminval) xminval = v9;
+			if (v9 > xmaxval) xmaxval = v9;
+		    }
+
+		    /* construct array of absolute differences */
+
+		    if (!(v5 == v6 && v6 == v7) ) {
+		        differences2[nvals2] =  fabs(v5 - v7);
+			nvals2++;
+		    }
+
+		    if (!(v3 == v4 && v4 == v5 && v5 == v6 && v6 == v7) ) {
+		        differences3[nvals] =  fabs((2 * v5) - v3 - v7);
+		        differences5[nvals] =  fabs((6 * v5) - (4 * v3) - (4 * v7) + v1 + v9);
+		        nvals++;  
+		    } else {
+		        /* ignore constant background regions */
+			ngoodpix++;
+		    }
+
+		    /* shift over 1 pixel */
+		    v1 = v2;
+		    v2 = v3;
+		    v3 = v4;
+		    v4 = v5;
+		    v5 = v6;
+		    v6 = v7;
+		    v7 = v8;
+		    v8 = v9;
+	        }  /* end of loop over pixels in the row */
+
+		/* compute the median diffs */
+		/* Note that there are 8 more pixel values than there are diffs values. */
+		ngoodpix += (nvals + 8);
+
+		if (nvals == 0) {
+		    continue;  /* cannot compute medians on this row */
+		} else if (nvals == 1) {
+		    if (nvals2 == 1) {
+		        diffs2[nrows2] = differences2[0];
+			nrows2++;
+		    }
+		        
+		    diffs3[nrows] = differences3[0];
+		    diffs5[nrows] = differences5[0];
+		} else {
+                    /* quick_select returns the median MUCH faster than using qsort */
+		    if (nvals2 > 1) {
+                        diffs2[nrows2] = quick_select_double(differences2, nvals);
+			nrows2++;
+		    }
+
+                    diffs3[nrows] = quick_select_double(differences3, nvals);
+                    diffs5[nrows] = quick_select_double(differences5, nvals);
+		}
+
+		nrows++;
+	}  /* end of loop over rows */
+
+	    /* compute median of the values for each row */
+	if (nrows == 0) { 
+	       xnoise3 = 0;
+	       xnoise5 = 0;
+	} else if (nrows == 1) {
+	       xnoise3 = diffs3[0];
+	       xnoise5 = diffs5[0];
+	} else {	    
+	       qsort(diffs3, nrows, sizeof(double), FnCompare_double);
+	       qsort(diffs5, nrows, sizeof(double), FnCompare_double);
+	       xnoise3 =  (diffs3[(nrows - 1)/2] + diffs3[nrows/2]) / 2.;
+	       xnoise5 =  (diffs5[(nrows - 1)/2] + diffs5[nrows/2]) / 2.;
+	}
+
+	if (nrows2 == 0) { 
+	       xnoise2 = 0;
+	} else if (nrows2 == 1) {
+	       xnoise2 = diffs2[0];
+	} else {	    
+	       qsort(diffs2, nrows2, sizeof(double), FnCompare_double);
+	       xnoise2 =  (diffs2[(nrows2 - 1)/2] + diffs2[nrows2/2]) / 2.;
+	}
+
+	if (ngood)  *ngood  = ngoodpix;
+	if (minval) *minval = xminval;
+	if (maxval) *maxval = xmaxval;
+	if (noise2)  *noise2  = 1.0483579 * xnoise2;
+	if (noise3)  *noise3  = 0.6052697 * xnoise3;
+	if (noise5)  *noise5  = 0.1772048 * xnoise5;
+
+	free(diffs5);
+	free(diffs3);
+	free(diffs2);
+	free(differences5);
+	free(differences3);
+	free(differences2);
+
+	return(*status);
+}
+/*--------------------------------------------------------------------------*/
+static int FnNoise3_short
+       (short *array,       /*  2 dimensional array of image pixels */
+        long nx,            /* number of pixels in each row of the image */
+        long ny,            /* number of rows in the image */
+	int nullcheck,      /* check for null values, if true */
+	short nullvalue,    /* value of null pixels, if nullcheck is true */
+   /* returned parameters */   
+	long *ngood,        /* number of good, non-null pixels? */
+	short *minval,    /* minimum non-null value */
+	short *maxval,    /* maximum non-null value */
+	double *noise,      /* returned R.M.S. value of all non-null pixels */
+	int *status)        /* error status */
+
+/*
+Estimate the median and background noise in the input image using 3rd order differences.
+
+The noise in the background of the image is calculated using the 3rd order algorithm 
+developed for deriving the signal to noise ratio in spectra
+(see issue #42 of the ST-ECF newsletter, http://www.stecf.org/documents/newsletter/)
+
+  noise = 1.482602 / sqrt(6) * median (abs(2*flux(i) - flux(i-2) - flux(i+2)))
+
+The returned estimates are the median of the values that are computed for each 
+row of the image.
+*/
+{
+	long ii, jj, nrows = 0, nvals, ngoodpix = 0;
+	short *differences, *rowpix, v1, v2, v3, v4, v5;
+	short xminval = SHRT_MAX, xmaxval = SHRT_MIN, do_range = 0;
+	double *diffs, xnoise = 0, sigma;
+
+	if (nx < 5) {
+		/* treat entire array as an image with a single row */
+		nx = nx * ny;
+		ny = 1;
+	}
+
+	/* rows must have at least 5 pixels */
+	if (nx < 5) {
+
+		for (ii = 0; ii < nx; ii++) {
+		    if (nullcheck && array[ii] == nullvalue)
+		        continue;
+		    else {
+			if (array[ii] < xminval) xminval = array[ii];
+			if (array[ii] > xmaxval) xmaxval = array[ii];
+			ngoodpix++;
+		    }
+		}
+		if (minval) *minval = xminval;
+		if (maxval) *maxval = xmaxval;
+		if (ngood) *ngood = ngoodpix;
+		if (noise) *noise = 0.;
+		return(*status);
+	}
+
+	/* do we need to compute the min and max value? */
+	if (minval || maxval) do_range = 1;
+	
+        /* allocate arrays used to compute the median and noise estimates */
+	differences = calloc(nx, sizeof(short));
+	if (!differences) {
+        	*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+
+	diffs = calloc(ny, sizeof(double));
+	if (!diffs) {
+		free(differences);
+        	*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+
+	/* loop over each row of the image */
+	for (jj=0; jj < ny; jj++) {
+
+                rowpix = array + (jj * nx); /* point to first pixel in the row */
+
+		/***** find the first valid pixel in row */
+		ii = 0;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v1 = rowpix[ii];  /* store the good pixel value */
+
+		if (do_range) {
+			if (v1 < xminval) xminval = v1;
+			if (v1 > xmaxval) xmaxval = v1;
+		}
+
+		/***** find the 2nd valid pixel in row (which we will skip over) */
+		ii++;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v2 = rowpix[ii];  /* store the good pixel value */
+		
+		if (do_range) {
+			if (v2 < xminval) xminval = v2;
+			if (v2 > xmaxval) xmaxval = v2;
+		}
+
+		/***** find the 3rd valid pixel in row */
+		ii++;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v3 = rowpix[ii];  /* store the good pixel value */
+
+		if (do_range) {
+			if (v3 < xminval) xminval = v3;
+			if (v3 > xmaxval) xmaxval = v3;
+		}
+				
+		/* find the 4nd valid pixel in row (to be skipped) */
+		ii++;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v4 = rowpix[ii];  /* store the good pixel value */
+
+		if (do_range) {
+			if (v4 < xminval) xminval = v4;
+			if (v4 > xmaxval) xmaxval = v4;
+		}
+		
+		/* now populate the differences arrays */
+		/* for the remaining pixels in the row */
+		nvals = 0;
+		for (ii++; ii < nx; ii++) {
+
+		    /* find the next valid pixel in row */
+                    if (nullcheck)
+		        while (ii < nx && rowpix[ii] == nullvalue) ii++;
+		     
+		    if (ii == nx) break;  /* hit end of row */
+		    v5 = rowpix[ii];  /* store the good pixel value */
+
+		    if (do_range) {
+			if (v5 < xminval) xminval = v5;
+			if (v5 > xmaxval) xmaxval = v5;
+		    }
+
+		    /* construct array of 3rd order absolute differences */
+		    if (!(v1 == v2 && v2 == v3 && v3 == v4 && v4 == v5)) {
+		        differences[nvals] = abs((2 * v3) - v1 - v5);
+		        nvals++;  
+		    } else {
+		        /* ignore constant background regions */
+			ngoodpix++;
+		    }
+
+
+		    /* shift over 1 pixel */
+		    v1 = v2;
+		    v2 = v3;
+		    v3 = v4;
+		    v4 = v5;
+	        }  /* end of loop over pixels in the row */
+
+		/* compute the 3rd order diffs */
+		/* Note that there are 4 more pixel values than there are diffs values. */
+		ngoodpix += (nvals + 4);
+
+		if (nvals == 0) {
+		    continue;  /* cannot compute medians on this row */
+		} else if (nvals == 1) {
+		    diffs[nrows] = differences[0];
+		} else {
+                    /* quick_select returns the median MUCH faster than using qsort */
+                    diffs[nrows] = quick_select_short(differences, nvals);
+		}
+
+		nrows++;
+	}  /* end of loop over rows */
+
+	    /* compute median of the values for each row */
+	if (nrows == 0) { 
+	       xnoise = 0;
+	} else if (nrows == 1) {
+	       xnoise = diffs[0];
+	} else {	    
+
+
+	       qsort(diffs, nrows, sizeof(double), FnCompare_double);
+	       xnoise =  (diffs[(nrows - 1)/2] + diffs[nrows/2]) / 2.;
+
+              FnMeanSigma_double(diffs, nrows, 0, 0.0, 0, &xnoise, &sigma, status); 
+
+	      /* do a 4.5 sigma rejection of outliers */
+	      jj = 0;
+	      sigma = 4.5 * sigma;
+	      for (ii = 0; ii < nrows; ii++) {
+		if ( fabs(diffs[ii] - xnoise) <= sigma)	 {
+		   if (jj != ii)
+		       diffs[jj] = diffs[ii];
+		   jj++;
+	        } 
+	      }
+	      if (ii != jj)
+                FnMeanSigma_double(diffs, jj, 0, 0.0, 0, &xnoise, &sigma, status); 
+	}
+
+	if (ngood)  *ngood  = ngoodpix;
+	if (minval) *minval = xminval;
+	if (maxval) *maxval = xmaxval;
+	if (noise)  *noise  = 0.6052697 * xnoise;
+
+	free(diffs);
+	free(differences);
+
+	return(*status);
+}
+/*--------------------------------------------------------------------------*/
+static int FnNoise3_int
+       (int *array,       /*  2 dimensional array of image pixels */
+        long nx,            /* number of pixels in each row of the image */
+        long ny,            /* number of rows in the image */
+	int nullcheck,      /* check for null values, if true */
+	int nullvalue,    /* value of null pixels, if nullcheck is true */
+   /* returned parameters */   
+	long *ngood,        /* number of good, non-null pixels? */
+	int *minval,    /* minimum non-null value */
+	int *maxval,    /* maximum non-null value */
+	double *noise,      /* returned R.M.S. value of all non-null pixels */
+	int *status)        /* error status */
+
+/*
+Estimate the background noise in the input image using 3rd order differences.
+
+The noise in the background of the image is calculated using the 3rd order algorithm 
+developed for deriving the signal to noise ratio in spectra
+(see issue #42 of the ST-ECF newsletter, http://www.stecf.org/documents/newsletter/)
+
+  noise = 1.482602 / sqrt(6) * median (abs(2*flux(i) - flux(i-2) - flux(i+2)))
+
+The returned estimates are the median of the values that are computed for each 
+row of the image.
+*/
+{
+	long ii, jj, nrows = 0, nvals, ngoodpix = 0;
+	int *differences, *rowpix, v1, v2, v3, v4, v5;
+	int xminval = INT_MAX, xmaxval = INT_MIN, do_range = 0;
+	double *diffs, xnoise = 0, sigma;
+	
+	if (nx < 5) {
+		/* treat entire array as an image with a single row */
+		nx = nx * ny;
+		ny = 1;
+	}
+
+	/* rows must have at least 5 pixels */
+	if (nx < 5) {
+
+		for (ii = 0; ii < nx; ii++) {
+		    if (nullcheck && array[ii] == nullvalue)
+		        continue;
+		    else {
+			if (array[ii] < xminval) xminval = array[ii];
+			if (array[ii] > xmaxval) xmaxval = array[ii];
+			ngoodpix++;
+		    }
+		}
+		if (minval) *minval = xminval;
+		if (maxval) *maxval = xmaxval;
+		if (ngood) *ngood = ngoodpix;
+		if (noise) *noise = 0.;
+		return(*status);
+	}
+
+	/* do we need to compute the min and max value? */
+	if (minval || maxval) do_range = 1;
+	
+        /* allocate arrays used to compute the median and noise estimates */
+	differences = calloc(nx, sizeof(int));
+	if (!differences) {
+        	*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+
+	diffs = calloc(ny, sizeof(double));
+	if (!diffs) {
+		free(differences);
+        	*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+
+	/* loop over each row of the image */
+	for (jj=0; jj < ny; jj++) {
+
+                rowpix = array + (jj * nx); /* point to first pixel in the row */
+
+		/***** find the first valid pixel in row */
+		ii = 0;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v1 = rowpix[ii];  /* store the good pixel value */
+
+		if (do_range) {
+			if (v1 < xminval) xminval = v1;
+			if (v1 > xmaxval) xmaxval = v1;
+		}
+
+		/***** find the 2nd valid pixel in row (which we will skip over) */
+		ii++;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v2 = rowpix[ii];  /* store the good pixel value */
+		
+		if (do_range) {
+			if (v2 < xminval) xminval = v2;
+			if (v2 > xmaxval) xmaxval = v2;
+		}
+
+		/***** find the 3rd valid pixel in row */
+		ii++;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v3 = rowpix[ii];  /* store the good pixel value */
+
+		if (do_range) {
+			if (v3 < xminval) xminval = v3;
+			if (v3 > xmaxval) xmaxval = v3;
+		}
+				
+		/* find the 4nd valid pixel in row (to be skipped) */
+		ii++;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v4 = rowpix[ii];  /* store the good pixel value */
+
+		if (do_range) {
+			if (v4 < xminval) xminval = v4;
+			if (v4 > xmaxval) xmaxval = v4;
+		}
+		
+		/* now populate the differences arrays */
+		/* for the remaining pixels in the row */
+		nvals = 0;
+		for (ii++; ii < nx; ii++) {
+
+		    /* find the next valid pixel in row */
+                    if (nullcheck)
+		        while (ii < nx && rowpix[ii] == nullvalue) ii++;
+		     
+		    if (ii == nx) break;  /* hit end of row */
+		    v5 = rowpix[ii];  /* store the good pixel value */
+
+		    if (do_range) {
+			if (v5 < xminval) xminval = v5;
+			if (v5 > xmaxval) xmaxval = v5;
+		    }
+
+		    /* construct array of 3rd order absolute differences */
+		    if (!(v1 == v2 && v2 == v3 && v3 == v4 && v4 == v5)) {
+		        differences[nvals] = abs((2 * v3) - v1 - v5);
+		        nvals++;  
+		    } else {
+		        /* ignore constant background regions */
+			ngoodpix++;
+		    }
+
+		    /* shift over 1 pixel */
+		    v1 = v2;
+		    v2 = v3;
+		    v3 = v4;
+		    v4 = v5;
+	        }  /* end of loop over pixels in the row */
+
+		/* compute the 3rd order diffs */
+		/* Note that there are 4 more pixel values than there are diffs values. */
+		ngoodpix += (nvals + 4);
+
+		if (nvals == 0) {
+		    continue;  /* cannot compute medians on this row */
+		} else if (nvals == 1) {
+		    diffs[nrows] = differences[0];
+		} else {
+                    /* quick_select returns the median MUCH faster than using qsort */
+                    diffs[nrows] = quick_select_int(differences, nvals);
+		}
+
+		nrows++;
+	}  /* end of loop over rows */
+
+	    /* compute median of the values for each row */
+	if (nrows == 0) { 
+	       xnoise = 0;
+	} else if (nrows == 1) {
+	       xnoise = diffs[0];
+	} else {	    
+
+	       qsort(diffs, nrows, sizeof(double), FnCompare_double);
+	       xnoise =  (diffs[(nrows - 1)/2] + diffs[nrows/2]) / 2.;
+
+              FnMeanSigma_double(diffs, nrows, 0, 0.0, 0, &xnoise, &sigma, status); 
+
+	      /* do a 4.5 sigma rejection of outliers */
+	      jj = 0;
+	      sigma = 4.5 * sigma;
+	      for (ii = 0; ii < nrows; ii++) {
+		if ( fabs(diffs[ii] - xnoise) <= sigma)	 {
+		   if (jj != ii)
+		       diffs[jj] = diffs[ii];
+		   jj++;
+	        }
+	      }
+	      if (ii != jj)
+                FnMeanSigma_double(diffs, jj, 0, 0.0, 0, &xnoise, &sigma, status); 
+	}
+
+	if (ngood)  *ngood  = ngoodpix;
+	if (minval) *minval = xminval;
+	if (maxval) *maxval = xmaxval;
+	if (noise)  *noise  = 0.6052697 * xnoise;
+
+	free(diffs);
+	free(differences);
+
+	return(*status);
+}
+/*--------------------------------------------------------------------------*/
+static int FnNoise3_float
+       (float *array,       /*  2 dimensional array of image pixels */
+        long nx,            /* number of pixels in each row of the image */
+        long ny,            /* number of rows in the image */
+	int nullcheck,      /* check for null values, if true */
+	float nullvalue,    /* value of null pixels, if nullcheck is true */
+   /* returned parameters */   
+	long *ngood,        /* number of good, non-null pixels? */
+	float *minval,    /* minimum non-null value */
+	float *maxval,    /* maximum non-null value */
+	double *noise,      /* returned R.M.S. value of all non-null pixels */
+	int *status)        /* error status */
+
+/*
+Estimate the median and background noise in the input image using 3rd order differences.
+
+The noise in the background of the image is calculated using the 3rd order algorithm 
+developed for deriving the signal to noise ratio in spectra
+(see issue #42 of the ST-ECF newsletter, http://www.stecf.org/documents/newsletter/)
+
+  noise = 1.482602 / sqrt(6) * median (abs(2*flux(i) - flux(i-2) - flux(i+2)))
+
+The returned estimates are the median of the values that are computed for each 
+row of the image.
+*/
+{
+	long ii, jj, nrows = 0, nvals, ngoodpix = 0;
+	float *differences, *rowpix, v1, v2, v3, v4, v5;
+	float xminval = FLT_MAX, xmaxval = -FLT_MAX;
+	int do_range = 0;
+	double *diffs, xnoise = 0;
+
+	if (nx < 5) {
+		/* treat entire array as an image with a single row */
+		nx = nx * ny;
+		ny = 1;
+	}
+
+	/* rows must have at least 5 pixels to calc noise, so just calc min, max, ngood */
+	if (nx < 5) {
+
+		for (ii = 0; ii < nx; ii++) {
+		    if (nullcheck && array[ii] == nullvalue)
+		        continue;
+		    else {
+			if (array[ii] < xminval) xminval = array[ii];
+			if (array[ii] > xmaxval) xmaxval = array[ii];
+			ngoodpix++;
+		    }
+		}
+		if (minval) *minval = xminval;
+		if (maxval) *maxval = xmaxval;
+		if (ngood) *ngood = ngoodpix;
+		if (noise) *noise = 0.;
+		return(*status);
+	}
+
+	/* do we need to compute the min and max value? */
+	if (minval || maxval) do_range = 1;
+	
+        /* allocate arrays used to compute the median and noise estimates */
+	if (noise) {
+	    differences = calloc(nx, sizeof(float));
+	    if (!differences) {
+        	*status = MEMORY_ALLOCATION;
+		return(*status);
+	    }
+
+	    diffs = calloc(ny, sizeof(double));
+	    if (!diffs) {
+		free(differences);
+        	*status = MEMORY_ALLOCATION;
+		return(*status);
+	    }
+	}
+
+	/* loop over each row of the image */
+	for (jj=0; jj < ny; jj++) {
+
+                rowpix = array + (jj * nx); /* point to first pixel in the row */
+
+		/***** find the first valid pixel in row */
+		ii = 0;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v1 = rowpix[ii];  /* store the good pixel value */
+
+		if (do_range) {
+			if (v1 < xminval) xminval = v1;
+			if (v1 > xmaxval) xmaxval = v1;
+		}
+
+		/***** find the 2nd valid pixel in row (which we will skip over) */
+		ii++;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v2 = rowpix[ii];  /* store the good pixel value */
+		
+		if (do_range) {
+			if (v2 < xminval) xminval = v2;
+			if (v2 > xmaxval) xmaxval = v2;
+		}
+
+		/***** find the 3rd valid pixel in row */
+		ii++;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v3 = rowpix[ii];  /* store the good pixel value */
+
+		if (do_range) {
+			if (v3 < xminval) xminval = v3;
+			if (v3 > xmaxval) xmaxval = v3;
+		}
+				
+		/* find the 4nd valid pixel in row (to be skipped) */
+		ii++;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v4 = rowpix[ii];  /* store the good pixel value */
+
+		if (do_range) {
+			if (v4 < xminval) xminval = v4;
+			if (v4 > xmaxval) xmaxval = v4;
+		}
+		
+		/* now populate the differences arrays */
+		/* for the remaining pixels in the row */
+		nvals = 0;
+		for (ii++; ii < nx; ii++) {
+
+		    /* find the next valid pixel in row */
+                    if (nullcheck)
+		        while (ii < nx && rowpix[ii] == nullvalue) {
+			  ii++;
+		        }
+			
+		    if (ii == nx) break;  /* hit end of row */
+		    v5 = rowpix[ii];  /* store the good pixel value */
+
+		    if (do_range) {
+			if (v5 < xminval) xminval = v5;
+			if (v5 > xmaxval) xmaxval = v5;
+		    }
+
+		    /* construct array of 3rd order absolute differences */
+		    if (noise) {
+		        if (!(v1 == v2 && v2 == v3 && v3 == v4 && v4 == v5)) {
+
+		            differences[nvals] = (float) fabs((2. * v3) - v1 - v5);
+		            nvals++;  
+		       } else {
+		            /* ignore constant background regions */
+			    ngoodpix++;
+		       }
+		    } else {
+		       /* just increment the number of non-null pixels */
+		       ngoodpix++;
+		    }
+
+		    /* shift over 1 pixel */
+		    v1 = v2;
+		    v2 = v3;
+		    v3 = v4;
+		    v4 = v5;
+	        }  /* end of loop over pixels in the row */
+
+		/* compute the 3rd order diffs */
+		/* Note that there are 4 more pixel values than there are diffs values. */
+		ngoodpix += (nvals + 4);
+
+		if (noise) {
+		    if (nvals == 0) {
+		        continue;  /* cannot compute medians on this row */
+		    } else if (nvals == 1) {
+		        diffs[nrows] = differences[0];
+		    } else {
+                        /* quick_select returns the median MUCH faster than using qsort */
+                        diffs[nrows] = quick_select_float(differences, nvals);
+		    }
+		}
+		nrows++;
+	}  /* end of loop over rows */
+
+	    /* compute median of the values for each row */
+	if (noise) {
+	    if (nrows == 0) { 
+	       xnoise = 0;
+	    } else if (nrows == 1) {
+	       xnoise = diffs[0];
+	    } else {	    
+	       qsort(diffs, nrows, sizeof(double), FnCompare_double);
+	       xnoise =  (diffs[(nrows - 1)/2] + diffs[nrows/2]) / 2.;
+	    }
+	}
+
+	if (ngood)  *ngood  = ngoodpix;
+	if (minval) *minval = xminval;
+	if (maxval) *maxval = xmaxval;
+	if (noise) {
+		*noise  = 0.6052697 * xnoise;
+		free(diffs);
+		free(differences);
+	}
+
+	return(*status);
+}
+/*--------------------------------------------------------------------------*/
+static int FnNoise3_double
+       (double *array,       /*  2 dimensional array of image pixels */
+        long nx,            /* number of pixels in each row of the image */
+        long ny,            /* number of rows in the image */
+	int nullcheck,      /* check for null values, if true */
+	double nullvalue,    /* value of null pixels, if nullcheck is true */
+   /* returned parameters */   
+	long *ngood,        /* number of good, non-null pixels? */
+	double *minval,    /* minimum non-null value */
+	double *maxval,    /* maximum non-null value */
+	double *noise,      /* returned R.M.S. value of all non-null pixels */
+	int *status)        /* error status */
+
+/*
+Estimate the median and background noise in the input image using 3rd order differences.
+
+The noise in the background of the image is calculated using the 3rd order algorithm 
+developed for deriving the signal to noise ratio in spectra
+(see issue #42 of the ST-ECF newsletter, http://www.stecf.org/documents/newsletter/)
+
+  noise = 1.482602 / sqrt(6) * median (abs(2*flux(i) - flux(i-2) - flux(i+2)))
+
+The returned estimates are the median of the values that are computed for each 
+row of the image.
+*/
+{
+	long ii, jj, nrows = 0, nvals, ngoodpix = 0;
+	double *differences, *rowpix, v1, v2, v3, v4, v5;
+	double xminval = DBL_MAX, xmaxval = -DBL_MAX;
+	int do_range = 0;
+	double *diffs, xnoise = 0;
+	
+	if (nx < 5) {
+		/* treat entire array as an image with a single row */
+		nx = nx * ny;
+		ny = 1;
+	}
+
+	/* rows must have at least 5 pixels */
+	if (nx < 5) {
+
+		for (ii = 0; ii < nx; ii++) {
+		    if (nullcheck && array[ii] == nullvalue)
+		        continue;
+		    else {
+			if (array[ii] < xminval) xminval = array[ii];
+			if (array[ii] > xmaxval) xmaxval = array[ii];
+			ngoodpix++;
+		    }
+		}
+		if (minval) *minval = xminval;
+		if (maxval) *maxval = xmaxval;
+		if (ngood) *ngood = ngoodpix;
+		if (noise) *noise = 0.;
+		return(*status);
+	}
+
+	/* do we need to compute the min and max value? */
+	if (minval || maxval) do_range = 1;
+	
+        /* allocate arrays used to compute the median and noise estimates */
+	if (noise) {
+	    differences = calloc(nx, sizeof(double));
+	    if (!differences) {
+        	*status = MEMORY_ALLOCATION;
+		return(*status);
+	    }
+
+	    diffs = calloc(ny, sizeof(double));
+	    if (!diffs) {
+		free(differences);
+        	*status = MEMORY_ALLOCATION;
+		return(*status);
+	    }
+	}
+
+	/* loop over each row of the image */
+	for (jj=0; jj < ny; jj++) {
+
+                rowpix = array + (jj * nx); /* point to first pixel in the row */
+
+		/***** find the first valid pixel in row */
+		ii = 0;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v1 = rowpix[ii];  /* store the good pixel value */
+
+		if (do_range) {
+			if (v1 < xminval) xminval = v1;
+			if (v1 > xmaxval) xmaxval = v1;
+		}
+
+		/***** find the 2nd valid pixel in row (which we will skip over) */
+		ii++;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v2 = rowpix[ii];  /* store the good pixel value */
+		
+		if (do_range) {
+			if (v2 < xminval) xminval = v2;
+			if (v2 > xmaxval) xmaxval = v2;
+		}
+
+		/***** find the 3rd valid pixel in row */
+		ii++;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v3 = rowpix[ii];  /* store the good pixel value */
+
+		if (do_range) {
+			if (v3 < xminval) xminval = v3;
+			if (v3 > xmaxval) xmaxval = v3;
+		}
+				
+		/* find the 4nd valid pixel in row (to be skipped) */
+		ii++;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v4 = rowpix[ii];  /* store the good pixel value */
+
+		if (do_range) {
+			if (v4 < xminval) xminval = v4;
+			if (v4 > xmaxval) xmaxval = v4;
+		}
+		
+		/* now populate the differences arrays */
+		/* for the remaining pixels in the row */
+		nvals = 0;
+		for (ii++; ii < nx; ii++) {
+
+		    /* find the next valid pixel in row */
+                    if (nullcheck)
+		        while (ii < nx && rowpix[ii] == nullvalue) ii++;
+		     
+		    if (ii == nx) break;  /* hit end of row */
+		    v5 = rowpix[ii];  /* store the good pixel value */
+
+		    if (do_range) {
+			if (v5 < xminval) xminval = v5;
+			if (v5 > xmaxval) xmaxval = v5;
+		    }
+
+		    /* construct array of 3rd order absolute differences */
+		    if (noise) {
+		        if (!(v1 == v2 && v2 == v3 && v3 == v4 && v4 == v5)) {
+
+		            differences[nvals] = fabs((2. * v3) - v1 - v5);
+		            nvals++;  
+		        } else {
+		            /* ignore constant background regions */
+			    ngoodpix++;
+		        }
+		    } else {
+		       /* just increment the number of non-null pixels */
+		       ngoodpix++;
+		    }
+
+		    /* shift over 1 pixel */
+		    v1 = v2;
+		    v2 = v3;
+		    v3 = v4;
+		    v4 = v5;
+	        }  /* end of loop over pixels in the row */
+
+		/* compute the 3rd order diffs */
+		/* Note that there are 4 more pixel values than there are diffs values. */
+		ngoodpix += (nvals + 4);
+
+		if (noise) {
+		    if (nvals == 0) {
+		        continue;  /* cannot compute medians on this row */
+		    } else if (nvals == 1) {
+		        diffs[nrows] = differences[0];
+		    } else {
+                        /* quick_select returns the median MUCH faster than using qsort */
+                        diffs[nrows] = quick_select_double(differences, nvals);
+		    }
+		}
+		nrows++;
+	}  /* end of loop over rows */
+
+	    /* compute median of the values for each row */
+	if (noise) {
+	    if (nrows == 0) { 
+	       xnoise = 0;
+	    } else if (nrows == 1) {
+	       xnoise = diffs[0];
+	    } else {	    
+	       qsort(diffs, nrows, sizeof(double), FnCompare_double);
+	       xnoise =  (diffs[(nrows - 1)/2] + diffs[nrows/2]) / 2.;
+	    }
+	}
+
+	if (ngood)  *ngood  = ngoodpix;
+	if (minval) *minval = xminval;
+	if (maxval) *maxval = xmaxval;
+	if (noise) {
+		*noise  = 0.6052697 * xnoise;
+		free(diffs);
+		free(differences);
+	}
+
+	return(*status);
+}
+/*--------------------------------------------------------------------------*/
+static int FnNoise1_short
+       (short *array,       /*  2 dimensional array of image pixels */
+        long nx,            /* number of pixels in each row of the image */
+        long ny,            /* number of rows in the image */
+	int nullcheck,      /* check for null values, if true */
+	short nullvalue,    /* value of null pixels, if nullcheck is true */
+   /* returned parameters */   
+	double *noise,      /* returned R.M.S. value of all non-null pixels */
+	int *status)        /* error status */
+/*
+Estimate the background noise in the input image using sigma of 1st order differences.
+
+  noise = 1.0 / sqrt(2) * rms of (flux[i] - flux[i-1])
+
+The returned estimate is the median of the values that are computed for each 
+row of the image.
+*/
+{
+	int iter;
+	long ii, jj, kk, nrows = 0, nvals;
+	short *differences, *rowpix, v1;
+	double  *diffs, xnoise, mean, stdev;
+
+	/* rows must have at least 3 pixels to estimate noise */
+	if (nx < 3) {
+		*noise = 0;
+		return(*status);
+	}
+	
+        /* allocate arrays used to compute the median and noise estimates */
+	differences = calloc(nx, sizeof(short));
+	if (!differences) {
+        	*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+
+	diffs = calloc(ny, sizeof(double));
+	if (!diffs) {
+		free(differences);
+        	*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+
+	/* loop over each row of the image */
+	for (jj=0; jj < ny; jj++) {
+
+                rowpix = array + (jj * nx); /* point to first pixel in the row */
+
+		/***** find the first valid pixel in row */
+		ii = 0;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v1 = rowpix[ii];  /* store the good pixel value */
+
+		/* now continue populating the differences arrays */
+		/* for the remaining pixels in the row */
+		nvals = 0;
+		for (ii++; ii < nx; ii++) {
+
+		    /* find the next valid pixel in row */
+                    if (nullcheck)
+		        while (ii < nx && rowpix[ii] == nullvalue) ii++;
+		     
+		    if (ii == nx) break;  /* hit end of row */
+		
+		    /* construct array of 1st order differences */
+		    differences[nvals] = v1 - rowpix[ii];
+
+		    nvals++;  
+		    /* shift over 1 pixel */
+		    v1 = rowpix[ii];
+	        }  /* end of loop over pixels in the row */
+
+		if (nvals < 2)
+		   continue;
+		else {
+
+		    FnMeanSigma_short(differences, nvals, 0, 0, 0, &mean, &stdev, status);
+
+		    if (stdev > 0.) {
+		        for (iter = 0;  iter < NITER;  iter++) {
+		            kk = 0;
+		            for (ii = 0;  ii < nvals;  ii++) {
+		                if (fabs (differences[ii] - mean) < SIGMA_CLIP * stdev) {
+			            if (kk < ii)
+			                differences[kk] = differences[ii];
+			            kk++;
+		                }
+		            }
+		            if (kk == nvals) break;
+
+		            nvals = kk;
+		            FnMeanSigma_short(differences, nvals, 0, 0, 0, &mean, &stdev, status);
+	              }
+		   }
+
+		   diffs[nrows] = stdev;
+		   nrows++;
+		}
+	}  /* end of loop over rows */
+
+	/* compute median of the values for each row */
+	if (nrows == 0) { 
+	       xnoise = 0;
+	} else if (nrows == 1) {
+	       xnoise = diffs[0];
+	} else {
+	       qsort(diffs, nrows, sizeof(double), FnCompare_double);
+	       xnoise =  (diffs[(nrows - 1)/2] + diffs[nrows/2]) / 2.;
+	}
+
+	*noise = .70710678 * xnoise;
+
+	free(diffs);
+	free(differences);
+
+	return(*status);
+}
+/*--------------------------------------------------------------------------*/
+static int FnNoise1_int
+       (int *array,       /*  2 dimensional array of image pixels */
+        long nx,            /* number of pixels in each row of the image */
+        long ny,            /* number of rows in the image */
+	int nullcheck,      /* check for null values, if true */
+	int nullvalue,    /* value of null pixels, if nullcheck is true */
+   /* returned parameters */   
+	double *noise,      /* returned R.M.S. value of all non-null pixels */
+	int *status)        /* error status */
+/*
+Estimate the background noise in the input image using sigma of 1st order differences.
+
+  noise = 1.0 / sqrt(2) * rms of (flux[i] - flux[i-1])
+
+The returned estimate is the median of the values that are computed for each 
+row of the image.
+*/
+{
+	int iter;
+	long ii, jj, kk, nrows = 0, nvals;
+	int *differences, *rowpix, v1;
+	double  *diffs, xnoise, mean, stdev;
+
+	/* rows must have at least 3 pixels to estimate noise */
+	if (nx < 3) {
+		*noise = 0;
+		return(*status);
+	}
+	
+        /* allocate arrays used to compute the median and noise estimates */
+	differences = calloc(nx, sizeof(int));
+	if (!differences) {
+        	*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+
+	diffs = calloc(ny, sizeof(double));
+	if (!diffs) {
+		free(differences);
+        	*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+
+	/* loop over each row of the image */
+	for (jj=0; jj < ny; jj++) {
+
+                rowpix = array + (jj * nx); /* point to first pixel in the row */
+
+		/***** find the first valid pixel in row */
+		ii = 0;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v1 = rowpix[ii];  /* store the good pixel value */
+
+		/* now continue populating the differences arrays */
+		/* for the remaining pixels in the row */
+		nvals = 0;
+		for (ii++; ii < nx; ii++) {
+
+		    /* find the next valid pixel in row */
+                    if (nullcheck)
+		        while (ii < nx && rowpix[ii] == nullvalue) ii++;
+		     
+		    if (ii == nx) break;  /* hit end of row */
+		
+		    /* construct array of 1st order differences */
+		    differences[nvals] = v1 - rowpix[ii];
+
+		    nvals++;  
+		    /* shift over 1 pixel */
+		    v1 = rowpix[ii];
+	        }  /* end of loop over pixels in the row */
+
+		if (nvals < 2)
+		   continue;
+		else {
+
+		    FnMeanSigma_int(differences, nvals, 0, 0, 0, &mean, &stdev, status);
+
+		    if (stdev > 0.) {
+		        for (iter = 0;  iter < NITER;  iter++) {
+		            kk = 0;
+		            for (ii = 0;  ii < nvals;  ii++) {
+		                if (fabs (differences[ii] - mean) < SIGMA_CLIP * stdev) {
+			            if (kk < ii)
+			                differences[kk] = differences[ii];
+			            kk++;
+		                }
+		            }
+		            if (kk == nvals) break;
+
+		            nvals = kk;
+		            FnMeanSigma_int(differences, nvals, 0, 0, 0, &mean, &stdev, status);
+	              }
+		   }
+
+		   diffs[nrows] = stdev;
+		   nrows++;
+		}
+	}  /* end of loop over rows */
+
+	/* compute median of the values for each row */
+	if (nrows == 0) { 
+	       xnoise = 0;
+	} else if (nrows == 1) {
+	       xnoise = diffs[0];
+	} else {
+	       qsort(diffs, nrows, sizeof(double), FnCompare_double);
+	       xnoise =  (diffs[(nrows - 1)/2] + diffs[nrows/2]) / 2.;
+	}
+
+	*noise = .70710678 * xnoise;
+
+	free(diffs);
+	free(differences);
+
+	return(*status);
+}
+/*--------------------------------------------------------------------------*/
+static int FnNoise1_float
+       (float *array,       /*  2 dimensional array of image pixels */
+        long nx,            /* number of pixels in each row of the image */
+        long ny,            /* number of rows in the image */
+	int nullcheck,      /* check for null values, if true */
+	float nullvalue,    /* value of null pixels, if nullcheck is true */
+   /* returned parameters */   
+	double *noise,      /* returned R.M.S. value of all non-null pixels */
+	int *status)        /* error status */
+/*
+Estimate the background noise in the input image using sigma of 1st order differences.
+
+  noise = 1.0 / sqrt(2) * rms of (flux[i] - flux[i-1])
+
+The returned estimate is the median of the values that are computed for each 
+row of the image.
+*/
+{
+	int iter;
+	long ii, jj, kk, nrows = 0, nvals;
+	float *differences, *rowpix, v1;
+	double  *diffs, xnoise, mean, stdev;
+
+	/* rows must have at least 3 pixels to estimate noise */
+	if (nx < 3) {
+		*noise = 0;
+		return(*status);
+	}
+	
+        /* allocate arrays used to compute the median and noise estimates */
+	differences = calloc(nx, sizeof(float));
+	if (!differences) {
+        	*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+
+	diffs = calloc(ny, sizeof(double));
+	if (!diffs) {
+		free(differences);
+        	*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+
+	/* loop over each row of the image */
+	for (jj=0; jj < ny; jj++) {
+
+                rowpix = array + (jj * nx); /* point to first pixel in the row */
+
+		/***** find the first valid pixel in row */
+		ii = 0;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v1 = rowpix[ii];  /* store the good pixel value */
+
+		/* now continue populating the differences arrays */
+		/* for the remaining pixels in the row */
+		nvals = 0;
+		for (ii++; ii < nx; ii++) {
+
+		    /* find the next valid pixel in row */
+                    if (nullcheck)
+		        while (ii < nx && rowpix[ii] == nullvalue) ii++;
+		     
+		    if (ii == nx) break;  /* hit end of row */
+		
+		    /* construct array of 1st order differences */
+		    differences[nvals] = v1 - rowpix[ii];
+
+		    nvals++;  
+		    /* shift over 1 pixel */
+		    v1 = rowpix[ii];
+	        }  /* end of loop over pixels in the row */
+
+		if (nvals < 2)
+		   continue;
+		else {
+
+		    FnMeanSigma_float(differences, nvals, 0, 0, 0, &mean, &stdev, status);
+
+		    if (stdev > 0.) {
+		        for (iter = 0;  iter < NITER;  iter++) {
+		            kk = 0;
+		            for (ii = 0;  ii < nvals;  ii++) {
+		                if (fabs (differences[ii] - mean) < SIGMA_CLIP * stdev) {
+			            if (kk < ii)
+			                differences[kk] = differences[ii];
+			            kk++;
+		                }
+		            }
+		            if (kk == nvals) break;
+
+		            nvals = kk;
+		            FnMeanSigma_float(differences, nvals, 0, 0, 0, &mean, &stdev, status);
+	              }
+		   }
+
+		   diffs[nrows] = stdev;
+		   nrows++;
+		}
+	}  /* end of loop over rows */
+
+	/* compute median of the values for each row */
+	if (nrows == 0) { 
+	       xnoise = 0;
+	} else if (nrows == 1) {
+	       xnoise = diffs[0];
+	} else {
+	       qsort(diffs, nrows, sizeof(double), FnCompare_double);
+	       xnoise =  (diffs[(nrows - 1)/2] + diffs[nrows/2]) / 2.;
+	}
+
+	*noise = .70710678 * xnoise;
+
+	free(diffs);
+	free(differences);
+
+	return(*status);
+}
+/*--------------------------------------------------------------------------*/
+static int FnNoise1_double
+       (double *array,       /*  2 dimensional array of image pixels */
+        long nx,            /* number of pixels in each row of the image */
+        long ny,            /* number of rows in the image */
+	int nullcheck,      /* check for null values, if true */
+	double nullvalue,    /* value of null pixels, if nullcheck is true */
+   /* returned parameters */   
+	double *noise,      /* returned R.M.S. value of all non-null pixels */
+	int *status)        /* error status */
+/*
+Estimate the background noise in the input image using sigma of 1st order differences.
+
+  noise = 1.0 / sqrt(2) * rms of (flux[i] - flux[i-1])
+
+The returned estimate is the median of the values that are computed for each 
+row of the image.
+*/
+{
+	int iter;
+	long ii, jj, kk, nrows = 0, nvals;
+	double *differences, *rowpix, v1;
+	double  *diffs, xnoise, mean, stdev;
+
+	/* rows must have at least 3 pixels to estimate noise */
+	if (nx < 3) {
+		*noise = 0;
+		return(*status);
+	}
+	
+        /* allocate arrays used to compute the median and noise estimates */
+	differences = calloc(nx, sizeof(double));
+	if (!differences) {
+        	*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+
+	diffs = calloc(ny, sizeof(double));
+	if (!diffs) {
+		free(differences);
+        	*status = MEMORY_ALLOCATION;
+		return(*status);
+	}
+
+	/* loop over each row of the image */
+	for (jj=0; jj < ny; jj++) {
+
+                rowpix = array + (jj * nx); /* point to first pixel in the row */
+
+		/***** find the first valid pixel in row */
+		ii = 0;
+		if (nullcheck)
+		    while (ii < nx && rowpix[ii] == nullvalue) ii++;
+
+		if (ii == nx) continue;  /* hit end of row */
+		v1 = rowpix[ii];  /* store the good pixel value */
+
+		/* now continue populating the differences arrays */
+		/* for the remaining pixels in the row */
+		nvals = 0;
+		for (ii++; ii < nx; ii++) {
+
+		    /* find the next valid pixel in row */
+                    if (nullcheck)
+		        while (ii < nx && rowpix[ii] == nullvalue) ii++;
+		     
+		    if (ii == nx) break;  /* hit end of row */
+		
+		    /* construct array of 1st order differences */
+		    differences[nvals] = v1 - rowpix[ii];
+
+		    nvals++;  
+		    /* shift over 1 pixel */
+		    v1 = rowpix[ii];
+	        }  /* end of loop over pixels in the row */
+
+		if (nvals < 2)
+		   continue;
+		else {
+
+		    FnMeanSigma_double(differences, nvals, 0, 0, 0, &mean, &stdev, status);
+
+		    if (stdev > 0.) {
+		        for (iter = 0;  iter < NITER;  iter++) {
+		            kk = 0;
+		            for (ii = 0;  ii < nvals;  ii++) {
+		                if (fabs (differences[ii] - mean) < SIGMA_CLIP * stdev) {
+			            if (kk < ii)
+			                differences[kk] = differences[ii];
+			            kk++;
+		                }
+		            }
+		            if (kk == nvals) break;
+
+		            nvals = kk;
+		            FnMeanSigma_double(differences, nvals, 0, 0, 0, &mean, &stdev, status);
+	              }
+		   }
+
+		   diffs[nrows] = stdev;
+		   nrows++;
+		}
+	}  /* end of loop over rows */
+
+	/* compute median of the values for each row */
+	if (nrows == 0) { 
+	       xnoise = 0;
+	} else if (nrows == 1) {
+	       xnoise = diffs[0];
+	} else {
+	       qsort(diffs, nrows, sizeof(double), FnCompare_double);
+	       xnoise =  (diffs[(nrows - 1)/2] + diffs[nrows/2]) / 2.;
+	}
+
+	*noise = .70710678 * xnoise;
+
+	free(diffs);
+	free(differences);
+
+	return(*status);
+}
+/*--------------------------------------------------------------------------*/
+static int FnCompare_short(const void *v1, const void *v2)
+{
+   const short *i1 = v1;
+   const short *i2 = v2;
+   
+   if (*i1 < *i2)
+     return(-1);
+   else if (*i1 > *i2)
+     return(1);
+   else
+     return(0);
+}
+/*--------------------------------------------------------------------------*/
+static int FnCompare_int(const void *v1, const void *v2)
+{
+   const int *i1 = v1;
+   const int *i2 = v2;
+   
+   if (*i1 < *i2)
+     return(-1);
+   else if (*i1 > *i2)
+     return(1);
+   else
+     return(0);
+}
+/*--------------------------------------------------------------------------*/
+static int FnCompare_float(const void *v1, const void *v2)
+{
+   const float *i1 = v1;
+   const float *i2 = v2;
+   
+   if (*i1 < *i2)
+     return(-1);
+   else if (*i1 > *i2)
+     return(1);
+   else
+     return(0);
+}
+/*--------------------------------------------------------------------------*/
+static int FnCompare_double(const void *v1, const void *v2)
+{
+   const double *i1 = v1;
+   const double *i2 = v2;
+   
+   if (*i1 < *i2)
+     return(-1);
+   else if (*i1 > *i2)
+     return(1);
+   else
+     return(0);
+}
+/*--------------------------------------------------------------------------*/
+
+/*
+ *  These Quickselect routines are based on the algorithm described in
+ *  "Numerical recipes in C", Second Edition,
+ *  Cambridge University Press, 1992, Section 8.5, ISBN 0-521-43108-5
+ *  This code by Nicolas Devillard - 1998. Public domain.
+ */
+
+/*--------------------------------------------------------------------------*/
+
+#define ELEM_SWAP(a,b) { register float t=(a);(a)=(b);(b)=t; }
+
+static float quick_select_float(float arr[], int n) 
+{
+    int low, high ;
+    int median;
+    int middle, ll, hh;
+
+    low = 0 ; high = n-1 ; median = (low + high) / 2;
+    for (;;) {
+        if (high <= low) /* One element only */
+            return arr[median] ;
+
+        if (high == low + 1) {  /* Two elements only */
+            if (arr[low] > arr[high])
+                ELEM_SWAP(arr[low], arr[high]) ;
+            return arr[median] ;
+        }
+
+    /* Find median of low, middle and high items; swap into position low */
+    middle = (low + high) / 2;
+    if (arr[middle] > arr[high])    ELEM_SWAP(arr[middle], arr[high]) ;
+    if (arr[low] > arr[high])       ELEM_SWAP(arr[low], arr[high]) ;
+    if (arr[middle] > arr[low])     ELEM_SWAP(arr[middle], arr[low]) ;
+
+    /* Swap low item (now in position middle) into position (low+1) */
+    ELEM_SWAP(arr[middle], arr[low+1]) ;
+
+    /* Nibble from each end towards middle, swapping items when stuck */
+    ll = low + 1;
+    hh = high;
+    for (;;) {
+        do ll++; while (arr[low] > arr[ll]) ;
+        do hh--; while (arr[hh]  > arr[low]) ;
+
+        if (hh < ll)
+        break;
+
+        ELEM_SWAP(arr[ll], arr[hh]) ;
+    }
+
+    /* Swap middle item (in position low) back into correct position */
+    ELEM_SWAP(arr[low], arr[hh]) ;
+
+    /* Re-set active partition */
+    if (hh <= median)
+        low = ll;
+        if (hh >= median)
+        high = hh - 1;
+    }
+}
+
+#undef ELEM_SWAP
+
+/*--------------------------------------------------------------------------*/
+
+#define ELEM_SWAP(a,b) { register short t=(a);(a)=(b);(b)=t; }
+
+static short quick_select_short(short arr[], int n) 
+{
+    int low, high ;
+    int median;
+    int middle, ll, hh;
+
+    low = 0 ; high = n-1 ; median = (low + high) / 2;
+    for (;;) {
+        if (high <= low) /* One element only */
+            return arr[median] ;
+
+        if (high == low + 1) {  /* Two elements only */
+            if (arr[low] > arr[high])
+                ELEM_SWAP(arr[low], arr[high]) ;
+            return arr[median] ;
+        }
+
+    /* Find median of low, middle and high items; swap into position low */
+    middle = (low + high) / 2;
+    if (arr[middle] > arr[high])    ELEM_SWAP(arr[middle], arr[high]) ;
+    if (arr[low] > arr[high])       ELEM_SWAP(arr[low], arr[high]) ;
+    if (arr[middle] > arr[low])     ELEM_SWAP(arr[middle], arr[low]) ;
+
+    /* Swap low item (now in position middle) into position (low+1) */
+    ELEM_SWAP(arr[middle], arr[low+1]) ;
+
+    /* Nibble from each end towards middle, swapping items when stuck */
+    ll = low + 1;
+    hh = high;
+    for (;;) {
+        do ll++; while (arr[low] > arr[ll]) ;
+        do hh--; while (arr[hh]  > arr[low]) ;
+
+        if (hh < ll)
+        break;
+
+        ELEM_SWAP(arr[ll], arr[hh]) ;
+    }
+
+    /* Swap middle item (in position low) back into correct position */
+    ELEM_SWAP(arr[low], arr[hh]) ;
+
+    /* Re-set active partition */
+    if (hh <= median)
+        low = ll;
+        if (hh >= median)
+        high = hh - 1;
+    }
+}
+
+#undef ELEM_SWAP
+
+/*--------------------------------------------------------------------------*/
+
+#define ELEM_SWAP(a,b) { register int t=(a);(a)=(b);(b)=t; }
+
+static int quick_select_int(int arr[], int n) 
+{
+    int low, high ;
+    int median;
+    int middle, ll, hh;
+
+    low = 0 ; high = n-1 ; median = (low + high) / 2;
+    for (;;) {
+        if (high <= low) /* One element only */
+            return arr[median] ;
+
+        if (high == low + 1) {  /* Two elements only */
+            if (arr[low] > arr[high])
+                ELEM_SWAP(arr[low], arr[high]) ;
+            return arr[median] ;
+        }
+
+    /* Find median of low, middle and high items; swap into position low */
+    middle = (low + high) / 2;
+    if (arr[middle] > arr[high])    ELEM_SWAP(arr[middle], arr[high]) ;
+    if (arr[low] > arr[high])       ELEM_SWAP(arr[low], arr[high]) ;
+    if (arr[middle] > arr[low])     ELEM_SWAP(arr[middle], arr[low]) ;
+
+    /* Swap low item (now in position middle) into position (low+1) */
+    ELEM_SWAP(arr[middle], arr[low+1]) ;
+
+    /* Nibble from each end towards middle, swapping items when stuck */
+    ll = low + 1;
+    hh = high;
+    for (;;) {
+        do ll++; while (arr[low] > arr[ll]) ;
+        do hh--; while (arr[hh]  > arr[low]) ;
+
+        if (hh < ll)
+        break;
+
+        ELEM_SWAP(arr[ll], arr[hh]) ;
+    }
+
+    /* Swap middle item (in position low) back into correct position */
+    ELEM_SWAP(arr[low], arr[hh]) ;
+
+    /* Re-set active partition */
+    if (hh <= median)
+        low = ll;
+        if (hh >= median)
+        high = hh - 1;
+    }
+}
+
+#undef ELEM_SWAP
+
+/*--------------------------------------------------------------------------*/
+
+#define ELEM_SWAP(a,b) { register LONGLONG  t=(a);(a)=(b);(b)=t; }
+
+static LONGLONG quick_select_longlong(LONGLONG arr[], int n) 
+{
+    int low, high ;
+    int median;
+    int middle, ll, hh;
+
+    low = 0 ; high = n-1 ; median = (low + high) / 2;
+    for (;;) {
+        if (high <= low) /* One element only */
+            return arr[median] ;
+
+        if (high == low + 1) {  /* Two elements only */
+            if (arr[low] > arr[high])
+                ELEM_SWAP(arr[low], arr[high]) ;
+            return arr[median] ;
+        }
+
+    /* Find median of low, middle and high items; swap into position low */
+    middle = (low + high) / 2;
+    if (arr[middle] > arr[high])    ELEM_SWAP(arr[middle], arr[high]) ;
+    if (arr[low] > arr[high])       ELEM_SWAP(arr[low], arr[high]) ;
+    if (arr[middle] > arr[low])     ELEM_SWAP(arr[middle], arr[low]) ;
+
+    /* Swap low item (now in position middle) into position (low+1) */
+    ELEM_SWAP(arr[middle], arr[low+1]) ;
+
+    /* Nibble from each end towards middle, swapping items when stuck */
+    ll = low + 1;
+    hh = high;
+    for (;;) {
+        do ll++; while (arr[low] > arr[ll]) ;
+        do hh--; while (arr[hh]  > arr[low]) ;
+
+        if (hh < ll)
+        break;
+
+        ELEM_SWAP(arr[ll], arr[hh]) ;
+    }
+
+    /* Swap middle item (in position low) back into correct position */
+    ELEM_SWAP(arr[low], arr[hh]) ;
+
+    /* Re-set active partition */
+    if (hh <= median)
+        low = ll;
+        if (hh >= median)
+        high = hh - 1;
+    }
+}
+
+#undef ELEM_SWAP
+
+/*--------------------------------------------------------------------------*/
+
+#define ELEM_SWAP(a,b) { register double t=(a);(a)=(b);(b)=t; }
+
+static double quick_select_double(double arr[], int n) 
+{
+    int low, high ;
+    int median;
+    int middle, ll, hh;
+
+    low = 0 ; high = n-1 ; median = (low + high) / 2;
+    for (;;) {
+        if (high <= low) /* One element only */
+            return arr[median] ;
+
+        if (high == low + 1) {  /* Two elements only */
+            if (arr[low] > arr[high])
+                ELEM_SWAP(arr[low], arr[high]) ;
+            return arr[median] ;
+        }
+
+    /* Find median of low, middle and high items; swap into position low */
+    middle = (low + high) / 2;
+    if (arr[middle] > arr[high])    ELEM_SWAP(arr[middle], arr[high]) ;
+    if (arr[low] > arr[high])       ELEM_SWAP(arr[low], arr[high]) ;
+    if (arr[middle] > arr[low])     ELEM_SWAP(arr[middle], arr[low]) ;
+
+    /* Swap low item (now in position middle) into position (low+1) */
+    ELEM_SWAP(arr[middle], arr[low+1]) ;
+
+    /* Nibble from each end towards middle, swapping items when stuck */
+    ll = low + 1;
+    hh = high;
+    for (;;) {
+        do ll++; while (arr[low] > arr[ll]) ;
+        do hh--; while (arr[hh]  > arr[low]) ;
+
+        if (hh < ll)
+        break;
+
+        ELEM_SWAP(arr[ll], arr[hh]) ;
+    }
+
+    /* Swap middle item (in position low) back into correct position */
+    ELEM_SWAP(arr[low], arr[hh]) ;
+
+    /* Re-set active partition */
+    if (hh <= median)
+        low = ll;
+        if (hh >= median)
+        high = hh - 1;
+    }
+}
+
+#undef ELEM_SWAP
+
+
diff --git a/src/plugins/cfitsio/region.c b/src/plugins/cfitsio/region.c
new file mode 100644
index 0000000..3ec5bc2
--- /dev/null
+++ b/src/plugins/cfitsio/region.c
@@ -0,0 +1,1747 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <ctype.h>
+#include "fitsio2.h"
+#include "region.h"
+static int Pt_in_Poly( double x, double y, int nPts, double *Pts );
+
+/*---------------------------------------------------------------------------*/
+int fits_read_rgnfile( const char *filename,
+            WCSdata    *wcs,
+            SAORegion  **Rgn,
+            int        *status )
+/*  Read regions from either a FITS or ASCII region file and return the information     */
+/*  in the "SAORegion" structure.  If it is nonNULL, use wcs to convert the  */
+/*  region coordinates to pixels.  Return an error if region is in degrees   */
+/*  but no WCS data is provided.                                             */
+/*---------------------------------------------------------------------------*/
+{
+  fitsfile *fptr;
+  int tstatus = 0;
+
+  if( *status ) return( *status );
+
+  /* try to open as a FITS file - if that doesn't work treat as an ASCII file */
+
+  fits_write_errmark();
+  if ( ffopen(&fptr, filename, READONLY, &tstatus) ) {
+    fits_clear_errmark();
+    fits_read_ascii_region(filename, wcs, Rgn, status);
+  } else {
+    fits_read_fits_region(fptr, wcs, Rgn, status);
+  }
+
+  return(*status);
+
+}
+/*---------------------------------------------------------------------------*/
+int fits_read_ascii_region( const char *filename,
+			    WCSdata    *wcs,
+			    SAORegion  **Rgn,
+			    int        *status )
+/*  Read regions from a SAO-style region file and return the information     */
+/*  in the "SAORegion" structure.  If it is nonNULL, use wcs to convert the  */
+/*  region coordinates to pixels.  Return an error if region is in degrees   */
+/*  but no WCS data is provided.                                             */
+/*---------------------------------------------------------------------------*/
+{
+   char     *currLine;
+   char     *namePtr, *paramPtr, *currLoc;
+   char     *pX, *pY, *endp;
+   long     allocLen, lineLen, hh, mm, dd;
+   double   *coords, X, Y, x, y, ss, div, xsave= 0., ysave= 0.;
+   int      nParams, nCoords, negdec;
+   int      i, done;
+   FILE     *rgnFile;
+   coordFmt cFmt;
+   SAORegion *aRgn;
+   RgnShape *newShape, *tmpShape;
+
+   if( *status ) return( *status );
+
+   aRgn = (SAORegion *)malloc( sizeof(SAORegion) );
+   if( ! aRgn ) {
+      ffpmsg("Couldn't allocate memory to hold Region file contents.");
+      return(*status = MEMORY_ALLOCATION );
+   }
+   aRgn->nShapes    =    0;
+   aRgn->Shapes     = NULL;
+   if( wcs && wcs->exists )
+      aRgn->wcs = *wcs;
+   else
+      aRgn->wcs.exists = 0;
+
+   cFmt = pixel_fmt; /* set default format */
+
+   /*  Allocate Line Buffer  */
+
+   allocLen = 512;
+   currLine = (char *)malloc( allocLen * sizeof(char) );
+   if( !currLine ) {
+      free( aRgn );
+      ffpmsg("Couldn't allocate memory to hold Region file contents.");
+      return(*status = MEMORY_ALLOCATION );
+   }
+
+   /*  Open Region File  */
+
+   if( (rgnFile = fopen( filename, "r" ))==NULL ) {
+      sprintf(currLine,"Could not open Region file %s.",filename);
+      ffpmsg( currLine );
+      free( currLine );
+      free( aRgn );
+      return( *status = FILE_NOT_OPENED );
+   }
+   
+   /*  Read in file, line by line  */
+
+   while( fgets(currLine,allocLen,rgnFile) != NULL ) {
+
+      /*  Make sure we have a full line of text  */
+
+      lineLen = strlen(currLine);
+      while( lineLen==allocLen-1 && currLine[lineLen-1]!='\n' ) {
+         currLoc = (char *)realloc( currLine, 2 * allocLen * sizeof(char) );
+         if( !currLoc ) {
+            ffpmsg("Couldn't allocate memory to hold Region file contents.");
+            *status = MEMORY_ALLOCATION;
+            goto error;
+         } else {
+            currLine = currLoc;
+         }
+         fgets( currLine+lineLen, allocLen+1, rgnFile );
+         allocLen += allocLen;
+         lineLen  += strlen(currLine+lineLen);
+      }
+
+      currLoc = currLine;
+      if( *currLoc == '#' ) {
+
+         /*  Look to see if it is followed by a format statement...  */
+         /*  if not skip line                                        */
+
+         currLoc++;
+         while( isspace(*currLoc) ) currLoc++;
+         if( !strncasecmp( currLoc, "format:", 7 ) ) {
+            if( aRgn->nShapes ) {
+               ffpmsg("Format code encountered after reading 1 or more shapes.");
+               *status = PARSE_SYNTAX_ERR;
+               goto error;
+            }
+            currLoc += 7;
+            while( isspace(*currLoc) ) currLoc++;
+            if( !strncasecmp( currLoc, "pixel", 5 ) ) {
+               cFmt = pixel_fmt;
+            } else if( !strncasecmp( currLoc, "degree", 6 ) ) {
+               cFmt = degree_fmt;
+            } else if( !strncasecmp( currLoc, "hhmmss", 6 ) ) {
+               cFmt = hhmmss_fmt;
+            } else if( !strncasecmp( currLoc, "hms", 3 ) ) {
+               cFmt = hhmmss_fmt;
+            } else {
+               ffpmsg("Unknown format code encountered in region file.");
+               *status = PARSE_SYNTAX_ERR;
+               goto error;
+            }
+         }
+
+      } else if( !strncasecmp( currLoc, "glob", 4 ) ) {
+		  /* skip lines that begin with the word 'global' */
+
+      } else {
+
+         while( *currLoc != '\0' ) {
+
+            namePtr  = currLoc;
+            paramPtr = NULL;
+            nParams  = 1;
+
+            /*  Search for closing parenthesis  */
+
+            done = 0;
+            while( !done && !*status && *currLoc ) {
+               switch (*currLoc) {
+               case '(':
+                  *currLoc = '\0';
+                  currLoc++;
+                  if( paramPtr )   /* Can't have two '(' in a region! */
+                     *status = 1;
+                  else
+                     paramPtr = currLoc;
+                  break;
+               case ')':
+                  *currLoc = '\0';
+                  currLoc++;
+                  if( !paramPtr )  /* Can't have a ')' without a '(' first */
+                     *status = 1;
+                  else
+                     done = 1;
+                  break;
+               case '#':
+               case '\n':
+                  *currLoc = '\0';
+                  if( !paramPtr )  /* Allow for a blank line */
+                     done = 1;
+                  break;
+               case ':':  
+                  currLoc++;
+                  if ( paramPtr ) cFmt = hhmmss_fmt; /* set format if parameter has : */
+                  break;
+               case 'd':
+                  currLoc++;
+                  if ( paramPtr ) cFmt = degree_fmt; /* set format if parameter has d */  
+                  break;
+               case ',':
+                  nParams++;  /* Fall through to default */
+               default:
+                  currLoc++;
+                  break;
+               }
+            }
+            if( *status || !done ) {
+               ffpmsg( "Error reading Region file" );
+               *status = PARSE_SYNTAX_ERR;
+               goto error;
+            }
+
+            /*  Skip white space in region name  */
+
+            while( isspace(*namePtr) ) namePtr++;
+
+            /*  Was this a blank line? Or the end of the current one  */
+
+            if( ! *namePtr && ! paramPtr ) continue;
+
+            /*  Check for format code at beginning of the line */
+
+            if( !strncasecmp( namePtr, "image;", 6 ) ) {
+				namePtr += 6;
+				cFmt = pixel_fmt;
+            } else if( !strncasecmp( namePtr, "physical;", 9 ) ) {
+                                namePtr += 9;
+                                cFmt = pixel_fmt;
+            } else if( !strncasecmp( namePtr, "linear;", 7 ) ) {
+                                namePtr += 7;
+                                cFmt = pixel_fmt;
+            } else if( !strncasecmp( namePtr, "fk4;", 4 ) ) {
+				namePtr += 4;
+				cFmt = degree_fmt;
+            } else if( !strncasecmp( namePtr, "fk5;", 4 ) ) {
+				namePtr += 4;
+				cFmt = degree_fmt;
+            } else if( !strncasecmp( namePtr, "icrs;", 5 ) ) {
+				namePtr += 5;
+				cFmt = degree_fmt;
+
+            /* the following 5 cases support region files created by POW 
+	       (or ds9 Version 4.x) which
+               may have lines containing  only a format code, not followed
+               by a ';' (and with no region specifier on the line).  We use
+               the 'continue' statement to jump to the end of the loop and
+               then continue reading the next line of the region file. */
+
+            } else if( !strncasecmp( namePtr, "fk5", 3 ) ) {
+				cFmt = degree_fmt;
+                                continue;  /* supports POW region file format */
+            } else if( !strncasecmp( namePtr, "fk4", 3 ) ) {
+				cFmt = degree_fmt;
+                                continue;  /* supports POW region file format */
+            } else if( !strncasecmp( namePtr, "icrs", 4 ) ) {
+				cFmt = degree_fmt;
+                                continue;  /* supports POW region file format */
+            } else if( !strncasecmp( namePtr, "image", 5 ) ) {
+				cFmt = pixel_fmt;
+                                continue;  /* supports POW region file format */
+            } else if( !strncasecmp( namePtr, "physical", 8 ) ) {
+				cFmt = pixel_fmt;
+                                continue;  /* supports POW region file format */
+
+
+            } else if( !strncasecmp( namePtr, "galactic;", 9 ) ) {
+               ffpmsg( "Galactic region coordinates not supported" );
+               ffpmsg( namePtr );
+               *status = PARSE_SYNTAX_ERR;
+               goto error;
+            } else if( !strncasecmp( namePtr, "ecliptic;", 9 ) ) {
+               ffpmsg( "ecliptic region coordinates not supported" );
+               ffpmsg( namePtr );
+               *status = PARSE_SYNTAX_ERR;
+               goto error;
+            }
+
+            /**************************************************/
+            /*  We've apparently found a region... Set it up  */
+            /**************************************************/
+
+            if( !(aRgn->nShapes % 10) ) {
+               if( aRgn->Shapes )
+                  tmpShape = (RgnShape *)realloc( aRgn->Shapes,
+                                                  (10+aRgn->nShapes)
+                                                  * sizeof(RgnShape) );
+               else
+                  tmpShape = (RgnShape *) malloc( 10 * sizeof(RgnShape) );
+               if( tmpShape ) {
+                  aRgn->Shapes = tmpShape;
+               } else {
+                  ffpmsg( "Failed to allocate memory for Region data");
+                  *status = MEMORY_ALLOCATION;
+                  goto error;
+               }
+
+            }
+            newShape        = &aRgn->Shapes[aRgn->nShapes++];
+            newShape->sign  = 1;
+            newShape->shape = point_rgn;
+	    for (i=0; i<8; i++) newShape->param.gen.p[i] = 0.0;
+	    newShape->param.gen.a = 0.0;
+	    newShape->param.gen.b = 0.0;
+	    newShape->param.gen.sinT = 0.0;
+	    newShape->param.gen.cosT = 0.0;
+
+            while( isspace(*namePtr) ) namePtr++;
+            
+			/*  Check for the shape's sign  */
+
+            if( *namePtr=='+' ) {
+               namePtr++;
+            } else if( *namePtr=='-' ) {
+               namePtr++;
+               newShape->sign = 0;
+            }
+
+            /* Skip white space in region name */
+
+            while( isspace(*namePtr) ) namePtr++;
+            if( *namePtr=='\0' ) {
+               ffpmsg( "Error reading Region file" );
+               *status = PARSE_SYNTAX_ERR;
+               goto error;
+            }
+            lineLen = strlen( namePtr ) - 1;
+            while( isspace(namePtr[lineLen]) ) namePtr[lineLen--] = '\0';
+
+            /*  Now identify the region  */
+
+            if(        !strcasecmp( namePtr, "circle"  ) ) {
+               newShape->shape = circle_rgn;
+               if( nParams != 3 )
+                  *status = PARSE_SYNTAX_ERR;
+               nCoords = 2;
+            } else if( !strcasecmp( namePtr, "annulus" ) ) {
+               newShape->shape = annulus_rgn;
+               if( nParams != 4 )
+                  *status = PARSE_SYNTAX_ERR;
+               nCoords = 2;
+            } else if( !strcasecmp( namePtr, "ellipse" ) ) {
+               if( nParams < 4 || nParams > 8 ) {
+                  *status = PARSE_SYNTAX_ERR;
+	       } else if ( nParams < 6 ) {
+		 newShape->shape = ellipse_rgn;
+		 newShape->param.gen.p[4] = 0.0;
+	       } else {
+		 newShape->shape = elliptannulus_rgn;
+		 newShape->param.gen.p[6] = 0.0;
+		 newShape->param.gen.p[7] = 0.0;
+	       }
+               nCoords = 2;
+            } else if( !strcasecmp( namePtr, "elliptannulus" ) ) {
+               newShape->shape = elliptannulus_rgn;
+               if( !( nParams==8 || nParams==6 ) )
+                  *status = PARSE_SYNTAX_ERR;
+               newShape->param.gen.p[6] = 0.0;
+               newShape->param.gen.p[7] = 0.0;
+               nCoords = 2;
+            } else if( !strcasecmp( namePtr, "box"    ) 
+                    || !strcasecmp( namePtr, "rotbox" ) ) {
+	       if( nParams < 4 || nParams > 8 ) {
+		 *status = PARSE_SYNTAX_ERR;
+	       } else if ( nParams < 6 ) {
+		 newShape->shape = box_rgn;
+		 newShape->param.gen.p[4] = 0.0;
+	       } else {
+		  newShape->shape = boxannulus_rgn;
+		  newShape->param.gen.p[6] = 0.0;
+		  newShape->param.gen.p[7] = 0.0;
+	       }
+	       nCoords = 2;
+            } else if( !strcasecmp( namePtr, "rectangle"    )
+                    || !strcasecmp( namePtr, "rotrectangle" ) ) {
+               newShape->shape = rectangle_rgn;
+               if( nParams < 4 || nParams > 5 )
+                  *status = PARSE_SYNTAX_ERR;
+               newShape->param.gen.p[4] = 0.0;
+               nCoords = 4;
+            } else if( !strcasecmp( namePtr, "diamond"    )
+                    || !strcasecmp( namePtr, "rotdiamond" )
+                    || !strcasecmp( namePtr, "rhombus"    )
+                    || !strcasecmp( namePtr, "rotrhombus" ) ) {
+               newShape->shape = diamond_rgn;
+               if( nParams < 4 || nParams > 5 )
+                  *status = PARSE_SYNTAX_ERR;
+               newShape->param.gen.p[4] = 0.0;
+               nCoords = 2;
+            } else if( !strcasecmp( namePtr, "sector"  )
+                    || !strcasecmp( namePtr, "pie"     ) ) {
+               newShape->shape = sector_rgn;
+               if( nParams != 4 )
+                  *status = PARSE_SYNTAX_ERR;
+               nCoords = 2;
+            } else if( !strcasecmp( namePtr, "point"   ) ) {
+               newShape->shape = point_rgn;
+               if( nParams != 2 )
+                  *status = PARSE_SYNTAX_ERR;
+               nCoords = 2;
+            } else if( !strcasecmp( namePtr, "line"    ) ) {
+               newShape->shape = line_rgn;
+               if( nParams != 4 )
+                  *status = PARSE_SYNTAX_ERR;
+               nCoords = 4;
+            } else if( !strcasecmp( namePtr, "polygon" ) ) {
+               newShape->shape = poly_rgn;
+               if( nParams < 6 || (nParams&1) )
+                  *status = PARSE_SYNTAX_ERR;
+               nCoords = nParams;
+            } else if( !strcasecmp( namePtr, "panda" ) ) {
+               newShape->shape = panda_rgn;
+               if( nParams != 8 )
+                  *status = PARSE_SYNTAX_ERR;
+               nCoords = 2;
+            } else if( !strcasecmp( namePtr, "epanda" ) ) {
+               newShape->shape = epanda_rgn;
+               if( nParams < 10 || nParams > 11 )
+                  *status = PARSE_SYNTAX_ERR;
+               newShape->param.gen.p[10] = 0.0;
+               nCoords = 2;
+            } else if( !strcasecmp( namePtr, "bpanda" ) ) {
+               newShape->shape = bpanda_rgn;
+               if( nParams < 10 || nParams > 11 )
+                  *status = PARSE_SYNTAX_ERR;
+               newShape->param.gen.p[10] = 0.0;
+               nCoords = 2;
+            } else {
+               ffpmsg( "Unrecognized region found in region file:" );
+               ffpmsg( namePtr );
+               *status = PARSE_SYNTAX_ERR;
+               goto error;
+            }
+            if( *status ) {
+               ffpmsg( "Wrong number of parameters found for region" );
+               ffpmsg( namePtr );
+               goto error;
+            }
+
+            /*  Parse Parameter string... convert to pixels if necessary  */
+
+            if( newShape->shape==poly_rgn ) {
+               newShape->param.poly.Pts = (double *)malloc( nParams
+                                                            * sizeof(double) );
+               if( !newShape->param.poly.Pts ) {
+                  ffpmsg(
+                      "Could not allocate memory to hold polygon parameters" );
+                  *status = MEMORY_ALLOCATION;
+                  goto error;
+               }
+               newShape->param.poly.nPts = nParams;
+               coords = newShape->param.poly.Pts;
+            } else
+               coords = newShape->param.gen.p;
+
+            /*  Parse the initial "WCS?" coordinates  */
+            for( i=0; i<nCoords; i+=2 ) {
+
+               pX = paramPtr;
+               while( *paramPtr!=',' ) paramPtr++;
+               *(paramPtr++) = '\0';
+
+               pY = paramPtr;
+               while( *paramPtr!=',' && *paramPtr != '\0' ) paramPtr++;
+               *(paramPtr++) = '\0';
+
+               if( strchr(pX, ':' ) ) {
+                  /*  Read in special format & convert to decimal degrees  */
+                  cFmt = hhmmss_fmt;
+                  mm = 0;
+                  ss = 0.;
+                  hh = strtol(pX, &endp, 10);
+                  if (endp && *endp==':') {
+                      pX = endp + 1;
+                      mm = strtol(pX, &endp, 10);
+                      if (endp && *endp==':') {
+                          pX = endp + 1;
+                          ss = atof( pX );
+                      }
+                  }
+                  X = 15. * (hh + mm/60. + ss/3600.); /* convert to degrees */
+
+                  mm = 0;
+                  ss = 0.;
+                  negdec = 0;
+
+                  while( isspace(*pY) ) pY++;
+                  if (*pY=='-') {
+                      negdec = 1;
+                      pY++;
+                  }
+                  dd = strtol(pY, &endp, 10);
+                  if (endp && *endp==':') {
+                      pY = endp + 1;
+                      mm = strtol(pY, &endp, 10);
+                      if (endp && *endp==':') {
+                          pY = endp + 1;
+                          ss = atof( pY );
+                      }
+                  }
+                  if (negdec)
+                     Y = -dd - mm/60. - ss/3600.; /* convert to degrees */
+                  else
+                     Y = dd + mm/60. + ss/3600.;
+
+               } else {
+                  X = atof( pX );
+                  Y = atof( pY );
+               }
+               if (i==0) {   /* save 1st coord. in case needed later */
+                   xsave = X;
+                   ysave = Y;
+               }
+
+               if( cFmt!=pixel_fmt ) {
+                  /*  Convert to pixels  */
+                  if( wcs==NULL || ! wcs->exists ) {
+                     ffpmsg("WCS information needed to convert region coordinates.");
+                     *status = NO_WCS_KEY;
+                     goto error;
+                  }
+                  
+                  if( ffxypx(  X,  Y, wcs->xrefval, wcs->yrefval,
+                                      wcs->xrefpix, wcs->yrefpix,
+                                      wcs->xinc,    wcs->yinc,
+                                      wcs->rot,     wcs->type,
+                              &x, &y, status ) ) {
+                     ffpmsg("Error converting region to pixel coordinates.");
+                     goto error;
+                  }
+                  X = x; Y = y;
+               }
+               coords[i]   = X;
+               coords[i+1] = Y;
+
+            }
+
+            /*  Read in remaining parameters...  */
+
+            for( ; i<nParams; i++ ) {
+               pX = paramPtr;
+               while( *paramPtr!=',' && *paramPtr != '\0' ) paramPtr++;
+               *(paramPtr++) = '\0';
+               coords[i] = strtod( pX, &endp );
+
+	       if (endp && (*endp=='"' || *endp=='\'' || *endp=='d') ) {
+		  div = 1.0;
+		  if ( *endp=='"' ) div = 3600.0;
+		  if ( *endp=='\'' ) div = 60.0;
+		  /* parameter given in arcsec so convert to pixels. */
+		  /* Increment first Y coordinate by this amount then calc */
+		  /* the distance in pixels from the original coordinate. */
+		  /* NOTE: This assumes the pixels are square!! */
+		  if (ysave < 0.)
+		     Y = ysave + coords[i]/div;  /* don't exceed -90 */
+		  else
+		     Y = ysave - coords[i]/div;  /* don't exceed +90 */
+
+		  X = xsave;
+		  if( ffxypx(  X,  Y, wcs->xrefval, wcs->yrefval,
+			       wcs->xrefpix, wcs->yrefpix,
+			       wcs->xinc,    wcs->yinc,
+			       wcs->rot,     wcs->type,
+                               &x, &y, status ) ) {
+		     ffpmsg("Error converting region to pixel coordinates.");
+		     goto error;
+		  }
+		 
+		  coords[i] = sqrt( pow(x-coords[0],2) + pow(y-coords[1],2) );
+
+               }
+            }
+
+	    /* special case for elliptannulus and boxannulus if only one angle
+	       was given */
+
+	    if ( (newShape->shape == elliptannulus_rgn || 
+		  newShape->shape == boxannulus_rgn ) && nParams == 7 ) {
+	      coords[7] = coords[6];
+	    }
+
+            /* Also, correct the position angle for any WCS rotation:  */
+            /*    If regions are specified in WCS coordintes, then the angles */
+            /*    are relative to the WCS system, not the pixel X,Y system */
+
+	    if( cFmt!=pixel_fmt ) {	    
+	      switch( newShape->shape ) {
+	      case sector_rgn:
+	      case panda_rgn:
+		coords[2] += (wcs->rot);
+		coords[3] += (wcs->rot);
+		break;
+	      case box_rgn:
+	      case rectangle_rgn:
+	      case diamond_rgn:
+	      case ellipse_rgn:
+		coords[4] += (wcs->rot);
+		break;
+	      case boxannulus_rgn:
+	      case elliptannulus_rgn:
+		coords[6] += (wcs->rot);
+		coords[7] += (wcs->rot);
+		break;
+	      case epanda_rgn:
+	      case bpanda_rgn:
+		coords[2] += (wcs->rot);
+		coords[3] += (wcs->rot);
+		coords[10] += (wcs->rot);
+	      }
+	    }
+
+	    /* do some precalculations to speed up tests */
+
+	    fits_setup_shape(newShape);
+
+         }  /* End of while( *currLoc ) */
+/*
+  if (coords)printf("%.8f %.8f %.8f %.8f %.8f\n",
+   coords[0],coords[1],coords[2],coords[3],coords[4]); 
+*/
+      }  /* End of if...else parse line */
+   }   /* End of while( fgets(rgnFile) ) */
+
+   /* set up component numbers */
+
+   fits_set_region_components( aRgn );
+
+error:
+
+   if( *status ) {
+      fits_free_region( aRgn );
+   } else {
+      *Rgn = aRgn;
+   }
+
+   fclose( rgnFile );
+   free( currLine );
+
+   return( *status );
+}
+
+/*---------------------------------------------------------------------------*/
+int fits_in_region( double    X,
+            double    Y,
+            SAORegion *Rgn )
+/*  Test if the given point is within the region described by Rgn.  X and    */
+/*  Y are in pixel coordinates.                                              */
+/*---------------------------------------------------------------------------*/
+{
+   double x, y, dx, dy, xprime, yprime, r, th;
+   RgnShape *Shapes;
+   int i, cur_comp;
+   int result, comp_result;
+
+   Shapes = Rgn->Shapes;
+
+   result = 0;
+   comp_result = 0;
+   cur_comp = Rgn->Shapes[0].comp;
+
+   for( i=0; i<Rgn->nShapes; i++, Shapes++ ) {
+
+     /* if this region has a different component number to the last one  */
+     /*	then replace the accumulated selection logical with the union of */
+     /*	the current logical and the total logical. Reinitialize the      */
+     /* temporary logical.                                               */
+
+     if ( i==0 || Shapes->comp != cur_comp ) {
+       result = result || comp_result;
+       cur_comp = Shapes->comp;
+       /* if an excluded region is given first, then implicitly   */
+       /* assume a previous shape that includes the entire image. */
+       comp_result = !Shapes->sign;
+     }
+
+    /* only need to test if  */
+    /*   the point is not already included and this is an include region, */
+    /* or the point is included and this is an excluded region */
+
+    if ( (!comp_result && Shapes->sign) || (comp_result && !Shapes->sign) ) { 
+
+      comp_result = 1;
+
+      switch( Shapes->shape ) {
+
+      case box_rgn:
+         /*  Shift origin to center of region  */
+         xprime = X - Shapes->param.gen.p[0];
+         yprime = Y - Shapes->param.gen.p[1];
+
+         /*  Rotate point to region's orientation  */
+         x =  xprime * Shapes->param.gen.cosT + yprime * Shapes->param.gen.sinT;
+         y = -xprime * Shapes->param.gen.sinT + yprime * Shapes->param.gen.cosT;
+
+         dx = 0.5 * Shapes->param.gen.p[2];
+         dy = 0.5 * Shapes->param.gen.p[3];
+         if( (x < -dx) || (x > dx) || (y < -dy) || (y > dy) )
+            comp_result = 0;
+         break;
+
+      case boxannulus_rgn:
+         /*  Shift origin to center of region  */
+         xprime = X - Shapes->param.gen.p[0];
+         yprime = Y - Shapes->param.gen.p[1];
+
+         /*  Rotate point to region's orientation  */
+         x =  xprime * Shapes->param.gen.cosT + yprime * Shapes->param.gen.sinT;
+         y = -xprime * Shapes->param.gen.sinT + yprime * Shapes->param.gen.cosT;
+
+         dx = 0.5 * Shapes->param.gen.p[4];
+         dy = 0.5 * Shapes->param.gen.p[5];
+         if( (x < -dx) || (x > dx) || (y < -dy) || (y > dy) ) {
+	   comp_result = 0;
+	 } else {
+	   /* Repeat test for inner box */
+	   x =  xprime * Shapes->param.gen.b + yprime * Shapes->param.gen.a;
+	   y = -xprime * Shapes->param.gen.a + yprime * Shapes->param.gen.b;
+	   
+	   dx = 0.5 * Shapes->param.gen.p[2];
+	   dy = 0.5 * Shapes->param.gen.p[3];
+	   if( (x >= -dx) && (x <= dx) && (y >= -dy) && (y <= dy) )
+	     comp_result = 0;
+	 }
+         break;
+
+      case rectangle_rgn:
+         /*  Shift origin to center of region  */
+         xprime = X - Shapes->param.gen.p[5];
+         yprime = Y - Shapes->param.gen.p[6];
+
+         /*  Rotate point to region's orientation  */
+         x =  xprime * Shapes->param.gen.cosT + yprime * Shapes->param.gen.sinT;
+         y = -xprime * Shapes->param.gen.sinT + yprime * Shapes->param.gen.cosT;
+
+         dx = Shapes->param.gen.a;
+         dy = Shapes->param.gen.b;
+         if( (x < -dx) || (x > dx) || (y < -dy) || (y > dy) )
+            comp_result = 0;
+         break;
+
+      case diamond_rgn:
+         /*  Shift origin to center of region  */
+         xprime = X - Shapes->param.gen.p[0];
+         yprime = Y - Shapes->param.gen.p[1];
+
+         /*  Rotate point to region's orientation  */
+         x =  xprime * Shapes->param.gen.cosT + yprime * Shapes->param.gen.sinT;
+         y = -xprime * Shapes->param.gen.sinT + yprime * Shapes->param.gen.cosT;
+
+         dx = 0.5 * Shapes->param.gen.p[2];
+         dy = 0.5 * Shapes->param.gen.p[3];
+         r  = fabs(x/dx) + fabs(y/dy);
+         if( r > 1 )
+            comp_result = 0;
+         break;
+
+      case circle_rgn:
+         /*  Shift origin to center of region  */
+         x = X - Shapes->param.gen.p[0];
+         y = Y - Shapes->param.gen.p[1];
+
+         r  = x*x + y*y;
+         if ( r > Shapes->param.gen.a )
+            comp_result = 0;
+         break;
+
+      case annulus_rgn:
+         /*  Shift origin to center of region  */
+         x = X - Shapes->param.gen.p[0];
+         y = Y - Shapes->param.gen.p[1];
+
+         r = x*x + y*y;
+         if ( r < Shapes->param.gen.a || r > Shapes->param.gen.b )
+            comp_result = 0;
+         break;
+
+      case sector_rgn:
+         /*  Shift origin to center of region  */
+         x = X - Shapes->param.gen.p[0];
+         y = Y - Shapes->param.gen.p[1];
+
+         if( x || y ) {
+            r = atan2( y, x ) * RadToDeg;
+            if( Shapes->param.gen.p[2] <= Shapes->param.gen.p[3] ) {
+               if( r < Shapes->param.gen.p[2] || r > Shapes->param.gen.p[3] )
+                  comp_result = 0;
+            } else {
+               if( r < Shapes->param.gen.p[2] && r > Shapes->param.gen.p[3] )
+                  comp_result = 0;
+            }
+         }
+         break;
+
+      case ellipse_rgn:
+         /*  Shift origin to center of region  */
+         xprime = X - Shapes->param.gen.p[0];
+         yprime = Y - Shapes->param.gen.p[1];
+
+         /*  Rotate point to region's orientation  */
+         x =  xprime * Shapes->param.gen.cosT + yprime * Shapes->param.gen.sinT;
+         y = -xprime * Shapes->param.gen.sinT + yprime * Shapes->param.gen.cosT;
+
+         x /= Shapes->param.gen.p[2];
+         y /= Shapes->param.gen.p[3];
+         r = x*x + y*y;
+         if( r>1.0 )
+            comp_result = 0;
+         break;
+
+      case elliptannulus_rgn:
+         /*  Shift origin to center of region  */
+         xprime = X - Shapes->param.gen.p[0];
+         yprime = Y - Shapes->param.gen.p[1];
+
+         /*  Rotate point to outer ellipse's orientation  */
+         x =  xprime * Shapes->param.gen.cosT + yprime * Shapes->param.gen.sinT;
+         y = -xprime * Shapes->param.gen.sinT + yprime * Shapes->param.gen.cosT;
+
+         x /= Shapes->param.gen.p[4];
+         y /= Shapes->param.gen.p[5];
+         r = x*x + y*y;
+         if( r>1.0 )
+            comp_result = 0;
+         else {
+            /*  Repeat test for inner ellipse  */
+            x =  xprime * Shapes->param.gen.b + yprime * Shapes->param.gen.a;
+            y = -xprime * Shapes->param.gen.a + yprime * Shapes->param.gen.b;
+
+            x /= Shapes->param.gen.p[2];
+            y /= Shapes->param.gen.p[3];
+            r = x*x + y*y;
+            if( r<1.0 )
+               comp_result = 0;
+         }
+         break;
+
+      case line_rgn:
+         /*  Shift origin to first point of line  */
+         xprime = X - Shapes->param.gen.p[0];
+         yprime = Y - Shapes->param.gen.p[1];
+
+         /*  Rotate point to line's orientation  */
+         x =  xprime * Shapes->param.gen.cosT + yprime * Shapes->param.gen.sinT;
+         y = -xprime * Shapes->param.gen.sinT + yprime * Shapes->param.gen.cosT;
+
+         if( (y < -0.5) || (y >= 0.5) || (x < -0.5)
+             || (x >= Shapes->param.gen.a) )
+            comp_result = 0;
+         break;
+
+      case point_rgn:
+         /*  Shift origin to center of region  */
+         x = X - Shapes->param.gen.p[0];
+         y = Y - Shapes->param.gen.p[1];
+
+         if ( (x<-0.5) || (x>=0.5) || (y<-0.5) || (y>=0.5) )
+            comp_result = 0;
+         break;
+
+      case poly_rgn:
+         if( X<Shapes->xmin || X>Shapes->xmax
+             || Y<Shapes->ymin || Y>Shapes->ymax )
+            comp_result = 0;
+         else
+            comp_result = Pt_in_Poly( X, Y, Shapes->param.poly.nPts,
+                                       Shapes->param.poly.Pts );
+         break;
+
+      case panda_rgn:
+         /*  Shift origin to center of region  */
+         x = X - Shapes->param.gen.p[0];
+         y = Y - Shapes->param.gen.p[1];
+
+         r = x*x + y*y;
+         if ( r < Shapes->param.gen.a || r > Shapes->param.gen.b ) {
+	   comp_result = 0;
+	 } else {
+	   if( x || y ) {
+	     th = atan2( y, x ) * RadToDeg;
+	     if( Shapes->param.gen.p[2] <= Shapes->param.gen.p[3] ) {
+               if( th < Shapes->param.gen.p[2] || th > Shapes->param.gen.p[3] )
+		 comp_result = 0;
+	     } else {
+               if( th < Shapes->param.gen.p[2] && th > Shapes->param.gen.p[3] )
+		 comp_result = 0;
+	     }
+	   }
+         }
+         break;
+
+      case epanda_rgn:
+         /*  Shift origin to center of region  */
+         xprime = X - Shapes->param.gen.p[0];
+         yprime = Y - Shapes->param.gen.p[1];
+
+         /*  Rotate point to region's orientation  */
+         x =  xprime * Shapes->param.gen.cosT + yprime * Shapes->param.gen.sinT;
+         y = -xprime * Shapes->param.gen.sinT + yprime * Shapes->param.gen.cosT;
+	 xprime = x;
+	 yprime = y;
+
+	 /* outer region test */
+         x = xprime/Shapes->param.gen.p[7];
+         y = yprime/Shapes->param.gen.p[8];
+         r = x*x + y*y;
+	 if ( r>1.0 )
+	   comp_result = 0;
+	 else {
+	   /* inner region test */
+	   x = xprime/Shapes->param.gen.p[5];
+	   y = yprime/Shapes->param.gen.p[6];
+	   r = x*x + y*y;
+	   if ( r<1.0 )
+	     comp_result = 0;
+	   else {
+	     /* angle test */
+	     if( xprime || yprime ) {
+	       th = atan2( yprime, xprime ) * RadToDeg;
+	       if( Shapes->param.gen.p[2] <= Shapes->param.gen.p[3] ) {
+		 if( th < Shapes->param.gen.p[2] || th > Shapes->param.gen.p[3] )
+		   comp_result = 0;
+	       } else {
+		 if( th < Shapes->param.gen.p[2] && th > Shapes->param.gen.p[3] )
+		   comp_result = 0;
+	       }
+	     }
+	   }
+	 }
+         break;
+
+      case bpanda_rgn:
+         /*  Shift origin to center of region  */
+         xprime = X - Shapes->param.gen.p[0];
+         yprime = Y - Shapes->param.gen.p[1];
+
+         /*  Rotate point to region's orientation  */
+         x =  xprime * Shapes->param.gen.cosT + yprime * Shapes->param.gen.sinT;
+         y = -xprime * Shapes->param.gen.sinT + yprime * Shapes->param.gen.cosT;
+
+	 /* outer box test */
+         dx = 0.5 * Shapes->param.gen.p[7];
+         dy = 0.5 * Shapes->param.gen.p[8];
+         if( (x < -dx) || (x > dx) || (y < -dy) || (y > dy) )
+	   comp_result = 0;
+	 else {
+	   /* inner box test */
+	   dx = 0.5 * Shapes->param.gen.p[5];
+	   dy = 0.5 * Shapes->param.gen.p[6];
+	   if( (x >= -dx) && (x <= dx) && (y >= -dy) && (y <= dy) )
+	     comp_result = 0;
+	   else {
+	     /* angle test */
+	     if( x || y ) {
+	       th = atan2( y, x ) * RadToDeg;
+	       if( Shapes->param.gen.p[2] <= Shapes->param.gen.p[3] ) {
+		 if( th < Shapes->param.gen.p[2] || th > Shapes->param.gen.p[3] )
+		   comp_result = 0;
+	       } else {
+		 if( th < Shapes->param.gen.p[2] && th > Shapes->param.gen.p[3] )
+		   comp_result = 0;
+	       }
+	     }
+	   }
+	 }
+         break;
+      }
+
+      if( !Shapes->sign ) comp_result = !comp_result;
+
+     } 
+
+   }
+
+   result = result || comp_result;
+   
+   return( result );
+}
+
+/*---------------------------------------------------------------------------*/
+void fits_free_region( SAORegion *Rgn )
+/*   Free up memory allocated to hold the region data.                       */
+/*---------------------------------------------------------------------------*/
+{
+   int i;
+
+   for( i=0; i<Rgn->nShapes; i++ )
+      if( Rgn->Shapes[i].shape == poly_rgn )
+         free( Rgn->Shapes[i].param.poly.Pts );
+   if( Rgn->Shapes )
+      free( Rgn->Shapes );
+   free( Rgn );
+}
+
+/*---------------------------------------------------------------------------*/
+static int Pt_in_Poly( double x,
+                       double y,
+                       int nPts,
+                       double *Pts )
+/*  Internal routine for testing whether the coordinate x,y is within the    */
+/*  polygon region traced out by the array Pts.                              */
+/*---------------------------------------------------------------------------*/
+{
+   int i, j, flag=0;
+   double prevX, prevY;
+   double nextX, nextY;
+   double dx, dy, Dy;
+
+   nextX = Pts[nPts-2];
+   nextY = Pts[nPts-1];
+
+   for( i=0; i<nPts; i+=2 ) {
+      prevX = nextX;
+      prevY = nextY;
+
+      nextX = Pts[i];
+      nextY = Pts[i+1];
+
+      if( (y>prevY && y>=nextY) || (y<prevY && y<=nextY)
+          || (x>prevX && x>=nextX) )
+         continue;
+      
+      /* Check to see if x,y lies right on the segment */
+
+      if( x>=prevX || x>nextX ) {
+         dy = y - prevY;
+         Dy = nextY - prevY;
+
+         if( fabs(Dy)<1e-10 ) {
+            if( fabs(dy)<1e-10 )
+               return( 1 );
+            else
+               continue;
+         }
+
+         dx = prevX + ( (nextX-prevX)/(Dy) ) * dy - x;
+         if( dx < -1e-10 )
+            continue;
+         if( dx <  1e-10 )
+            return( 1 );
+      }
+
+      /* There is an intersection! Make sure it isn't a V point.  */
+
+      if( y != prevY ) {
+         flag = 1 - flag;
+      } else {
+         j = i+1;  /* Point to Y component */
+         do {
+            if( j>1 )
+               j -= 2;
+            else
+               j = nPts-1;
+         } while( y == Pts[j] );
+
+         if( (nextY-y)*(y-Pts[j]) > 0 )
+            flag = 1-flag;
+      }
+
+   }
+   return( flag );
+}
+/*---------------------------------------------------------------------------*/
+void fits_set_region_components ( SAORegion *aRgn )
+{
+/* 
+   Internal routine to turn a collection of regions read from an ascii file into
+   the more complex structure that is allowed by the FITS REGION extension with
+   multiple components. Regions are anded within components and ored between them
+   ie for a pixel to be selected it must be selected by at least one component
+   and to be selected by a component it must be selected by all that component's
+   shapes.
+
+   The algorithm is to replicate every exclude region after every include
+   region before it in the list. eg reg1, reg2, -reg3, reg4, -reg5 becomes
+   (reg1, -reg3, -reg5), (reg2, -reg5, -reg3), (reg4, -reg5) where the
+   parentheses designate components.
+*/
+
+  int i, j, k, icomp;
+
+/* loop round shapes */
+
+  i = 0;
+  while ( i<aRgn->nShapes ) {
+
+    /* first do the case of an exclude region */
+
+    if ( !aRgn->Shapes[i].sign ) {
+
+      /* we need to run back through the list copying the current shape as
+	 required. start by findin the first include shape before this exclude */
+
+      j = i-1;
+      while ( j > 0 && !aRgn->Shapes[j].sign ) j--;
+
+      /* then go back one more shape */
+
+      j--;
+
+      /* and loop back through the regions */
+
+      while ( j >= 0 ) {
+
+	/* if this is an include region then insert a copy of the exclude
+	   region immediately after it */
+
+	if ( aRgn->Shapes[j].sign ) {
+
+	  aRgn->Shapes = (RgnShape *) realloc (aRgn->Shapes,(1+aRgn->nShapes)*sizeof(RgnShape));
+	  aRgn->nShapes++;
+	  for (k=aRgn->nShapes-1; k>j+1; k--) aRgn->Shapes[k] = aRgn->Shapes[k-1];
+
+	  i++;
+	  aRgn->Shapes[j+1] = aRgn->Shapes[i];
+
+	}
+
+	j--;
+
+      }
+
+    }
+
+    i++;
+
+  }
+
+  /* now set the component numbers */
+
+  icomp = 0;
+  for ( i=0; i<aRgn->nShapes; i++ ) {
+    if ( aRgn->Shapes[i].sign ) icomp++;
+    aRgn->Shapes[i].comp = icomp;
+
+    /*
+    printf("i = %d, shape = %d, sign = %d, comp = %d\n", i, aRgn->Shapes[i].shape, aRgn->Shapes[i].sign, aRgn->Shapes[i].comp);
+    */
+
+  }
+
+  return;
+
+}
+
+/*---------------------------------------------------------------------------*/
+void fits_setup_shape ( RgnShape *newShape)
+{
+/* Perform some useful calculations now to speed up filter later             */
+
+  double X, Y, R;
+  double *coords;
+  int i;
+
+  if ( newShape->shape == poly_rgn ) {
+    coords = newShape->param.poly.Pts;
+  } else {
+    coords = newShape->param.gen.p;
+  }
+
+  switch( newShape->shape ) {
+  case circle_rgn:
+    newShape->param.gen.a = coords[2] * coords[2];
+    break;
+  case annulus_rgn:
+    newShape->param.gen.a = coords[2] * coords[2];
+    newShape->param.gen.b = coords[3] * coords[3];
+    break;
+  case sector_rgn:
+    while( coords[2]> 180.0 ) coords[2] -= 360.0;
+    while( coords[2]<=-180.0 ) coords[2] += 360.0;
+    while( coords[3]> 180.0 ) coords[3] -= 360.0;
+    while( coords[3]<=-180.0 ) coords[3] += 360.0;
+    break;
+  case ellipse_rgn:
+    newShape->param.gen.sinT = sin( myPI * (coords[4] / 180.0) );
+    newShape->param.gen.cosT = cos( myPI * (coords[4] / 180.0) );
+    break;
+  case elliptannulus_rgn:
+    newShape->param.gen.a    = sin( myPI * (coords[6] / 180.0) );
+    newShape->param.gen.b    = cos( myPI * (coords[6] / 180.0) );
+    newShape->param.gen.sinT = sin( myPI * (coords[7] / 180.0) );
+    newShape->param.gen.cosT = cos( myPI * (coords[7] / 180.0) );
+    break;
+  case box_rgn:
+    newShape->param.gen.sinT = sin( myPI * (coords[4] / 180.0) );
+    newShape->param.gen.cosT = cos( myPI * (coords[4] / 180.0) );
+    break;
+  case boxannulus_rgn:
+    newShape->param.gen.a    = sin( myPI * (coords[6] / 180.0) );
+    newShape->param.gen.b    = cos( myPI * (coords[6] / 180.0) );
+    newShape->param.gen.sinT = sin( myPI * (coords[7] / 180.0) );
+    newShape->param.gen.cosT = cos( myPI * (coords[7] / 180.0) );
+    break;
+  case rectangle_rgn:
+    newShape->param.gen.sinT = sin( myPI * (coords[4] / 180.0) );
+    newShape->param.gen.cosT = cos( myPI * (coords[4] / 180.0) );
+    X = 0.5 * ( coords[2]-coords[0] );
+    Y = 0.5 * ( coords[3]-coords[1] );
+    newShape->param.gen.a = fabs( X * newShape->param.gen.cosT
+				  + Y * newShape->param.gen.sinT );
+    newShape->param.gen.b = fabs( Y * newShape->param.gen.cosT
+				  - X * newShape->param.gen.sinT );
+    newShape->param.gen.p[5] = 0.5 * ( coords[2]+coords[0] );
+    newShape->param.gen.p[6] = 0.5 * ( coords[3]+coords[1] );
+    break;
+  case diamond_rgn:
+    newShape->param.gen.sinT = sin( myPI * (coords[4] / 180.0) );
+    newShape->param.gen.cosT = cos( myPI * (coords[4] / 180.0) );
+    break;
+  case line_rgn:
+    X = coords[2] - coords[0];
+    Y = coords[3] - coords[1];
+    R = sqrt( X*X + Y*Y );
+    newShape->param.gen.sinT = ( R ? Y/R : 0.0 );
+    newShape->param.gen.cosT = ( R ? X/R : 1.0 );
+    newShape->param.gen.a    = R + 0.5;
+    break;
+  case panda_rgn:
+    while( coords[2]> 180.0 ) coords[2] -= 360.0;
+    while( coords[2]<=-180.0 ) coords[2] += 360.0;
+    while( coords[3]> 180.0 ) coords[3] -= 360.0;
+    while( coords[3]<=-180.0 ) coords[3] += 360.0;
+    newShape->param.gen.a = newShape->param.gen.p[5]*newShape->param.gen.p[5];
+    newShape->param.gen.b = newShape->param.gen.p[6]*newShape->param.gen.p[6];
+    break;
+  case epanda_rgn:
+  case bpanda_rgn:
+    while( coords[2]> 180.0 ) coords[2] -= 360.0;
+    while( coords[2]<=-180.0 ) coords[2] += 360.0;
+    while( coords[3]> 180.0 ) coords[3] -= 360.0;
+    while( coords[3]<=-180.0 ) coords[3] += 360.0;
+    newShape->param.gen.sinT = sin( myPI * (coords[10] / 180.0) );
+    newShape->param.gen.cosT = cos( myPI * (coords[10] / 180.0) );
+    break;
+  }
+
+  /*  Set the xmin, xmax, ymin, ymax elements of the RgnShape structure */
+
+  /* For everything which has first two parameters as center position just */
+  /* find a circle that encompasses the region and use it to set the       */
+  /* bounding box                                                          */
+
+  R = -1.0;
+
+  switch ( newShape->shape ) {
+
+  case circle_rgn:
+    R = coords[2];
+    break;
+
+  case annulus_rgn:
+    R = coords[3];
+    break;
+
+  case ellipse_rgn:
+    if ( coords[2] > coords[3] ) {
+      R = coords[2];
+    } else {
+      R = coords[3];
+    }
+    break;
+
+  case elliptannulus_rgn:
+    if ( coords[4] > coords[5] ) {
+      R = coords[4];
+    } else {
+      R = coords[5];
+    }
+    break;
+
+  case box_rgn:
+    R = sqrt(coords[2]*coords[2]+
+	     coords[3]*coords[3])/2.0;
+    break;
+
+  case boxannulus_rgn:
+    R = sqrt(coords[4]*coords[5]+
+	     coords[4]*coords[5])/2.0;
+    break;
+
+  case diamond_rgn:
+    if ( coords[2] > coords[3] ) {
+      R = coords[2]/2.0;
+    } else {
+      R = coords[3]/2.0;
+    }
+    break;
+    
+  case point_rgn:
+    R = 1.0;
+    break;
+
+  case panda_rgn:
+    R = coords[6];
+    break;
+
+  case epanda_rgn:
+    if ( coords[7] > coords[8] ) {
+      R = coords[7];
+    } else {
+      R = coords[8];
+    }
+    break;
+
+  case bpanda_rgn:
+    R = sqrt(coords[7]*coords[8]+
+	     coords[7]*coords[8])/2.0;
+    break;
+
+  }
+
+  if ( R > 0.0 ) {
+
+    newShape->xmin = coords[0] - R;
+    newShape->xmax = coords[0] + R;
+    newShape->ymin = coords[1] - R;
+    newShape->ymax = coords[1] + R;
+
+    return;
+
+  }
+
+  /* Now do the rest of the shapes that require individual methods */
+
+  switch ( newShape->shape ) {
+
+  case rectangle_rgn:
+    R = sqrt((coords[5]-coords[0])*(coords[5]-coords[0])+
+	     (coords[6]-coords[1])*(coords[6]-coords[1]));
+    newShape->xmin = coords[5] - R;
+    newShape->xmax = coords[5] + R;
+    newShape->ymin = coords[6] - R;
+    newShape->ymax = coords[6] + R;
+    break;
+
+  case poly_rgn:
+    newShape->xmin = coords[0];
+    newShape->xmax = coords[0];
+    newShape->ymin = coords[1];
+    newShape->ymax = coords[1];
+    for( i=2; i < newShape->param.poly.nPts; ) {
+      if( newShape->xmin > coords[i] ) /* Min X */
+	newShape->xmin = coords[i];
+      if( newShape->xmax < coords[i] ) /* Max X */
+	newShape->xmax = coords[i];
+      i++;
+      if( newShape->ymin > coords[i] ) /* Min Y */
+	newShape->ymin = coords[i];
+      if( newShape->ymax < coords[i] ) /* Max Y */
+	newShape->ymax = coords[i];
+      i++;
+    }
+    break;
+
+  case line_rgn:
+    if ( coords[0] > coords[2] ) {
+      newShape->xmin = coords[2];
+      newShape->xmax = coords[0];
+    } else {
+      newShape->xmin = coords[0];
+      newShape->xmax = coords[2];
+    }
+    if ( coords[1] > coords[3] ) {
+      newShape->ymin = coords[3];
+      newShape->ymax = coords[1];
+    } else {
+      newShape->ymin = coords[1];
+      newShape->ymax = coords[3];
+    }
+
+    break;
+
+    /* sector doesn't have min and max so indicate by setting max < min */
+
+  case sector_rgn:
+    newShape->xmin = 1.0;
+    newShape->xmax = -1.0;
+    newShape->ymin = 1.0;
+    newShape->ymax = -1.0;
+    break;
+
+  }
+
+  return;
+
+}
+
+/*---------------------------------------------------------------------------*/
+int fits_read_fits_region ( fitsfile *fptr, 
+			    WCSdata *wcs, 
+			    SAORegion **Rgn, 
+			    int *status)
+/*  Read regions from a FITS region extension and return the information     */
+/*  in the "SAORegion" structure.  If it is nonNULL, use wcs to convert the  */
+/*  region coordinates to pixels.  Return an error if region is in degrees   */
+/*  but no WCS data is provided.                                             */
+/*---------------------------------------------------------------------------*/
+{
+
+  int i, j, icol[6], idum, anynul, npos;
+  int dotransform, got_component = 1, tstatus;
+  long icsize[6];
+  double X, Y, Theta, Xsave, Ysave, Xpos, Ypos;
+  double *coords;
+  char *cvalue, *cvalue2;
+  char comment[FLEN_COMMENT];
+  char colname[6][FLEN_VALUE] = {"X", "Y", "SHAPE", "R", "ROTANG", "COMPONENT"};
+  char shapename[17][FLEN_VALUE] = {"POINT","CIRCLE","ELLIPSE","ANNULUS",
+				    "ELLIPTANNULUS","BOX","ROTBOX","BOXANNULUS",
+				    "RECTANGLE","ROTRECTANGLE","POLYGON","PIE",
+				    "SECTOR","DIAMOND","RHOMBUS","ROTDIAMOND",
+				    "ROTRHOMBUS"};
+  int shapetype[17] = {point_rgn, circle_rgn, ellipse_rgn, annulus_rgn, 
+		       elliptannulus_rgn, box_rgn, box_rgn, boxannulus_rgn, 
+		       rectangle_rgn, rectangle_rgn, poly_rgn, sector_rgn, 
+		       sector_rgn, diamond_rgn, diamond_rgn, diamond_rgn, 
+		       diamond_rgn};
+  SAORegion *aRgn;
+  RgnShape *newShape;
+  WCSdata *regwcs;
+
+  if ( *status ) return( *status );
+
+  aRgn = (SAORegion *)malloc( sizeof(SAORegion) );
+  if( ! aRgn ) {
+    ffpmsg("Couldn't allocate memory to hold Region file contents.");
+    return(*status = MEMORY_ALLOCATION );
+  }
+  aRgn->nShapes    =    0;
+  aRgn->Shapes     = NULL;
+  if( wcs && wcs->exists )
+    aRgn->wcs = *wcs;
+  else
+    aRgn->wcs.exists = 0;
+
+  /* See if we are already positioned to a region extension, else */
+  /* move to the REGION extension (file is already open). */
+
+  tstatus = 0;
+  for (i=0; i<5; i++) {
+    ffgcno(fptr, CASEINSEN, colname[i], &icol[i], &tstatus);
+  }
+
+  if (tstatus) {
+    /* couldn't find the required columns, so search for "REGION" extension */
+    if ( ffmnhd(fptr, BINARY_TBL, "REGION", 1, status) ) {
+      ffpmsg("Could not move to REGION extension.");
+      goto error;
+    }
+  }
+
+  /* get the number of shapes and allocate memory */
+
+  if ( ffgky(fptr, TINT, "NAXIS2", &aRgn->nShapes, comment, status) ) {
+    ffpmsg("Could not read NAXIS2 keyword.");
+    goto error;
+  }
+
+  aRgn->Shapes = (RgnShape *) malloc(aRgn->nShapes * sizeof(RgnShape));
+  if ( !aRgn->Shapes ) {
+    ffpmsg( "Failed to allocate memory for Region data");
+    *status = MEMORY_ALLOCATION;
+    goto error;
+  }
+
+  /* get the required column numbers */
+
+  for (i=0; i<5; i++) {
+    if ( ffgcno(fptr, CASEINSEN, colname[i], &icol[i], status) ) {
+      ffpmsg("Could not find column.");
+      goto error;
+    }
+  }
+
+  /* try to get the optional column numbers */
+
+  if ( ffgcno(fptr, CASEINSEN, colname[5], &icol[5], status) ) {
+       got_component = 0;
+  }
+
+  /* if there was input WCS then read the WCS info for the region in case they */
+  /* are different and we have to transform */
+
+  dotransform = 0;
+  if ( aRgn->wcs.exists ) {
+    regwcs = (WCSdata *) malloc ( sizeof(WCSdata) );
+    if ( !regwcs ) {
+      ffpmsg( "Failed to allocate memory for Region WCS data");
+      *status = MEMORY_ALLOCATION;
+      goto error;
+    }
+
+    regwcs->exists = 1;
+    if ( ffgtcs(fptr, icol[0], icol[1], &regwcs->xrefval,  &regwcs->yrefval,
+		&regwcs->xrefpix, &regwcs->yrefpix, &regwcs->xinc, &regwcs->yinc,
+		&regwcs->rot, regwcs->type, status) ) {
+      regwcs->exists = 0;
+      *status = 0;
+    }
+
+    if ( regwcs->exists && wcs->exists ) {
+      if ( fabs(regwcs->xrefval-wcs->xrefval) > 1.0e-6 ||
+	   fabs(regwcs->yrefval-wcs->yrefval) > 1.0e-6 ||
+	   fabs(regwcs->xrefpix-wcs->xrefpix) > 1.0e-6 ||
+	   fabs(regwcs->yrefpix-wcs->yrefpix) > 1.0e-6 ||
+	   fabs(regwcs->xinc-wcs->xinc) > 1.0e-6 ||
+	   fabs(regwcs->yinc-wcs->yinc) > 1.0e-6 ||
+	   fabs(regwcs->rot-wcs->rot) > 1.0e-6 ||
+	   !strcmp(regwcs->type,wcs->type) ) dotransform = 1;
+    }
+  }
+
+  /* get the sizes of the X, Y, R, and ROTANG vectors */
+
+  for (i=0; i<6; i++) {
+    if ( ffgtdm(fptr, icol[i], 1, &idum, &icsize[i], status) ) {
+      ffpmsg("Could not find vector size of column.");
+      goto error;
+    }
+  }
+
+  cvalue = (char *) malloc ((FLEN_VALUE+1)*sizeof(char));
+
+  /* loop over the shapes - note 1-based counting for rows in FITS files */
+
+  for (i=1; i<=aRgn->nShapes; i++) {
+
+    newShape = &aRgn->Shapes[i-1];
+    for (j=0; j<8; j++) newShape->param.gen.p[j] = 0.0;
+    newShape->param.gen.a = 0.0;
+    newShape->param.gen.b = 0.0;
+    newShape->param.gen.sinT = 0.0;
+    newShape->param.gen.cosT = 0.0;
+
+    /* get the shape */
+
+    if ( ffgcvs(fptr, icol[2], i, 1, 1, " ", &cvalue, &anynul, status) ) {
+      ffpmsg("Could not read shape.");
+      goto error;
+    }
+
+    /* set include or exclude */
+
+    newShape->sign = 1;
+    cvalue2 = cvalue;
+    if ( !strncmp(cvalue,"!",1) ) {
+      newShape->sign = 0;
+      cvalue2++;
+    }
+
+    /* set the shape type */
+
+    for (j=0; j<9; j++) {
+      if ( !strcmp(cvalue2, shapename[j]) ) newShape->shape = shapetype[j];
+    }
+
+    /* allocate memory for polygon case and set coords pointer */
+
+    if ( newShape->shape == poly_rgn ) {
+      newShape->param.poly.Pts = (double *) calloc (2*icsize[0], sizeof(double));
+      if ( !newShape->param.poly.Pts ) {
+	ffpmsg("Could not allocate memory to hold polygon parameters" );
+	*status = MEMORY_ALLOCATION;
+	goto error;
+      }
+      newShape->param.poly.nPts = 2*icsize[0];
+      coords = newShape->param.poly.Pts;
+    } else {
+      coords = newShape->param.gen.p;
+    }
+
+
+  /* read X and Y. Polygon and Rectangle require special cases */
+
+    npos = 1;
+    if ( newShape->shape == poly_rgn ) npos = newShape->param.poly.nPts/2;
+    if ( newShape->shape == rectangle_rgn ) npos = 2;
+
+    for (j=0; j<npos; j++) {
+      if ( ffgcvd(fptr, icol[0], i, j+1, 1, DOUBLENULLVALUE, coords, &anynul, status) ) {
+	ffpmsg("Failed to read X column for polygon region");
+	goto error;
+      }
+      if (*coords == DOUBLENULLVALUE) {  /* check for null value end of array marker */
+        npos = j;
+	newShape->param.poly.nPts = npos * 2;
+	break;
+      }
+      coords++;
+      
+      if ( ffgcvd(fptr, icol[1], i, j+1, 1, DOUBLENULLVALUE, coords, &anynul, status) ) {
+	ffpmsg("Failed to read Y column for polygon region");
+	goto error;
+      }
+      if (*coords == DOUBLENULLVALUE) { /* check for null value end of array marker */
+        npos = j;
+	newShape->param.poly.nPts = npos * 2;
+        coords--;
+	break;
+      }
+      coords++;
+ 
+      if (j == 0) {  /* save the first X and Y coordinate */
+        Xsave = *(coords - 2);
+	Ysave = *(coords - 1);
+      } else if ((Xsave == *(coords - 2)) && (Ysave == *(coords - 1)) ) {
+        /* if point has same coordinate as first point, this marks the end of the array */
+        npos = j + 1;
+	newShape->param.poly.nPts = npos * 2;
+	break;
+      }
+    }
+
+    /* transform positions if the region and input wcs differ */
+
+    if ( dotransform ) {
+
+      coords -= npos*2;
+      Xsave = coords[0];
+      Ysave = coords[1];
+      for (j=0; j<npos; j++) {
+	ffwldp(coords[2*j], coords[2*j+1], regwcs->xrefval, regwcs->yrefval, regwcs->xrefpix,
+	       regwcs->yrefpix, regwcs->xinc, regwcs->yinc, regwcs->rot,
+	       regwcs->type, &Xpos, &Ypos, status);
+	ffxypx(Xpos, Ypos, wcs->xrefval, wcs->yrefval, wcs->xrefpix,
+	       wcs->yrefpix, wcs->xinc, wcs->yinc, wcs->rot,
+	       wcs->type, &coords[2*j], &coords[2*j+1], status);
+	if ( *status ) {
+	  ffpmsg("Failed to transform coordinates");
+	  goto error;
+	}
+      }
+      coords += npos*2;
+    }
+
+  /* read R. Circle requires one number; Box, Diamond, Ellipse, Annulus, Sector 
+     and Panda two; Boxannulus and Elliptannulus four; Point, Rectangle and 
+     Polygon none. */
+
+    npos = 0;
+    switch ( newShape->shape ) {
+    case circle_rgn: 
+      npos = 1;
+      break;
+    case box_rgn:
+    case diamond_rgn:
+    case ellipse_rgn:
+    case annulus_rgn:
+    case sector_rgn:
+      npos = 2;
+      break;
+    case boxannulus_rgn:
+    case elliptannulus_rgn:
+      npos = 4;
+      break;
+    }
+
+    if ( npos > 0 ) {
+      if ( ffgcvd(fptr, icol[3], i, 1, npos, 0.0, coords, &anynul, status) ) {
+	ffpmsg("Failed to read R column for region");
+	goto error;
+      }
+
+    /* transform lengths if the region and input wcs differ */
+
+      if ( dotransform ) {
+	for (j=0; j<npos; j++) {
+	  Y = Ysave + (*coords);
+	  X = Xsave;
+	  ffwldp(X, Y, regwcs->xrefval, regwcs->yrefval, regwcs->xrefpix,
+		 regwcs->yrefpix, regwcs->xinc, regwcs->yinc, regwcs->rot,
+		 regwcs->type, &Xpos, &Ypos, status);
+	  ffxypx(Xpos, Ypos, wcs->xrefval, wcs->yrefval, wcs->xrefpix,
+		 wcs->yrefpix, wcs->xinc, wcs->yinc, wcs->rot,
+		 wcs->type, &X, &Y, status);
+	  if ( *status ) {
+	    ffpmsg("Failed to transform coordinates");
+	    goto error;
+	  }
+	  *(coords++) = sqrt(pow(X-newShape->param.gen.p[0],2)+pow(Y-newShape->param.gen.p[1],2));
+	}
+      } else {
+	coords += npos;
+      }
+    }
+
+  /* read ROTANG. Requires two values for Boxannulus, Elliptannulus, Sector, 
+     Panda; one for Box, Diamond, Ellipse; and none for Circle, Point, Annulus, 
+     Rectangle, Polygon */
+
+    npos = 0;
+    switch ( newShape->shape ) {
+    case box_rgn:
+    case diamond_rgn:
+    case ellipse_rgn:
+      npos = 1;
+      break;
+    case boxannulus_rgn:
+    case elliptannulus_rgn:
+    case sector_rgn:
+      npos = 2;
+      break;
+    }
+
+    if ( npos > 0 ) {
+      if ( ffgcvd(fptr, icol[4], i, 1, npos, 0.0, coords, &anynul, status) ) {
+	ffpmsg("Failed to read ROTANG column for region");
+	goto error;
+      }
+
+    /* transform angles if the region and input wcs differ */
+
+      if ( dotransform ) {
+	Theta = (wcs->rot) - (regwcs->rot);
+	for (j=0; j<npos; j++) *(coords++) += Theta;
+      } else {
+	coords += npos;
+      }
+    }
+
+  /* read the component number */
+
+    if (got_component) {
+      if ( ffgcv(fptr, TINT, icol[5], i, 1, 1, 0, &newShape->comp, &anynul, status) ) {
+        ffpmsg("Failed to read COMPONENT column for region");
+        goto error;
+      }
+    } else {
+      newShape->comp = 1;
+    }
+
+
+    /* do some precalculations to speed up tests */
+
+    fits_setup_shape(newShape);
+
+    /* end loop over shapes */
+
+  }
+
+error:
+
+   if( *status )
+      fits_free_region( aRgn );
+   else
+      *Rgn = aRgn;
+
+   ffclos(fptr, status);
+
+   return( *status );
+}
+
diff --git a/src/plugins/cfitsio/region.h b/src/plugins/cfitsio/region.h
new file mode 100644
index 0000000..516c4fd
--- /dev/null
+++ b/src/plugins/cfitsio/region.h
@@ -0,0 +1,82 @@
+/***************************************************************/
+/*                   REGION STUFF                              */
+/***************************************************************/
+
+#include "fitsio.h"
+#define myPI  3.1415926535897932385
+#define RadToDeg 180.0/myPI
+
+typedef struct {
+   int    exists;
+   double xrefval, yrefval;
+   double xrefpix, yrefpix;
+   double xinc,    yinc;
+   double rot;
+   char   type[6];
+} WCSdata;
+
+typedef enum {
+   point_rgn,
+   line_rgn,
+   circle_rgn,
+   annulus_rgn,
+   ellipse_rgn,
+   elliptannulus_rgn,
+   box_rgn,
+   boxannulus_rgn,
+   rectangle_rgn,
+   diamond_rgn,
+   sector_rgn,
+   poly_rgn,
+   panda_rgn,
+   epanda_rgn,
+   bpanda_rgn
+} shapeType;
+
+typedef enum { pixel_fmt, degree_fmt, hhmmss_fmt } coordFmt;
+   
+typedef struct {
+   char      sign;        /*  Include or exclude?        */
+   shapeType shape;       /*  Shape of this region       */
+   int       comp;        /*  Component number for this region */
+
+   double xmin,xmax;       /*  bounding box    */
+   double ymin,ymax;
+
+   union {                /*  Parameters - In pixels     */
+
+      /****   Generic Shape Data   ****/
+
+      struct {
+	 double p[11];       /*  Region parameters       */
+	 double sinT, cosT;  /*  For rotated shapes      */
+	 double a, b;        /*  Extra scratch area      */
+      } gen;
+
+      /****      Polygon Data      ****/
+
+      struct {
+         int    nPts;        /*  Number of Polygon pts   */
+         double *Pts;        /*  Polygon points          */
+      } poly;
+
+   } param;
+
+} RgnShape;
+
+typedef struct {
+   int       nShapes;
+   RgnShape  *Shapes;
+   WCSdata   wcs;
+} SAORegion;
+
+/*  SAO region file routines */
+int  fits_read_rgnfile( const char *filename, WCSdata *wcs, SAORegion **Rgn, int *status );
+int  fits_in_region( double X, double Y, SAORegion *Rgn );
+void fits_free_region( SAORegion *Rgn );
+void fits_set_region_components ( SAORegion *Rgn );
+void fits_setup_shape ( RgnShape *shape);
+int fits_read_fits_region ( fitsfile *fptr, WCSdata * wcs, SAORegion **Rgn, int *status);
+int fits_read_ascii_region ( const char *filename, WCSdata * wcs, SAORegion **Rgn, int *status);
+
+
diff --git a/src/plugins/cfitsio/ricecomp.c b/src/plugins/cfitsio/ricecomp.c
new file mode 100644
index 0000000..d5a11a4
--- /dev/null
+++ b/src/plugins/cfitsio/ricecomp.c
@@ -0,0 +1,1382 @@
+/*
+  The following code was written by Richard White at STScI and made
+  available for use in CFITSIO in July 1999.  These routines were
+  originally contained in 2 source files: rcomp.c and rdecomp.c,
+  and the 'include' file now called ricecomp.h was originally called buffer.h.
+*/
+
+/*----------------------------------------------------------*/
+/*                                                          */
+/*    START OF SOURCE FILE ORIGINALLY CALLED rcomp.c        */
+/*                                                          */
+/*----------------------------------------------------------*/
+/* @(#) rcomp.c 1.5 99/03/01 12:40:27 */
+/* rcomp.c	Compress image line using
+ *		(1) Difference of adjacent pixels
+ *		(2) Rice algorithm coding
+ *
+ * Returns number of bytes written to code buffer or
+ * -1 on failure
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+typedef unsigned char Buffer_t;
+
+typedef struct {
+	int bitbuffer;		/* bit buffer					*/
+	int bits_to_go;		/* bits to go in buffer			*/
+	Buffer_t *start;	/* start of buffer				*/
+	Buffer_t *current;	/* current position in buffer	*/
+	Buffer_t *end;		/* end of buffer				*/
+} Buffer;
+
+#define putcbuf(c,mf) 	((*(mf->current)++ = c), 0)
+
+#include "fitsio2.h"
+
+static void start_outputing_bits(Buffer *buffer);
+static int done_outputing_bits(Buffer *buffer);
+static int output_nbits(Buffer *buffer, int bits, int n);
+
+/* this routine used to be called 'rcomp'  (WDP)  */
+/*---------------------------------------------------------------------------*/
+
+int fits_rcomp(int a[],		/* input array			*/
+	  int nx,		/* number of input pixels	*/
+	  unsigned char *c,	/* output buffer		*/
+	  int clen,		/* max length of output		*/
+	  int nblock)		/* coding block size		*/
+{
+Buffer bufmem, *buffer = &bufmem;
+/* int bsize;  */
+int i, j, thisblock;
+int lastpix, nextpix, pdiff;
+int v, fs, fsmask, top, fsmax, fsbits, bbits;
+int lbitbuffer, lbits_to_go;
+unsigned int psum;
+double pixelsum, dpsum;
+unsigned int *diff;
+
+    /*
+     * Original size of each pixel (bsize, bytes) and coding block
+     * size (nblock, pixels)
+     * Could make bsize a parameter to allow more efficient
+     * compression of short & byte images.
+     */
+/*    bsize = 4;   */
+
+/*    nblock = 32; now an input parameter*/
+    /*
+     * From bsize derive:
+     * FSBITS = # bits required to store FS
+     * FSMAX = maximum value for FS
+     * BBITS = bits/pixel for direct coding
+     */
+
+/*
+    switch (bsize) {
+    case 1:
+	fsbits = 3;
+	fsmax = 6;
+	break;
+    case 2:
+	fsbits = 4;
+	fsmax = 14;
+	break;
+    case 4:
+	fsbits = 5;
+	fsmax = 25;
+	break;
+    default:
+        ffpmsg("rdecomp: bsize must be 1, 2, or 4 bytes");
+	return(-1);
+    }
+*/
+
+    /* move out of switch block, to tweak performance */
+    fsbits = 5;
+    fsmax = 25;
+    bbits = 1<<fsbits;
+
+    /*
+     * Set up buffer pointers
+     */
+    buffer->start = c;
+    buffer->current = c;
+    buffer->end = c+clen;
+    buffer->bits_to_go = 8;
+    /*
+     * array for differences mapped to non-negative values
+     */
+    diff = (unsigned int *) malloc(nblock*sizeof(unsigned int));
+    if (diff == (unsigned int *) NULL) {
+        ffpmsg("fits_rcomp: insufficient memory");
+	return(-1);
+    }
+    /*
+     * Code in blocks of nblock pixels
+     */
+    start_outputing_bits(buffer);
+
+    /* write out first int value to the first 4 bytes of the buffer */
+    if (output_nbits(buffer, a[0], 32) == EOF) {
+        ffpmsg("rice_encode: end of buffer");
+        free(diff);
+        return(-1);
+    }
+
+    lastpix = a[0];  /* the first difference will always be zero */
+
+    thisblock = nblock;
+    for (i=0; i<nx; i += nblock) {
+	/* last block may be shorter */
+	if (nx-i < nblock) thisblock = nx-i;
+	/*
+	 * Compute differences of adjacent pixels and map them to unsigned values.
+	 * Note that this may overflow the integer variables -- that's
+	 * OK, because we can recover when decompressing.  If we were
+	 * compressing shorts or bytes, would want to do this arithmetic
+	 * with short/byte working variables (though diff will still be
+	 * passed as an int.)
+	 *
+	 * compute sum of mapped pixel values at same time
+	 * use double precision for sum to allow 32-bit integer inputs
+	 */
+	pixelsum = 0.0;
+	for (j=0; j<thisblock; j++) {
+	    nextpix = a[i+j];
+	    pdiff = nextpix - lastpix;
+	    diff[j] = (unsigned int) ((pdiff<0) ? ~(pdiff<<1) : (pdiff<<1));
+	    pixelsum += diff[j];
+	    lastpix = nextpix;
+	}
+
+	/*
+	 * compute number of bits to split from sum
+	 */
+	dpsum = (pixelsum - (thisblock/2) - 1)/thisblock;
+	if (dpsum < 0) dpsum = 0.0;
+	psum = ((unsigned int) dpsum ) >> 1;
+	for (fs = 0; psum>0; fs++) psum >>= 1;
+
+	/*
+	 * write the codes
+	 * fsbits ID bits used to indicate split level
+	 */
+	if (fs >= fsmax) {
+	    /* Special high entropy case when FS >= fsmax
+	     * Just write pixel difference values directly, no Rice coding at all.
+	     */
+	    if (output_nbits(buffer, fsmax+1, fsbits) == EOF) {
+                ffpmsg("rice_encode: end of buffer");
+                free(diff);
+		return(-1);
+	    }
+	    for (j=0; j<thisblock; j++) {
+		if (output_nbits(buffer, diff[j], bbits) == EOF) {
+                    ffpmsg("rice_encode: end of buffer");
+                    free(diff);
+		    return(-1);
+		}
+	    }
+	} else if (fs == 0 && pixelsum == 0) {
+	    /*
+	     * special low entropy case when FS = 0 and pixelsum=0 (all
+	     * pixels in block are zero.)
+	     * Output a 0 and return
+	     */
+	    if (output_nbits(buffer, 0, fsbits) == EOF) {
+                ffpmsg("rice_encode: end of buffer");
+                free(diff);
+		return(-1);
+	    }
+	} else {
+	    /* normal case: not either very high or very low entropy */
+	    if (output_nbits(buffer, fs+1, fsbits) == EOF) {
+                ffpmsg("rice_encode: end of buffer");
+                free(diff);
+		return(-1);
+	    }
+	    fsmask = (1<<fs) - 1;
+	    /*
+	     * local copies of bit buffer to improve optimization
+	     */
+	    lbitbuffer = buffer->bitbuffer;
+	    lbits_to_go = buffer->bits_to_go;
+	    for (j=0; j<thisblock; j++) {
+		v = diff[j];
+		top = v >> fs;
+		/*
+		 * top is coded by top zeros + 1
+		 */
+		if (lbits_to_go >= top+1) {
+		    lbitbuffer <<= top+1;
+		    lbitbuffer |= 1;
+		    lbits_to_go -= top+1;
+		} else {
+		    lbitbuffer <<= lbits_to_go;
+		    putcbuf(lbitbuffer & 0xff,buffer);
+
+		    for (top -= lbits_to_go; top>=8; top -= 8) {
+			putcbuf(0, buffer);
+		    }
+		    lbitbuffer = 1;
+		    lbits_to_go = 7-top;
+		}
+		/*
+		 * bottom FS bits are written without coding
+		 * code is output_nbits, moved into this routine to reduce overheads
+		 * This code potentially breaks if FS>24, so I am limiting
+		 * FS to 24 by choice of FSMAX above.
+		 */
+		if (fs > 0) {
+		    lbitbuffer <<= fs;
+		    lbitbuffer |= v & fsmask;
+		    lbits_to_go -= fs;
+		    while (lbits_to_go <= 0) {
+			putcbuf((lbitbuffer>>(-lbits_to_go)) & 0xff,buffer);
+			lbits_to_go += 8;
+		    }
+		}
+	    }
+
+	    /* check if overflowed output buffer */
+	    if (buffer->current > buffer->end) {
+                 ffpmsg("rice_encode: end of buffer");
+                 free(diff);
+		 return(-1);
+	    }
+	    buffer->bitbuffer = lbitbuffer;
+	    buffer->bits_to_go = lbits_to_go;
+	}
+    }
+    done_outputing_bits(buffer);
+    free(diff);
+    /*
+     * return number of bytes used
+     */
+    return(buffer->current - buffer->start);
+}
+/*---------------------------------------------------------------------------*/
+
+int fits_rcomp_short(
+	  short a[],		/* input array			*/
+	  int nx,		/* number of input pixels	*/
+	  unsigned char *c,	/* output buffer		*/
+	  int clen,		/* max length of output		*/
+	  int nblock)		/* coding block size		*/
+{
+Buffer bufmem, *buffer = &bufmem;
+/* int bsize;  */
+int i, j, thisblock;
+
+/* 
+NOTE: in principle, the following 2 variable could be declared as 'short'
+but in fact the code runs faster (on 32-bit Linux at least) as 'int'
+*/
+int lastpix, nextpix;
+/* int pdiff; */
+short pdiff; 
+int v, fs, fsmask, top, fsmax, fsbits, bbits;
+int lbitbuffer, lbits_to_go;
+/* unsigned int psum; */
+unsigned short psum;
+double pixelsum, dpsum;
+unsigned int *diff;
+
+    /*
+     * Original size of each pixel (bsize, bytes) and coding block
+     * size (nblock, pixels)
+     * Could make bsize a parameter to allow more efficient
+     * compression of short & byte images.
+     */
+/*    bsize = 2; */
+
+/*    nblock = 32; now an input parameter */
+    /*
+     * From bsize derive:
+     * FSBITS = # bits required to store FS
+     * FSMAX = maximum value for FS
+     * BBITS = bits/pixel for direct coding
+     */
+
+/*
+    switch (bsize) {
+    case 1:
+	fsbits = 3;
+	fsmax = 6;
+	break;
+    case 2:
+	fsbits = 4;
+	fsmax = 14;
+	break;
+    case 4:
+	fsbits = 5;
+	fsmax = 25;
+	break;
+    default:
+        ffpmsg("rdecomp: bsize must be 1, 2, or 4 bytes");
+	return(-1);
+    }
+*/
+
+    /* move these out of switch block to further tweak performance */
+    fsbits = 4;
+    fsmax = 14;
+    
+    bbits = 1<<fsbits;
+
+    /*
+     * Set up buffer pointers
+     */
+    buffer->start = c;
+    buffer->current = c;
+    buffer->end = c+clen;
+    buffer->bits_to_go = 8;
+    /*
+     * array for differences mapped to non-negative values
+     */
+    diff = (unsigned int *) malloc(nblock*sizeof(unsigned int));
+    if (diff == (unsigned int *) NULL) {
+        ffpmsg("fits_rcomp: insufficient memory");
+	return(-1);
+    }
+    /*
+     * Code in blocks of nblock pixels
+     */
+    start_outputing_bits(buffer);
+
+    /* write out first short value to the first 2 bytes of the buffer */
+    if (output_nbits(buffer, a[0], 16) == EOF) {
+        ffpmsg("rice_encode: end of buffer");
+        free(diff);
+        return(-1);
+    }
+
+    lastpix = a[0];  /* the first difference will always be zero */
+
+    thisblock = nblock;
+    for (i=0; i<nx; i += nblock) {
+	/* last block may be shorter */
+	if (nx-i < nblock) thisblock = nx-i;
+	/*
+	 * Compute differences of adjacent pixels and map them to unsigned values.
+	 * Note that this may overflow the integer variables -- that's
+	 * OK, because we can recover when decompressing.  If we were
+	 * compressing shorts or bytes, would want to do this arithmetic
+	 * with short/byte working variables (though diff will still be
+	 * passed as an int.)
+	 *
+	 * compute sum of mapped pixel values at same time
+	 * use double precision for sum to allow 32-bit integer inputs
+	 */
+	pixelsum = 0.0;
+	for (j=0; j<thisblock; j++) {
+	    nextpix = a[i+j];
+	    pdiff = nextpix - lastpix;
+	    diff[j] = (unsigned int) ((pdiff<0) ? ~(pdiff<<1) : (pdiff<<1));
+	    pixelsum += diff[j];
+	    lastpix = nextpix;
+	}
+	/*
+	 * compute number of bits to split from sum
+	 */
+	dpsum = (pixelsum - (thisblock/2) - 1)/thisblock;
+	if (dpsum < 0) dpsum = 0.0;
+/*	psum = ((unsigned int) dpsum ) >> 1; */
+	psum = ((unsigned short) dpsum ) >> 1;
+	for (fs = 0; psum>0; fs++) psum >>= 1;
+
+	/*
+	 * write the codes
+	 * fsbits ID bits used to indicate split level
+	 */
+	if (fs >= fsmax) {
+	    /* Special high entropy case when FS >= fsmax
+	     * Just write pixel difference values directly, no Rice coding at all.
+	     */
+	    if (output_nbits(buffer, fsmax+1, fsbits) == EOF) {
+                ffpmsg("rice_encode: end of buffer");
+                free(diff);
+		return(-1);
+	    }
+	    for (j=0; j<thisblock; j++) {
+		if (output_nbits(buffer, diff[j], bbits) == EOF) {
+                    ffpmsg("rice_encode: end of buffer");
+                    free(diff);
+		    return(-1);
+		}
+	    }
+	} else if (fs == 0 && pixelsum == 0) {
+	    /*
+	     * special low entropy case when FS = 0 and pixelsum=0 (all
+	     * pixels in block are zero.)
+	     * Output a 0 and return
+	     */
+	    if (output_nbits(buffer, 0, fsbits) == EOF) {
+                ffpmsg("rice_encode: end of buffer");
+                free(diff);
+		return(-1);
+	    }
+	} else {
+	    /* normal case: not either very high or very low entropy */
+	    if (output_nbits(buffer, fs+1, fsbits) == EOF) {
+                ffpmsg("rice_encode: end of buffer");
+                free(diff);
+		return(-1);
+	    }
+	    fsmask = (1<<fs) - 1;
+	    /*
+	     * local copies of bit buffer to improve optimization
+	     */
+	    lbitbuffer = buffer->bitbuffer;
+	    lbits_to_go = buffer->bits_to_go;
+	    for (j=0; j<thisblock; j++) {
+		v = diff[j];
+		top = v >> fs;
+		/*
+		 * top is coded by top zeros + 1
+		 */
+		if (lbits_to_go >= top+1) {
+		    lbitbuffer <<= top+1;
+		    lbitbuffer |= 1;
+		    lbits_to_go -= top+1;
+		} else {
+		    lbitbuffer <<= lbits_to_go;
+		    putcbuf(lbitbuffer & 0xff,buffer);
+		    for (top -= lbits_to_go; top>=8; top -= 8) {
+			putcbuf(0, buffer);
+		    }
+		    lbitbuffer = 1;
+		    lbits_to_go = 7-top;
+		}
+		/*
+		 * bottom FS bits are written without coding
+		 * code is output_nbits, moved into this routine to reduce overheads
+		 * This code potentially breaks if FS>24, so I am limiting
+		 * FS to 24 by choice of FSMAX above.
+		 */
+		if (fs > 0) {
+		    lbitbuffer <<= fs;
+		    lbitbuffer |= v & fsmask;
+		    lbits_to_go -= fs;
+		    while (lbits_to_go <= 0) {
+			putcbuf((lbitbuffer>>(-lbits_to_go)) & 0xff,buffer);
+			lbits_to_go += 8;
+		    }
+		}
+	    }
+	    /* check if overflowed output buffer */
+	    if (buffer->current > buffer->end) {
+                 ffpmsg("rice_encode: end of buffer");
+                 free(diff);
+		 return(-1);
+	    }
+	    buffer->bitbuffer = lbitbuffer;
+	    buffer->bits_to_go = lbits_to_go;
+	}
+    }
+    done_outputing_bits(buffer);
+    free(diff);
+    /*
+     * return number of bytes used
+     */
+    return(buffer->current - buffer->start);
+}
+/*---------------------------------------------------------------------------*/
+
+int fits_rcomp_byte(
+	  signed char a[],		/* input array			*/
+	  int nx,		/* number of input pixels	*/
+	  unsigned char *c,	/* output buffer		*/
+	  int clen,		/* max length of output		*/
+	  int nblock)		/* coding block size		*/
+{
+Buffer bufmem, *buffer = &bufmem;
+/* int bsize; */
+int i, j, thisblock;
+
+/* 
+NOTE: in principle, the following 2 variable could be declared as 'short'
+but in fact the code runs faster (on 32-bit Linux at least) as 'int'
+*/
+int lastpix, nextpix;
+/* int pdiff; */
+signed char pdiff; 
+int v, fs, fsmask, top, fsmax, fsbits, bbits;
+int lbitbuffer, lbits_to_go;
+/* unsigned int psum; */
+unsigned char psum;
+double pixelsum, dpsum;
+unsigned int *diff;
+
+    /*
+     * Original size of each pixel (bsize, bytes) and coding block
+     * size (nblock, pixels)
+     * Could make bsize a parameter to allow more efficient
+     * compression of short & byte images.
+     */
+/*    bsize = 1;  */
+
+/*    nblock = 32; now an input parameter */
+    /*
+     * From bsize derive:
+     * FSBITS = # bits required to store FS
+     * FSMAX = maximum value for FS
+     * BBITS = bits/pixel for direct coding
+     */
+
+/*
+    switch (bsize) {
+    case 1:
+	fsbits = 3;
+	fsmax = 6;
+	break;
+    case 2:
+	fsbits = 4;
+	fsmax = 14;
+	break;
+    case 4:
+	fsbits = 5;
+	fsmax = 25;
+	break;
+    default:
+        ffpmsg("rdecomp: bsize must be 1, 2, or 4 bytes");
+	return(-1);
+    }
+*/
+
+    /* move these out of switch block to further tweak performance */
+    fsbits = 3;
+    fsmax = 6;
+    bbits = 1<<fsbits;
+
+    /*
+     * Set up buffer pointers
+     */
+    buffer->start = c;
+    buffer->current = c;
+    buffer->end = c+clen;
+    buffer->bits_to_go = 8;
+    /*
+     * array for differences mapped to non-negative values
+     */
+    diff = (unsigned int *) malloc(nblock*sizeof(unsigned int));
+    if (diff == (unsigned int *) NULL) {
+        ffpmsg("fits_rcomp: insufficient memory");
+	return(-1);
+    }
+    /*
+     * Code in blocks of nblock pixels
+     */
+    start_outputing_bits(buffer);
+
+    /* write out first byte value to the first  byte of the buffer */
+    if (output_nbits(buffer, a[0], 8) == EOF) {
+        ffpmsg("rice_encode: end of buffer");
+        free(diff);
+        return(-1);
+    }
+
+    lastpix = a[0];  /* the first difference will always be zero */
+
+    thisblock = nblock;
+    for (i=0; i<nx; i += nblock) {
+	/* last block may be shorter */
+	if (nx-i < nblock) thisblock = nx-i;
+	/*
+	 * Compute differences of adjacent pixels and map them to unsigned values.
+	 * Note that this may overflow the integer variables -- that's
+	 * OK, because we can recover when decompressing.  If we were
+	 * compressing shorts or bytes, would want to do this arithmetic
+	 * with short/byte working variables (though diff will still be
+	 * passed as an int.)
+	 *
+	 * compute sum of mapped pixel values at same time
+	 * use double precision for sum to allow 32-bit integer inputs
+	 */
+	pixelsum = 0.0;
+	for (j=0; j<thisblock; j++) {
+	    nextpix = a[i+j];
+	    pdiff = nextpix - lastpix;
+	    diff[j] = (unsigned int) ((pdiff<0) ? ~(pdiff<<1) : (pdiff<<1));
+	    pixelsum += diff[j];
+	    lastpix = nextpix;
+	}
+	/*
+	 * compute number of bits to split from sum
+	 */
+	dpsum = (pixelsum - (thisblock/2) - 1)/thisblock;
+	if (dpsum < 0) dpsum = 0.0;
+/*	psum = ((unsigned int) dpsum ) >> 1; */
+	psum = ((unsigned char) dpsum ) >> 1;
+	for (fs = 0; psum>0; fs++) psum >>= 1;
+
+	/*
+	 * write the codes
+	 * fsbits ID bits used to indicate split level
+	 */
+	if (fs >= fsmax) {
+	    /* Special high entropy case when FS >= fsmax
+	     * Just write pixel difference values directly, no Rice coding at all.
+	     */
+	    if (output_nbits(buffer, fsmax+1, fsbits) == EOF) {
+                ffpmsg("rice_encode: end of buffer");
+                free(diff);
+		return(-1);
+	    }
+	    for (j=0; j<thisblock; j++) {
+		if (output_nbits(buffer, diff[j], bbits) == EOF) {
+                    ffpmsg("rice_encode: end of buffer");
+                    free(diff);
+		    return(-1);
+		}
+	    }
+	} else if (fs == 0 && pixelsum == 0) {
+	    /*
+	     * special low entropy case when FS = 0 and pixelsum=0 (all
+	     * pixels in block are zero.)
+	     * Output a 0 and return
+	     */
+	    if (output_nbits(buffer, 0, fsbits) == EOF) {
+                ffpmsg("rice_encode: end of buffer");
+                free(diff);
+		return(-1);
+	    }
+	} else {
+	    /* normal case: not either very high or very low entropy */
+	    if (output_nbits(buffer, fs+1, fsbits) == EOF) {
+                ffpmsg("rice_encode: end of buffer");
+                free(diff);
+		return(-1);
+	    }
+	    fsmask = (1<<fs) - 1;
+	    /*
+	     * local copies of bit buffer to improve optimization
+	     */
+	    lbitbuffer = buffer->bitbuffer;
+	    lbits_to_go = buffer->bits_to_go;
+	    for (j=0; j<thisblock; j++) {
+		v = diff[j];
+		top = v >> fs;
+		/*
+		 * top is coded by top zeros + 1
+		 */
+		if (lbits_to_go >= top+1) {
+		    lbitbuffer <<= top+1;
+		    lbitbuffer |= 1;
+		    lbits_to_go -= top+1;
+		} else {
+		    lbitbuffer <<= lbits_to_go;
+		    putcbuf(lbitbuffer & 0xff,buffer);
+		    for (top -= lbits_to_go; top>=8; top -= 8) {
+			putcbuf(0, buffer);
+		    }
+		    lbitbuffer = 1;
+		    lbits_to_go = 7-top;
+		}
+		/*
+		 * bottom FS bits are written without coding
+		 * code is output_nbits, moved into this routine to reduce overheads
+		 * This code potentially breaks if FS>24, so I am limiting
+		 * FS to 24 by choice of FSMAX above.
+		 */
+		if (fs > 0) {
+		    lbitbuffer <<= fs;
+		    lbitbuffer |= v & fsmask;
+		    lbits_to_go -= fs;
+		    while (lbits_to_go <= 0) {
+			putcbuf((lbitbuffer>>(-lbits_to_go)) & 0xff,buffer);
+			lbits_to_go += 8;
+		    }
+		}
+	    }
+	    /* check if overflowed output buffer */
+	    if (buffer->current > buffer->end) {
+                 ffpmsg("rice_encode: end of buffer");
+                 free(diff);
+		 return(-1);
+	    }
+	    buffer->bitbuffer = lbitbuffer;
+	    buffer->bits_to_go = lbits_to_go;
+	}
+    }
+    done_outputing_bits(buffer);
+    free(diff);
+    /*
+     * return number of bytes used
+     */
+    return(buffer->current - buffer->start);
+}
+/*---------------------------------------------------------------------------*/
+/* bit_output.c
+ *
+ * Bit output routines
+ * Procedures return zero on success, EOF on end-of-buffer
+ *
+ * Programmer: R. White     Date: 20 July 1998
+ */
+
+/* Initialize for bit output */
+
+static void start_outputing_bits(Buffer *buffer)
+{
+    /*
+     * Buffer is empty to start with
+     */
+    buffer->bitbuffer = 0;
+    buffer->bits_to_go = 8;
+}
+
+/*---------------------------------------------------------------------------*/
+/* Output N bits (N must be <= 32) */
+
+static int output_nbits(Buffer *buffer, int bits, int n)
+{
+/* local copies */
+int lbitbuffer;
+int lbits_to_go;
+    /* AND mask for the right-most n bits */
+    static unsigned int mask[33] = 
+         {0,
+	  0x1,       0x3,       0x7,       0xf,       0x1f,       0x3f,       0x7f,       0xff,
+	  0x1ff,     0x3ff,     0x7ff,     0xfff,     0x1fff,     0x3fff,     0x7fff,     0xffff,
+	  0x1ffff,   0x3ffff,   0x7ffff,   0xfffff,   0x1fffff,   0x3fffff,   0x7fffff,   0xffffff,
+	  0x1ffffff, 0x3ffffff, 0x7ffffff, 0xfffffff, 0x1fffffff, 0x3fffffff, 0x7fffffff, 0xffffffff};
+
+    /*
+     * insert bits at end of bitbuffer
+     */
+    lbitbuffer = buffer->bitbuffer;
+    lbits_to_go = buffer->bits_to_go;
+    if (lbits_to_go+n > 32) {
+	/*
+	 * special case for large n: put out the top lbits_to_go bits first
+	 * note that 0 < lbits_to_go <= 8
+	 */
+	lbitbuffer <<= lbits_to_go;
+/*	lbitbuffer |= (bits>>(n-lbits_to_go)) & ((1<<lbits_to_go)-1); */
+	lbitbuffer |= (bits>>(n-lbits_to_go)) & *(mask+lbits_to_go);
+	putcbuf(lbitbuffer & 0xff,buffer);
+	n -= lbits_to_go;
+	lbits_to_go = 8;
+    }
+    lbitbuffer <<= n;
+/*    lbitbuffer |= ( bits & ((1<<n)-1) ); */
+    lbitbuffer |= ( bits & *(mask+n) );
+    lbits_to_go -= n;
+    while (lbits_to_go <= 0) {
+	/*
+	 * bitbuffer full, put out top 8 bits
+	 */
+	putcbuf((lbitbuffer>>(-lbits_to_go)) & 0xff,buffer);
+	lbits_to_go += 8;
+    }
+    buffer->bitbuffer = lbitbuffer;
+    buffer->bits_to_go = lbits_to_go;
+    return(0);
+}
+/*---------------------------------------------------------------------------*/
+/* Flush out the last bits */
+
+static int done_outputing_bits(Buffer *buffer)
+{
+    if(buffer->bits_to_go < 8) {
+	putcbuf(buffer->bitbuffer<<buffer->bits_to_go,buffer);
+	
+/*	if (putcbuf(buffer->bitbuffer<<buffer->bits_to_go,buffer) == EOF)
+	    return(EOF);
+*/
+    }
+    return(0);
+}
+/*---------------------------------------------------------------------------*/
+/*----------------------------------------------------------*/
+/*                                                          */
+/*    START OF SOURCE FILE ORIGINALLY CALLED rdecomp.c      */
+/*                                                          */
+/*----------------------------------------------------------*/
+
+/* @(#) rdecomp.c 1.4 99/03/01 12:38:41 */
+/* rdecomp.c	Decompress image line using
+ *		(1) Difference of adjacent pixels
+ *		(2) Rice algorithm coding
+ *
+ * Returns 0 on success or 1 on failure
+ */
+
+/*    moved these 'includes' to the beginning of the file (WDP)
+#include <stdio.h>
+#include <stdlib.h>
+*/
+
+/*---------------------------------------------------------------------------*/
+/* this routine used to be called 'rdecomp'  (WDP)  */
+
+int fits_rdecomp (unsigned char *c,		/* input buffer			*/
+	     int clen,			/* length of input		*/
+	     unsigned int array[],	/* output array			*/
+	     int nx,			/* number of output pixels	*/
+	     int nblock)		/* coding block size		*/
+{
+/* int bsize;  */
+int i, k, imax;
+int nbits, nzero, fs;
+unsigned char *cend, bytevalue;
+unsigned int b, diff, lastpix;
+int fsmax, fsbits, bbits;
+static int *nonzero_count = (int *)NULL;
+
+   /*
+     * Original size of each pixel (bsize, bytes) and coding block
+     * size (nblock, pixels)
+     * Could make bsize a parameter to allow more efficient
+     * compression of short & byte images.
+     */
+/*    bsize = 4; */
+
+/*    nblock = 32; now an input parameter */
+    /*
+     * From bsize derive:
+     * FSBITS = # bits required to store FS
+     * FSMAX = maximum value for FS
+     * BBITS = bits/pixel for direct coding
+     */
+
+/*
+    switch (bsize) {
+    case 1:
+	fsbits = 3;
+	fsmax = 6;
+	break;
+    case 2:
+	fsbits = 4;
+	fsmax = 14;
+	break;
+    case 4:
+	fsbits = 5;
+	fsmax = 25;
+	break;
+    default:
+        ffpmsg("rdecomp: bsize must be 1, 2, or 4 bytes");
+	return 1;
+    }
+*/
+
+    /* move out of switch block, to tweak performance */
+    fsbits = 5;
+    fsmax = 25;
+
+    bbits = 1<<fsbits;
+
+    FFLOCK;
+    if (nonzero_count == (int *) NULL) {
+	/*
+	 * nonzero_count is lookup table giving number of bits
+	 * in 8-bit values not including leading zeros
+	 */
+
+        /*  NOTE!!!  This memory never gets freed  */
+	nonzero_count = (int *) malloc(256*sizeof(int));
+	if (nonzero_count == (int *) NULL) {
+            ffpmsg("rdecomp: insufficient memory");
+            FFUNLOCK;
+	    return 1;
+	}
+	nzero = 8;
+	k = 128;
+	for (i=255; i>=0; ) {
+	    for ( ; i>=k; i--) nonzero_count[i] = nzero;
+	    k = k/2;
+	    nzero--;
+	}
+    }
+    FFUNLOCK;
+
+    /*
+     * Decode in blocks of nblock pixels
+     */
+
+    /* first 4 bytes of input buffer contain the value of the first */
+    /* 4 byte integer value, without any encoding */
+    
+    lastpix = 0;
+    bytevalue = c[0];
+    lastpix = lastpix | (bytevalue<<24);
+    bytevalue = c[1];
+    lastpix = lastpix | (bytevalue<<16);
+    bytevalue = c[2];
+    lastpix = lastpix | (bytevalue<<8);
+    bytevalue = c[3];
+    lastpix = lastpix | bytevalue;
+
+    c += 4;  
+    cend = c + clen - 4;
+
+    b = *c++;		    /* bit buffer			*/
+    nbits = 8;		    /* number of bits remaining in b	*/
+    for (i = 0; i<nx; ) {
+	/* get the FS value from first fsbits */
+	nbits -= fsbits;
+	while (nbits < 0) {
+	    b = (b<<8) | (*c++);
+	    nbits += 8;
+	}
+	fs = (b >> nbits) - 1;
+
+	b &= (1<<nbits)-1;
+	/* loop over the next block */
+	imax = i + nblock;
+	if (imax > nx) imax = nx;
+	if (fs<0) {
+	    /* low-entropy case, all zero differences */
+	    for ( ; i<imax; i++) array[i] = lastpix;
+	} else if (fs==fsmax) {
+	    /* high-entropy case, directly coded pixel values */
+	    for ( ; i<imax; i++) {
+		k = bbits - nbits;
+		diff = b<<k;
+		for (k -= 8; k >= 0; k -= 8) {
+		    b = *c++;
+		    diff |= b<<k;
+		}
+		if (nbits>0) {
+		    b = *c++;
+		    diff |= b>>(-k);
+		    b &= (1<<nbits)-1;
+		} else {
+		    b = 0;
+		}
+		/*
+		 * undo mapping and differencing
+		 * Note that some of these operations will overflow the
+		 * unsigned int arithmetic -- that's OK, it all works
+		 * out to give the right answers in the output file.
+		 */
+		if ((diff & 1) == 0) {
+		    diff = diff>>1;
+		} else {
+		    diff = ~(diff>>1);
+		}
+		array[i] = diff+lastpix;
+		lastpix = array[i];
+	    }
+	} else {
+	    /* normal case, Rice coding */
+	    for ( ; i<imax; i++) {
+		/* count number of leading zeros */
+		while (b == 0) {
+		    nbits += 8;
+		    b = *c++;
+		}
+		nzero = nbits - nonzero_count[b];
+		nbits -= nzero+1;
+		/* flip the leading one-bit */
+		b ^= 1<<nbits;
+		/* get the FS trailing bits */
+		nbits -= fs;
+		while (nbits < 0) {
+		    b = (b<<8) | (*c++);
+		    nbits += 8;
+		}
+		diff = (nzero<<fs) | (b>>nbits);
+		b &= (1<<nbits)-1;
+
+		/* undo mapping and differencing */
+		if ((diff & 1) == 0) {
+		    diff = diff>>1;
+		} else {
+		    diff = ~(diff>>1);
+		}
+		array[i] = diff+lastpix;
+		lastpix = array[i];
+	    }
+	}
+	if (c > cend) {
+            ffpmsg("decompression error: hit end of compressed byte stream");
+	    return 1;
+	}
+    }
+    if (c < cend) {
+        ffpmsg("decompression warning: unused bytes at end of compressed buffer");
+    }
+    return 0;
+}
+/*---------------------------------------------------------------------------*/
+/* this routine used to be called 'rdecomp'  (WDP)  */
+
+int fits_rdecomp_short (unsigned char *c,		/* input buffer			*/
+	     int clen,			/* length of input		*/
+	     unsigned short array[],  	/* output array			*/
+	     int nx,			/* number of output pixels	*/
+	     int nblock)		/* coding block size		*/
+{
+int i, imax;
+/* int bsize; */
+int k;
+int nbits, nzero, fs;
+unsigned char *cend, bytevalue;
+unsigned int b, diff, lastpix;
+int fsmax, fsbits, bbits;
+static int *nonzero_count = (int *)NULL;
+
+   /*
+     * Original size of each pixel (bsize, bytes) and coding block
+     * size (nblock, pixels)
+     * Could make bsize a parameter to allow more efficient
+     * compression of short & byte images.
+     */
+
+/*    bsize = 2; */
+    
+/*    nblock = 32; now an input parameter */
+    /*
+     * From bsize derive:
+     * FSBITS = # bits required to store FS
+     * FSMAX = maximum value for FS
+     * BBITS = bits/pixel for direct coding
+     */
+
+/*
+    switch (bsize) {
+    case 1:
+	fsbits = 3;
+	fsmax = 6;
+	break;
+    case 2:
+	fsbits = 4;
+	fsmax = 14;
+	break;
+    case 4:
+	fsbits = 5;
+	fsmax = 25;
+	break;
+    default:
+        ffpmsg("rdecomp: bsize must be 1, 2, or 4 bytes");
+	return 1;
+    }
+*/
+
+    /* move out of switch block, to tweak performance */
+    fsbits = 4;
+    fsmax = 14;
+
+    bbits = 1<<fsbits;
+
+    FFLOCK;
+    if (nonzero_count == (int *) NULL) {
+	/*
+	 * nonzero_count is lookup table giving number of bits
+	 * in 8-bit values not including leading zeros
+	 */
+
+        /*  NOTE!!!  This memory never gets freed  */
+	nonzero_count = (int *) malloc(256*sizeof(int));
+	if (nonzero_count == (int *) NULL) {
+            ffpmsg("rdecomp: insufficient memory");
+	    FFUNLOCK;
+	    return 1;
+	}
+	nzero = 8;
+	k = 128;
+	for (i=255; i>=0; ) {
+	    for ( ; i>=k; i--) nonzero_count[i] = nzero;
+	    k = k/2;
+	    nzero--;
+	}
+    }
+    FFUNLOCK;
+    /*
+     * Decode in blocks of nblock pixels
+     */
+
+    /* first 2 bytes of input buffer contain the value of the first */
+    /* 2 byte integer value, without any encoding */
+    
+    lastpix = 0;
+    bytevalue = c[0];
+    lastpix = lastpix | (bytevalue<<8);
+    bytevalue = c[1];
+    lastpix = lastpix | bytevalue;
+
+    c += 2;  
+    cend = c + clen - 2;
+
+    b = *c++;		    /* bit buffer			*/
+    nbits = 8;		    /* number of bits remaining in b	*/
+    for (i = 0; i<nx; ) {
+	/* get the FS value from first fsbits */
+	nbits -= fsbits;
+	while (nbits < 0) {
+	    b = (b<<8) | (*c++);
+	    nbits += 8;
+	}
+	fs = (b >> nbits) - 1;
+
+	b &= (1<<nbits)-1;
+	/* loop over the next block */
+	imax = i + nblock;
+	if (imax > nx) imax = nx;
+	if (fs<0) {
+	    /* low-entropy case, all zero differences */
+	    for ( ; i<imax; i++) array[i] = lastpix;
+	} else if (fs==fsmax) {
+	    /* high-entropy case, directly coded pixel values */
+	    for ( ; i<imax; i++) {
+		k = bbits - nbits;
+		diff = b<<k;
+		for (k -= 8; k >= 0; k -= 8) {
+		    b = *c++;
+		    diff |= b<<k;
+		}
+		if (nbits>0) {
+		    b = *c++;
+		    diff |= b>>(-k);
+		    b &= (1<<nbits)-1;
+		} else {
+		    b = 0;
+		}
+   
+		/*
+		 * undo mapping and differencing
+		 * Note that some of these operations will overflow the
+		 * unsigned int arithmetic -- that's OK, it all works
+		 * out to give the right answers in the output file.
+		 */
+		if ((diff & 1) == 0) {
+		    diff = diff>>1;
+		} else {
+		    diff = ~(diff>>1);
+		}
+		array[i] = diff+lastpix;
+		lastpix = array[i];
+	    }
+	} else {
+	    /* normal case, Rice coding */
+	    for ( ; i<imax; i++) {
+		/* count number of leading zeros */
+		while (b == 0) {
+		    nbits += 8;
+		    b = *c++;
+		}
+		nzero = nbits - nonzero_count[b];
+		nbits -= nzero+1;
+		/* flip the leading one-bit */
+		b ^= 1<<nbits;
+		/* get the FS trailing bits */
+		nbits -= fs;
+		while (nbits < 0) {
+		    b = (b<<8) | (*c++);
+		    nbits += 8;
+		}
+		diff = (nzero<<fs) | (b>>nbits);
+		b &= (1<<nbits)-1;
+
+		/* undo mapping and differencing */
+		if ((diff & 1) == 0) {
+		    diff = diff>>1;
+		} else {
+		    diff = ~(diff>>1);
+		}
+		array[i] = diff+lastpix;
+		lastpix = array[i];
+	    }
+	}
+	if (c > cend) {
+            ffpmsg("decompression error: hit end of compressed byte stream");
+	    return 1;
+	}
+    }
+    if (c < cend) {
+        ffpmsg("decompression warning: unused bytes at end of compressed buffer");
+    }
+    return 0;
+}
+/*---------------------------------------------------------------------------*/
+/* this routine used to be called 'rdecomp'  (WDP)  */
+
+int fits_rdecomp_byte (unsigned char *c,		/* input buffer			*/
+	     int clen,			/* length of input		*/
+	     unsigned char array[],  	/* output array			*/
+	     int nx,			/* number of output pixels	*/
+	     int nblock)		/* coding block size		*/
+{
+int i, imax;
+/* int bsize; */
+int k;
+int nbits, nzero, fs;
+unsigned char *cend;
+unsigned int b, diff, lastpix;
+int fsmax, fsbits, bbits;
+static int *nonzero_count = (int *)NULL;
+
+   /*
+     * Original size of each pixel (bsize, bytes) and coding block
+     * size (nblock, pixels)
+     * Could make bsize a parameter to allow more efficient
+     * compression of short & byte images.
+     */
+
+/*    bsize = 1; */
+    
+/*    nblock = 32; now an input parameter */
+    /*
+     * From bsize derive:
+     * FSBITS = # bits required to store FS
+     * FSMAX = maximum value for FS
+     * BBITS = bits/pixel for direct coding
+     */
+
+/*
+    switch (bsize) {
+    case 1:
+	fsbits = 3;
+	fsmax = 6;
+	break;
+    case 2:
+	fsbits = 4;
+	fsmax = 14;
+	break;
+    case 4:
+	fsbits = 5;
+	fsmax = 25;
+	break;
+    default:
+        ffpmsg("rdecomp: bsize must be 1, 2, or 4 bytes");
+	return 1;
+    }
+*/
+
+    /* move out of switch block, to tweak performance */
+    fsbits = 3;
+    fsmax = 6;
+
+    bbits = 1<<fsbits;
+
+    FFLOCK;
+    if (nonzero_count == (int *) NULL) {
+	/*
+	 * nonzero_count is lookup table giving number of bits
+	 * in 8-bit values not including leading zeros
+	 */
+
+        /*  NOTE!!!  This memory never gets freed  */
+	nonzero_count = (int *) malloc(256*sizeof(int));
+	if (nonzero_count == (int *) NULL) {
+            ffpmsg("rdecomp: insufficient memory");
+	    FFUNLOCK;
+	    return 1;
+	}
+	nzero = 8;
+	k = 128;
+	for (i=255; i>=0; ) {
+	    for ( ; i>=k; i--) nonzero_count[i] = nzero;
+	    k = k/2;
+	    nzero--;
+	}
+    }
+    FFUNLOCK;
+    /*
+     * Decode in blocks of nblock pixels
+     */
+
+    /* first byte of input buffer contain the value of the first */
+    /* byte integer value, without any encoding */
+    
+    lastpix = c[0];
+    c += 1;  
+    cend = c + clen - 1;
+
+    b = *c++;		    /* bit buffer			*/
+    nbits = 8;		    /* number of bits remaining in b	*/
+    for (i = 0; i<nx; ) {
+	/* get the FS value from first fsbits */
+	nbits -= fsbits;
+	while (nbits < 0) {
+	    b = (b<<8) | (*c++);
+	    nbits += 8;
+	}
+	fs = (b >> nbits) - 1;
+
+	b &= (1<<nbits)-1;
+	/* loop over the next block */
+	imax = i + nblock;
+	if (imax > nx) imax = nx;
+	if (fs<0) {
+	    /* low-entropy case, all zero differences */
+	    for ( ; i<imax; i++) array[i] = lastpix;
+	} else if (fs==fsmax) {
+	    /* high-entropy case, directly coded pixel values */
+	    for ( ; i<imax; i++) {
+		k = bbits - nbits;
+		diff = b<<k;
+		for (k -= 8; k >= 0; k -= 8) {
+		    b = *c++;
+		    diff |= b<<k;
+		}
+		if (nbits>0) {
+		    b = *c++;
+		    diff |= b>>(-k);
+		    b &= (1<<nbits)-1;
+		} else {
+		    b = 0;
+		}
+   
+		/*
+		 * undo mapping and differencing
+		 * Note that some of these operations will overflow the
+		 * unsigned int arithmetic -- that's OK, it all works
+		 * out to give the right answers in the output file.
+		 */
+		if ((diff & 1) == 0) {
+		    diff = diff>>1;
+		} else {
+		    diff = ~(diff>>1);
+		}
+		array[i] = diff+lastpix;
+		lastpix = array[i];
+	    }
+	} else {
+	    /* normal case, Rice coding */
+	    for ( ; i<imax; i++) {
+		/* count number of leading zeros */
+		while (b == 0) {
+		    nbits += 8;
+		    b = *c++;
+		}
+		nzero = nbits - nonzero_count[b];
+		nbits -= nzero+1;
+		/* flip the leading one-bit */
+		b ^= 1<<nbits;
+		/* get the FS trailing bits */
+		nbits -= fs;
+		while (nbits < 0) {
+		    b = (b<<8) | (*c++);
+		    nbits += 8;
+		}
+		diff = (nzero<<fs) | (b>>nbits);
+		b &= (1<<nbits)-1;
+
+		/* undo mapping and differencing */
+		if ((diff & 1) == 0) {
+		    diff = diff>>1;
+		} else {
+		    diff = ~(diff>>1);
+		}
+		array[i] = diff+lastpix;
+		lastpix = array[i];
+	    }
+	}
+	if (c > cend) {
+            ffpmsg("decompression error: hit end of compressed byte stream");
+	    return 1;
+	}
+    }
+    if (c < cend) {
+        ffpmsg("decompression warning: unused bytes at end of compressed buffer");
+    }
+    return 0;
+}
diff --git a/src/plugins/cfitsio/scalnull.c b/src/plugins/cfitsio/scalnull.c
new file mode 100644
index 0000000..d2f2924
--- /dev/null
+++ b/src/plugins/cfitsio/scalnull.c
@@ -0,0 +1,229 @@
+/*  This file, scalnull.c, contains the FITSIO routines used to define     */
+/*  the starting heap address, the value scaling and the null values.      */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+
+#include <string.h>
+#include "fitsio2.h"
+/*--------------------------------------------------------------------------*/
+int ffpthp(fitsfile *fptr,      /* I - FITS file pointer */
+           long theap,          /* I - starting addrss for the heap */
+           int *status)         /* IO - error status     */
+/*
+  Define the starting address for the heap for a binary table.
+  The default address is NAXIS1 * NAXIS2.  It is in units of
+  bytes relative to the beginning of the regular binary table data.
+  This routine also writes the appropriate THEAP keyword to the
+  FITS header.
+*/
+{
+    if (*status > 0 || theap < 1)
+        return(*status);
+
+    /* reset position to the correct HDU if necessary */
+    if (fptr->HDUposition != (fptr->Fptr)->curhdu)
+        ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status);
+
+    (fptr->Fptr)->heapstart = theap;
+
+    ffukyj(fptr, "THEAP", theap, "byte offset to heap area", status);
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpscl(fitsfile *fptr,      /* I - FITS file pointer               */
+           double scale,        /* I - scaling factor: value of BSCALE */
+           double zero,         /* I - zero point: value of BZERO      */
+           int *status)         /* IO - error status                   */
+/*
+  Define the linear scaling factor for the primary array or image extension
+  pixel values. This routine overrides the scaling values given by the
+  BSCALE and BZERO keywords if present.  Note that this routine does not
+  write or modify the BSCALE and BZERO keywords, but instead only modifies
+  the values temporarily in the internal buffer.  Thus, a subsequent call to
+  the ffrdef routine will reset the scaling back to the BSCALE and BZERO
+  keyword values (or 1. and 0. respectively if the keywords are not present).
+*/
+{
+    tcolumn *colptr;
+    int hdutype;
+
+    if (*status > 0)
+        return(*status);
+
+    if (scale == 0)
+        return(*status = ZERO_SCALE);  /* zero scale value is illegal */
+
+    if (ffghdt(fptr, &hdutype, status) > 0)  /* get HDU type */
+        return(*status);
+
+    if (hdutype != IMAGE_HDU)
+        return(*status = NOT_IMAGE);         /* not proper HDU type */
+
+    if (fits_is_compressed_image(fptr, status)) /* compressed images */
+    {
+        (fptr->Fptr)->cn_bscale = scale;
+        (fptr->Fptr)->cn_bzero  = zero;
+        return(*status);
+    }
+
+    /* set pointer to the first 'column' (contains group parameters if any) */
+    colptr = (fptr->Fptr)->tableptr; 
+
+    colptr++;   /* increment to the 2nd 'column' pointer  (the image itself) */
+
+    colptr->tscale = scale;
+    colptr->tzero = zero;
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffpnul(fitsfile *fptr,      /* I - FITS file pointer                */
+           LONGLONG nulvalue,   /* I - null pixel value: value of BLANK */
+           int *status)         /* IO - error status                    */
+/*
+  Define the value used to represent undefined pixels in the primary array or
+  image extension. This only applies to integer image pixel (i.e. BITPIX > 0).
+  This routine overrides the null pixel value given by the BLANK keyword
+  if present.  Note that this routine does not write or modify the BLANK
+  keyword, but instead only modifies the value temporarily in the internal
+  buffer. Thus, a subsequent call to the ffrdef routine will reset the null
+  value back to the BLANK  keyword value (or not defined if the keyword is not
+  present).
+*/
+{
+    tcolumn *colptr;
+    int hdutype;
+
+    if (*status > 0)
+        return(*status);
+
+    if (ffghdt(fptr, &hdutype, status) > 0)  /* get HDU type */
+        return(*status);
+
+    if (hdutype != IMAGE_HDU)
+        return(*status = NOT_IMAGE);         /* not proper HDU type */
+
+    if (fits_is_compressed_image(fptr, status)) /* ignore compressed images */
+        return(*status);
+
+    /* set pointer to the first 'column' (contains group parameters if any) */
+    colptr = (fptr->Fptr)->tableptr; 
+
+    colptr++;   /* increment to the 2nd 'column' pointer  (the image itself) */
+
+    colptr->tnull = nulvalue;
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fftscl(fitsfile *fptr,      /* I - FITS file pointer */
+           int colnum,          /* I - column number to apply scaling to */
+           double scale,        /* I - scaling factor: value of TSCALn   */
+           double zero,         /* I - zero point: value of TZEROn       */
+           int *status)         /* IO - error status     */
+/*
+  Define the linear scaling factor for the TABLE or BINTABLE extension
+  column values. This routine overrides the scaling values given by the
+  TSCALn and TZEROn keywords if present.  Note that this routine does not
+  write or modify the TSCALn and TZEROn keywords, but instead only modifies
+  the values temporarily in the internal buffer.  Thus, a subsequent call to
+  the ffrdef routine will reset the scaling back to the TSCALn and TZEROn
+  keyword values (or 1. and 0. respectively if the keywords are not present).
+*/
+{
+    tcolumn *colptr;
+    int hdutype;
+
+    if (*status > 0)
+        return(*status);
+
+    if (scale == 0)
+        return(*status = ZERO_SCALE);  /* zero scale value is illegal */
+
+    if (ffghdt(fptr, &hdutype, status) > 0)  /* get HDU type */
+        return(*status);
+
+    if (hdutype == IMAGE_HDU)
+        return(*status = NOT_TABLE);         /* not proper HDU type */
+
+    colptr = (fptr->Fptr)->tableptr;   /* set pointer to the first column */
+    colptr += (colnum - 1);     /* increment to the correct column */
+
+    colptr->tscale = scale;
+    colptr->tzero = zero;
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int fftnul(fitsfile *fptr,      /* I - FITS file pointer                  */
+           int colnum,          /* I - column number to apply nulvalue to */
+           LONGLONG nulvalue,   /* I - null pixel value: value of TNULLn  */
+           int *status)         /* IO - error status                      */
+/*
+  Define the value used to represent undefined pixels in the BINTABLE column.
+  This only applies to integer datatype columns (TFORM = B, I, or J).
+  This routine overrides the null pixel value given by the TNULLn keyword
+  if present.  Note that this routine does not write or modify the TNULLn
+  keyword, but instead only modifies the value temporarily in the internal
+  buffer. Thus, a subsequent call to the ffrdef routine will reset the null
+  value back to the TNULLn  keyword value (or not defined if the keyword is not
+  present).
+*/
+{
+    tcolumn *colptr;
+    int hdutype;
+
+    if (*status > 0)
+        return(*status);
+
+    if (ffghdt(fptr, &hdutype, status) > 0)  /* get HDU type */
+        return(*status);
+
+    if (hdutype != BINARY_TBL)
+        return(*status = NOT_BTABLE);        /* not proper HDU type */
+ 
+    colptr = (fptr->Fptr)->tableptr;   /* set pointer to the first column */
+    colptr += (colnum - 1);    /* increment to the correct column */
+
+    colptr->tnull = nulvalue;
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffsnul(fitsfile *fptr,      /* I - FITS file pointer                  */
+           int colnum,          /* I - column number to apply nulvalue to */
+           char *nulstring,     /* I - null pixel value: value of TNULLn  */
+           int *status)         /* IO - error status                      */
+/*
+  Define the string used to represent undefined pixels in the ASCII TABLE
+  column. This routine overrides the null  value given by the TNULLn keyword
+  if present.  Note that this routine does not write or modify the TNULLn
+  keyword, but instead only modifies the value temporarily in the internal
+  buffer. Thus, a subsequent call to the ffrdef routine will reset the null
+  value back to the TNULLn keyword value (or not defined if the keyword is not
+  present).
+*/
+{
+    tcolumn *colptr;
+    int hdutype;
+
+    if (*status > 0)
+        return(*status);
+
+    if (ffghdt(fptr, &hdutype, status) > 0)  /* get HDU type */
+        return(*status);
+
+    if (hdutype != ASCII_TBL)
+        return(*status = NOT_ATABLE);        /* not proper HDU type */
+ 
+    colptr = (fptr->Fptr)->tableptr;   /* set pointer to the first column */
+    colptr += (colnum - 1);    /* increment to the correct column */
+
+    colptr->strnull[0] = '\0';
+    strncat(colptr->strnull, nulstring, 19);  /* limit string to 19 chars */
+
+    return(*status);
+}
diff --git a/src/plugins/cfitsio/swapproc.c b/src/plugins/cfitsio/swapproc.c
new file mode 100644
index 0000000..cc69d6e
--- /dev/null
+++ b/src/plugins/cfitsio/swapproc.c
@@ -0,0 +1,247 @@
+/*  This file, swapproc.c, contains general utility routines that are      */
+/*  used by other FITSIO routines to swap bytes.                           */
+
+/*  The FITSIO software was written by William Pence at the High Energy    */
+/*  Astrophysic Science Archive Research Center (HEASARC) at the NASA      */
+/*  Goddard Space Flight Center.                                           */
+
+/* The fast SSE2 and SSSE3 functions were provided by Julian Taylor, ESO */
+
+#include <string.h>
+#include <stdlib.h>
+#include "fitsio2.h"
+
+/* bswap builtin is available since GCC 4.3 */
+#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3)
+#define HAVE_BSWAP
+#endif
+
+#ifdef __SSSE3__
+#include <tmmintrin.h>
+/* swap 16 bytes according to mask, values must be 16 byte aligned */
+static inline void swap_ssse3(char * values, __m128i mask)
+{
+    __m128i v = _mm_load_si128((__m128i *)values);
+    __m128i s = _mm_shuffle_epi8(v, mask);
+    _mm_store_si128((__m128i*)values, s);
+}
+#endif
+#ifdef __SSE2__
+#include <emmintrin.h>
+/* swap 8 shorts, values must be 16 byte aligned
+ * faster than ssse3 variant for shorts */
+static inline void swap2_sse2(char * values)
+{
+    __m128i r1 = _mm_load_si128((__m128i *)values);
+    __m128i r2 = r1;
+    r1 = _mm_srli_epi16(r1, 8);
+    r2 = _mm_slli_epi16(r2, 8);
+    r1 = _mm_or_si128(r1, r2);
+    _mm_store_si128((__m128i*)values, r1);
+}
+/* the three shuffles required for 4 and 8 byte variants make
+ * SSE2 slower than bswap */
+
+
+/* get number of elements to peel to reach alignment */
+static inline size_t get_peel(void * addr, size_t esize, size_t nvals,
+                              size_t alignment)
+{
+    const size_t offset = (size_t)addr % alignment;
+    size_t peel = offset ? (alignment - offset) / esize : 0;
+    peel = nvals < peel ? nvals : peel;
+    return peel;
+}
+#endif
+
+/*--------------------------------------------------------------------------*/
+static void ffswap2_slow(short *svalues, long nvals)
+{
+    register long ii;
+    unsigned short * usvalues;
+
+    usvalues = (unsigned short *) svalues;
+
+    for (ii = 0; ii < nvals; ii++)
+    {
+        usvalues[ii] = (usvalues[ii]>>8) | (usvalues[ii]<<8);
+    }
+}
+/*--------------------------------------------------------------------------*/
+#if __SSE2__
+void ffswap2(short *svalues,  /* IO - pointer to shorts to be swapped    */
+             long nvals)     /* I  - number of shorts to be swapped     */
+/*
+  swap the bytes in the input short integers: ( 0 1 -> 1 0 )
+*/
+{
+    if ((long)svalues % 2 != 0) { /* should not happen */
+        ffswap2_slow(svalues, nvals);
+        return;
+    }
+
+    long ii;
+    size_t peel = get_peel((void*)&svalues[0], sizeof(svalues[0]), nvals, 16);
+
+    ffswap2_slow(svalues, peel);
+    for (ii = peel; ii < (nvals - peel - (nvals - peel) % 8); ii+=8) {
+        swap2_sse2((char*)&svalues[ii]);
+    }
+    ffswap2_slow(&svalues[ii], nvals - ii);
+}
+#else
+void ffswap2(short *svalues,  /* IO - pointer to shorts to be swapped    */
+             long nvals)     /* I  - number of shorts to be swapped     */
+/*
+  swap the bytes in the input 4-byte integer: ( 0 1 2 3 -> 3 2 1 0 )
+*/
+{
+    ffswap2_slow(svalues, nvals);
+}
+#endif
+/*--------------------------------------------------------------------------*/
+static void ffswap4_slow(INT32BIT *ivalues, long nvals)
+{
+    register long ii;
+
+#if defined(HAVE_BSWAP)
+    for (ii = 0; ii < nvals; ii++)
+    {
+        ivalues[ii] = __builtin_bswap32(ivalues[ii]);
+    }
+#elif defined(_MSC_VER) && (_MSC_VER >= 1400)
+    /* intrinsic byte swapping function in Microsoft Visual C++ 8.0 and later */
+    unsigned int* uivalues = (unsigned int *) ivalues;
+
+    /* intrinsic byte swapping function in Microsoft Visual C++ */
+    for (ii = 0; ii < nvals; ii++)
+    {
+        uivalues[ii] = _byteswap_ulong(uivalues[ii]);
+    }
+#else
+    char *cvalues, tmp;
+
+    for (ii = 0; ii < nvals; ii++)
+    {
+        cvalues = (char *)&ivalues[ii];
+        tmp = cvalues[0];
+        cvalues[0] = cvalues[3];
+        cvalues[3] = tmp;
+        tmp = cvalues[1];
+        cvalues[1] = cvalues[2];
+        cvalues[2] = tmp;
+    }
+#endif
+}
+/*--------------------------------------------------------------------------*/
+#ifdef __SSSE3__
+void ffswap4(INT32BIT *ivalues,  /* IO - pointer to INT*4 to be swapped    */
+                 long nvals)     /* I  - number of floats to be swapped     */
+/*
+  swap the bytes in the input 4-byte integer: ( 0 1 2 3 -> 3 2 1 0 )
+*/
+{
+    if ((long)ivalues % 4 != 0) { /* should not happen */
+        ffswap4_slow(ivalues, nvals);
+        return;
+    }
+
+    long ii;
+    const __m128i cmask4 = _mm_set_epi8(12, 13, 14, 15,
+                                        8, 9, 10, 11,
+                                        4, 5, 6, 7,
+                                        0, 1, 2 ,3);
+    size_t peel = get_peel((void*)&ivalues[0], sizeof(ivalues[0]), nvals, 16);
+    ffswap4_slow(ivalues, peel);
+    for (ii = peel; ii < (nvals - peel - (nvals - peel) % 4); ii+=4) {
+        swap_ssse3((char*)&ivalues[ii], cmask4);
+    }
+    ffswap4_slow(&ivalues[ii], nvals - ii);
+}
+#else
+void ffswap4(INT32BIT *ivalues,  /* IO - pointer to INT*4 to be swapped    */
+                 long nvals)     /* I  - number of floats to be swapped     */
+/*
+  swap the bytes in the input 4-byte integer: ( 0 1 2 3 -> 3 2 1 0 )
+*/
+{
+    ffswap4_slow(ivalues, nvals);
+}
+#endif
+/*--------------------------------------------------------------------------*/
+static void ffswap8_slow(double *dvalues, long nvals)
+{
+    register long ii;
+#ifdef HAVE_BSWAP
+    LONGLONG * llvalues = (LONGLONG*)dvalues;
+
+    for (ii = 0; ii < nvals; ii++) {
+        llvalues[ii] = __builtin_bswap64(llvalues[ii]);
+    }
+#elif defined(_MSC_VER) && (_MSC_VER >= 1400)
+    /* intrinsic byte swapping function in Microsoft Visual C++ 8.0 and later */
+    unsigned __int64 * llvalues = (unsigned __int64 *) dvalues;
+
+    for (ii = 0; ii < nvals; ii++)
+    {
+        llvalues[ii] = _byteswap_uint64(llvalues[ii]);
+    }
+#else
+    register char *cvalues;
+    register char temp;
+
+    cvalues = (char *) dvalues;      /* copy the pointer value */
+
+    for (ii = 0; ii < nvals*8; ii += 8)
+    {
+        temp = cvalues[ii];
+        cvalues[ii] = cvalues[ii+7];
+        cvalues[ii+7] = temp;
+
+        temp = cvalues[ii+1];
+        cvalues[ii+1] = cvalues[ii+6];
+        cvalues[ii+6] = temp;
+
+        temp = cvalues[ii+2];
+        cvalues[ii+2] = cvalues[ii+5];
+        cvalues[ii+5] = temp;
+
+        temp = cvalues[ii+3];
+        cvalues[ii+3] = cvalues[ii+4];
+        cvalues[ii+4] = temp;
+    }
+#endif
+}
+/*--------------------------------------------------------------------------*/
+#ifdef __SSSE3__
+void ffswap8(double *dvalues,  /* IO - pointer to doubles to be swapped     */
+             long nvals)       /* I  - number of doubles to be swapped      */
+/*
+  swap the bytes in the input doubles: ( 01234567  -> 76543210 )
+*/
+{
+    if ((long)dvalues % 8 != 0) { /* should not happen on amd64 */
+        ffswap8_slow(dvalues, nvals);
+        return;
+    }
+
+    long ii;
+    const __m128i cmask8 = _mm_set_epi8(8, 9, 10, 11, 12, 13, 14, 15,
+                                        0, 1, 2 ,3, 4, 5, 6, 7);
+    size_t peel = get_peel((void*)&dvalues[0], sizeof(dvalues[0]), nvals, 16);
+    ffswap8_slow(dvalues, peel);
+    for (ii = peel; ii < (nvals - peel - (nvals - peel) % 2); ii+=2) {
+        swap_ssse3((char*)&dvalues[ii], cmask8);
+    }
+    ffswap8_slow(&dvalues[ii], nvals - ii);
+}
+#else
+void ffswap8(double *dvalues,  /* IO - pointer to doubles to be swapped     */
+             long nvals)       /* I  - number of doubles to be swapped      */
+/*
+  swap the bytes in the input doubles: ( 01234567  -> 76543210 )
+*/
+{
+    ffswap8_slow(dvalues, nvals);
+}
+#endif
diff --git a/src/plugins/cfitsio/trees.c b/src/plugins/cfitsio/trees.c
new file mode 100644
index 0000000..8436126
--- /dev/null
+++ b/src/plugins/cfitsio/trees.c
@@ -0,0 +1,1242 @@
+/* trees.c -- output deflated data using Huffman coding
+ * Copyright (C) 1995-2010 Jean-loup Gailly
+ * detect_data_type() function provided freely by Cosmin Truta, 2006
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/*
+ *  ALGORITHM
+ *
+ *      The "deflation" process uses several Huffman trees. The more
+ *      common source values are represented by shorter bit sequences.
+ *
+ *      Each code tree is stored in a compressed form which is itself
+ * a Huffman encoding of the lengths of all the code strings (in
+ * ascending order by source values).  The actual code strings are
+ * reconstructed from the lengths in the inflate process, as described
+ * in the deflate specification.
+ *
+ *  REFERENCES
+ *
+ *      Deutsch, L.P.,"'Deflate' Compressed Data Format Specification".
+ *      Available in ftp.uu.net:/pub/archiving/zip/doc/deflate-1.1.doc
+ *
+ *      Storer, James A.
+ *          Data Compression:  Methods and Theory, pp. 49-50.
+ *          Computer Science Press, 1988.  ISBN 0-7167-8156-5.
+ *
+ *      Sedgewick, R.
+ *          Algorithms, p290.
+ *          Addison-Wesley, 1983. ISBN 0-201-06672-6.
+ */
+
+/* #define GEN_TREES_H */
+
+#include "deflate.h"
+
+#ifdef DEBUG
+#  include <ctype.h>
+#endif
+
+/* ===========================================================================
+ * Constants
+ */
+
+#define MAX_BL_BITS 7
+/* Bit length codes must not exceed MAX_BL_BITS bits */
+
+#define END_BLOCK 256
+/* end of block literal code */
+
+#define REP_3_6      16
+/* repeat previous bit length 3-6 times (2 bits of repeat count) */
+
+#define REPZ_3_10    17
+/* repeat a zero length 3-10 times  (3 bits of repeat count) */
+
+#define REPZ_11_138  18
+/* repeat a zero length 11-138 times  (7 bits of repeat count) */
+
+local const int extra_lbits[LENGTH_CODES] /* extra bits for each length code */
+   = {0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0};
+
+local const int extra_dbits[D_CODES] /* extra bits for each distance code */
+   = {0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13};
+
+local const int extra_blbits[BL_CODES]/* extra bits for each bit length code */
+   = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7};
+
+local const uch bl_order[BL_CODES]
+   = {16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15};
+/* The lengths of the bit length codes are sent in order of decreasing
+ * probability, to avoid transmitting the lengths for unused bit length codes.
+ */
+
+#define Buf_size (8 * 2*sizeof(char))
+/* Number of bits used within bi_buf. (bi_buf might be implemented on
+ * more than 16 bits on some systems.)
+ */
+
+/* ===========================================================================
+ * Local data. These are initialized only once.
+ */
+
+#define DIST_CODE_LEN  512 /* see definition of array dist_code below */
+
+#if defined(GEN_TREES_H) || !defined(STDC)
+/* non ANSI compilers may not accept trees.h */
+
+local ct_data static_ltree[L_CODES+2];
+/* The static literal tree. Since the bit lengths are imposed, there is no
+ * need for the L_CODES extra codes used during heap construction. However
+ * The codes 286 and 287 are needed to build a canonical tree (see _tr_init
+ * below).
+ */
+
+local ct_data static_dtree[D_CODES];
+/* The static distance tree. (Actually a trivial tree since all codes use
+ * 5 bits.)
+ */
+
+uch _dist_code[DIST_CODE_LEN];
+/* Distance codes. The first 256 values correspond to the distances
+ * 3 .. 258, the last 256 values correspond to the top 8 bits of
+ * the 15 bit distances.
+ */
+
+uch _length_code[MAX_MATCH-MIN_MATCH+1];
+/* length code for each normalized match length (0 == MIN_MATCH) */
+
+local int base_length[LENGTH_CODES];
+/* First normalized length for each code (0 = MIN_MATCH) */
+
+local int base_dist[D_CODES];
+/* First normalized distance for each code (0 = distance of 1) */
+
+#else
+#  include "trees.h"
+#endif /* GEN_TREES_H */
+
+struct static_tree_desc_s {
+    const ct_data *static_tree;  /* static tree or NULL */
+    const intf *extra_bits;      /* extra bits for each code or NULL */
+    int     extra_base;          /* base index for extra_bits */
+    int     elems;               /* max number of elements in the tree */
+    int     max_length;          /* max bit length for the codes */
+};
+
+local static_tree_desc  static_l_desc =
+{static_ltree, extra_lbits, LITERALS+1, L_CODES, MAX_BITS};
+
+local static_tree_desc  static_d_desc =
+{static_dtree, extra_dbits, 0,          D_CODES, MAX_BITS};
+
+local static_tree_desc  static_bl_desc =
+{(const ct_data *)0, extra_blbits, 0,   BL_CODES, MAX_BL_BITS};
+
+/* ===========================================================================
+ * Local (static) routines in this file.
+ */
+
+local void tr_static_init OF((void));
+local void init_block     OF((deflate_state *s));
+local void pqdownheap     OF((deflate_state *s, ct_data *tree, int k));
+local void gen_bitlen     OF((deflate_state *s, tree_desc *desc));
+local void gen_codes      OF((ct_data *tree, int max_code, ushf *bl_count));
+local void build_tree     OF((deflate_state *s, tree_desc *desc));
+local void scan_tree      OF((deflate_state *s, ct_data *tree, int max_code));
+local void send_tree      OF((deflate_state *s, ct_data *tree, int max_code));
+local int  build_bl_tree  OF((deflate_state *s));
+local void send_all_trees OF((deflate_state *s, int lcodes, int dcodes,
+                              int blcodes));
+local void compress_block OF((deflate_state *s, ct_data *ltree,
+                              ct_data *dtree));
+local int  detect_data_type OF((deflate_state *s));
+local unsigned bi_reverse OF((unsigned value, int length));
+local void bi_windup      OF((deflate_state *s));
+local void bi_flush       OF((deflate_state *s));
+local void copy_block     OF((deflate_state *s, charf *buf, unsigned len,
+                              int header));
+
+#ifdef GEN_TREES_H
+local void gen_trees_header OF((void));
+#endif
+
+#ifndef DEBUG
+#  define send_code(s, c, tree) send_bits(s, tree[c].Code, tree[c].Len)
+   /* Send a code of the given tree. c and tree must not have side effects */
+
+#else /* DEBUG */
+#  define send_code(s, c, tree) \
+     { if (z_verbose>2) fprintf(stderr,"\ncd %3d ",(c)); \
+       send_bits(s, tree[c].Code, tree[c].Len); }
+#endif
+
+/* ===========================================================================
+ * Output a short LSB first on the stream.
+ * IN assertion: there is enough room in pendingBuf.
+ */
+#define put_short(s, w) { \
+    put_byte(s, (uch)((w) & 0xff)); \
+    put_byte(s, (uch)((ush)(w) >> 8)); \
+}
+
+/* ===========================================================================
+ * Send a value on a given number of bits.
+ * IN assertion: length <= 16 and value fits in length bits.
+ */
+#ifdef DEBUG
+local void send_bits      OF((deflate_state *s, int value, int length));
+
+local void send_bits(s, value, length)
+    deflate_state *s;
+    int value;  /* value to send */
+    int length; /* number of bits */
+{
+    Tracevv((stderr," l %2d v %4x ", length, value));
+    Assert(length > 0 && length <= 15, "invalid length");
+    s->bits_sent += (ulg)length;
+
+    /* If not enough room in bi_buf, use (valid) bits from bi_buf and
+     * (16 - bi_valid) bits from value, leaving (width - (16-bi_valid))
+     * unused bits in value.
+     */
+    if (s->bi_valid > (int)Buf_size - length) {
+        s->bi_buf |= (ush)value << s->bi_valid;
+        put_short(s, s->bi_buf);
+        s->bi_buf = (ush)value >> (Buf_size - s->bi_valid);
+        s->bi_valid += length - Buf_size;
+    } else {
+        s->bi_buf |= (ush)value << s->bi_valid;
+        s->bi_valid += length;
+    }
+}
+#else /* !DEBUG */
+
+#define send_bits(s, value, length) \
+{ int len = length;\
+  if (s->bi_valid > (int)Buf_size - len) {\
+    int val = value;\
+    s->bi_buf |= (ush)val << s->bi_valid;\
+    put_short(s, s->bi_buf);\
+    s->bi_buf = (ush)val >> (Buf_size - s->bi_valid);\
+    s->bi_valid += len - Buf_size;\
+  } else {\
+    s->bi_buf |= (ush)(value) << s->bi_valid;\
+    s->bi_valid += len;\
+  }\
+}
+#endif /* DEBUG */
+
+
+/* the arguments must not have side effects */
+
+/* ===========================================================================
+ * Initialize the various 'constant' tables.
+ */
+local void tr_static_init()
+{
+#if defined(GEN_TREES_H) || !defined(STDC)
+    static int static_init_done = 0;
+    int n;        /* iterates over tree elements */
+    int bits;     /* bit counter */
+    int length;   /* length value */
+    int code;     /* code value */
+    int dist;     /* distance index */
+    ush bl_count[MAX_BITS+1];
+    /* number of codes at each bit length for an optimal tree */
+
+    if (static_init_done) return;
+
+    /* For some embedded targets, global variables are not initialized: */
+#ifdef NO_INIT_GLOBAL_POINTERS
+    static_l_desc.static_tree = static_ltree;
+    static_l_desc.extra_bits = extra_lbits;
+    static_d_desc.static_tree = static_dtree;
+    static_d_desc.extra_bits = extra_dbits;
+    static_bl_desc.extra_bits = extra_blbits;
+#endif
+
+    /* Initialize the mapping length (0..255) -> length code (0..28) */
+    length = 0;
+    for (code = 0; code < LENGTH_CODES-1; code++) {
+        base_length[code] = length;
+        for (n = 0; n < (1<<extra_lbits[code]); n++) {
+            _length_code[length++] = (uch)code;
+        }
+    }
+    Assert (length == 256, "tr_static_init: length != 256");
+    /* Note that the length 255 (match length 258) can be represented
+     * in two different ways: code 284 + 5 bits or code 285, so we
+     * overwrite length_code[255] to use the best encoding:
+     */
+    _length_code[length-1] = (uch)code;
+
+    /* Initialize the mapping dist (0..32K) -> dist code (0..29) */
+    dist = 0;
+    for (code = 0 ; code < 16; code++) {
+        base_dist[code] = dist;
+        for (n = 0; n < (1<<extra_dbits[code]); n++) {
+            _dist_code[dist++] = (uch)code;
+        }
+    }
+    Assert (dist == 256, "tr_static_init: dist != 256");
+    dist >>= 7; /* from now on, all distances are divided by 128 */
+    for ( ; code < D_CODES; code++) {
+        base_dist[code] = dist << 7;
+        for (n = 0; n < (1<<(extra_dbits[code]-7)); n++) {
+            _dist_code[256 + dist++] = (uch)code;
+        }
+    }
+    Assert (dist == 256, "tr_static_init: 256+dist != 512");
+
+    /* Construct the codes of the static literal tree */
+    for (bits = 0; bits <= MAX_BITS; bits++) bl_count[bits] = 0;
+    n = 0;
+    while (n <= 143) static_ltree[n++].Len = 8, bl_count[8]++;
+    while (n <= 255) static_ltree[n++].Len = 9, bl_count[9]++;
+    while (n <= 279) static_ltree[n++].Len = 7, bl_count[7]++;
+    while (n <= 287) static_ltree[n++].Len = 8, bl_count[8]++;
+    /* Codes 286 and 287 do not exist, but we must include them in the
+     * tree construction to get a canonical Huffman tree (longest code
+     * all ones)
+     */
+    gen_codes((ct_data *)static_ltree, L_CODES+1, bl_count);
+
+    /* The static distance tree is trivial: */
+    for (n = 0; n < D_CODES; n++) {
+        static_dtree[n].Len = 5;
+        static_dtree[n].Code = bi_reverse((unsigned)n, 5);
+    }
+    static_init_done = 1;
+
+#  ifdef GEN_TREES_H
+    gen_trees_header();
+#  endif
+#endif /* defined(GEN_TREES_H) || !defined(STDC) */
+}
+
+/* ===========================================================================
+ * Genererate the file trees.h describing the static trees.
+ */
+#ifdef GEN_TREES_H
+#  ifndef DEBUG
+#    include <stdio.h>
+#  endif
+
+#  define SEPARATOR(i, last, width) \
+      ((i) == (last)? "\n};\n\n" :    \
+       ((i) % (width) == (width)-1 ? ",\n" : ", "))
+
+void gen_trees_header()
+{
+    FILE *header = fopen("trees.h", "w");
+    int i;
+
+    Assert (header != NULL, "Can't open trees.h");
+    fprintf(header,
+            "/* header created automatically with -DGEN_TREES_H */\n\n");
+
+    fprintf(header, "local const ct_data static_ltree[L_CODES+2] = {\n");
+    for (i = 0; i < L_CODES+2; i++) {
+        fprintf(header, "{{%3u},{%3u}}%s", static_ltree[i].Code,
+                static_ltree[i].Len, SEPARATOR(i, L_CODES+1, 5));
+    }
+
+    fprintf(header, "local const ct_data static_dtree[D_CODES] = {\n");
+    for (i = 0; i < D_CODES; i++) {
+        fprintf(header, "{{%2u},{%2u}}%s", static_dtree[i].Code,
+                static_dtree[i].Len, SEPARATOR(i, D_CODES-1, 5));
+    }
+
+    fprintf(header, "const uch ZLIB_INTERNAL _dist_code[DIST_CODE_LEN] = {\n");
+    for (i = 0; i < DIST_CODE_LEN; i++) {
+        fprintf(header, "%2u%s", _dist_code[i],
+                SEPARATOR(i, DIST_CODE_LEN-1, 20));
+    }
+
+    fprintf(header,
+        "const uch ZLIB_INTERNAL _length_code[MAX_MATCH-MIN_MATCH+1]= {\n");
+    for (i = 0; i < MAX_MATCH-MIN_MATCH+1; i++) {
+        fprintf(header, "%2u%s", _length_code[i],
+                SEPARATOR(i, MAX_MATCH-MIN_MATCH, 20));
+    }
+
+    fprintf(header, "local const int base_length[LENGTH_CODES] = {\n");
+    for (i = 0; i < LENGTH_CODES; i++) {
+        fprintf(header, "%1u%s", base_length[i],
+                SEPARATOR(i, LENGTH_CODES-1, 20));
+    }
+
+    fprintf(header, "local const int base_dist[D_CODES] = {\n");
+    for (i = 0; i < D_CODES; i++) {
+        fprintf(header, "%5u%s", base_dist[i],
+                SEPARATOR(i, D_CODES-1, 10));
+    }
+
+    fclose(header);
+}
+#endif /* GEN_TREES_H */
+
+/* ===========================================================================
+ * Initialize the tree data structures for a new zlib stream.
+ */
+void ZLIB_INTERNAL _tr_init(s)
+    deflate_state *s;
+{
+    tr_static_init();
+
+    s->l_desc.dyn_tree = s->dyn_ltree;
+    s->l_desc.stat_desc = &static_l_desc;
+
+    s->d_desc.dyn_tree = s->dyn_dtree;
+    s->d_desc.stat_desc = &static_d_desc;
+
+    s->bl_desc.dyn_tree = s->bl_tree;
+    s->bl_desc.stat_desc = &static_bl_desc;
+
+    s->bi_buf = 0;
+    s->bi_valid = 0;
+    s->last_eob_len = 8; /* enough lookahead for inflate */
+#ifdef DEBUG
+    s->compressed_len = 0L;
+    s->bits_sent = 0L;
+#endif
+
+    /* Initialize the first block of the first file: */
+    init_block(s);
+}
+
+/* ===========================================================================
+ * Initialize a new block.
+ */
+local void init_block(s)
+    deflate_state *s;
+{
+    int n; /* iterates over tree elements */
+
+    /* Initialize the trees. */
+    for (n = 0; n < L_CODES;  n++) s->dyn_ltree[n].Freq = 0;
+    for (n = 0; n < D_CODES;  n++) s->dyn_dtree[n].Freq = 0;
+    for (n = 0; n < BL_CODES; n++) s->bl_tree[n].Freq = 0;
+
+    s->dyn_ltree[END_BLOCK].Freq = 1;
+    s->opt_len = s->static_len = 0L;
+    s->last_lit = s->matches = 0;
+}
+
+#define SMALLEST 1
+/* Index within the heap array of least frequent node in the Huffman tree */
+
+
+/* ===========================================================================
+ * Remove the smallest element from the heap and recreate the heap with
+ * one less element. Updates heap and heap_len.
+ */
+#define pqremove(s, tree, top) \
+{\
+    top = s->heap[SMALLEST]; \
+    s->heap[SMALLEST] = s->heap[s->heap_len--]; \
+    pqdownheap(s, tree, SMALLEST); \
+}
+
+/* ===========================================================================
+ * Compares to subtrees, using the tree depth as tie breaker when
+ * the subtrees have equal frequency. This minimizes the worst case length.
+ */
+#define smaller(tree, n, m, depth) \
+   (tree[n].Freq < tree[m].Freq || \
+   (tree[n].Freq == tree[m].Freq && depth[n] <= depth[m]))
+
+/* ===========================================================================
+ * Restore the heap property by moving down the tree starting at node k,
+ * exchanging a node with the smallest of its two sons if necessary, stopping
+ * when the heap property is re-established (each father smaller than its
+ * two sons).
+ */
+local void pqdownheap(s, tree, k)
+    deflate_state *s;
+    ct_data *tree;  /* the tree to restore */
+    int k;               /* node to move down */
+{
+    int v = s->heap[k];
+    int j = k << 1;  /* left son of k */
+    while (j <= s->heap_len) {
+        /* Set j to the smallest of the two sons: */
+        if (j < s->heap_len &&
+            smaller(tree, s->heap[j+1], s->heap[j], s->depth)) {
+            j++;
+        }
+        /* Exit if v is smaller than both sons */
+        if (smaller(tree, v, s->heap[j], s->depth)) break;
+
+        /* Exchange v with the smallest son */
+        s->heap[k] = s->heap[j];  k = j;
+
+        /* And continue down the tree, setting j to the left son of k */
+        j <<= 1;
+    }
+    s->heap[k] = v;
+}
+
+/* ===========================================================================
+ * Compute the optimal bit lengths for a tree and update the total bit length
+ * for the current block.
+ * IN assertion: the fields freq and dad are set, heap[heap_max] and
+ *    above are the tree nodes sorted by increasing frequency.
+ * OUT assertions: the field len is set to the optimal bit length, the
+ *     array bl_count contains the frequencies for each bit length.
+ *     The length opt_len is updated; static_len is also updated if stree is
+ *     not null.
+ */
+local void gen_bitlen(s, desc)
+    deflate_state *s;
+    tree_desc *desc;    /* the tree descriptor */
+{
+    ct_data *tree        = desc->dyn_tree;
+    int max_code         = desc->max_code;
+    const ct_data *stree = desc->stat_desc->static_tree;
+    const intf *extra    = desc->stat_desc->extra_bits;
+    int base             = desc->stat_desc->extra_base;
+    int max_length       = desc->stat_desc->max_length;
+    int h;              /* heap index */
+    int n, m;           /* iterate over the tree elements */
+    int bits;           /* bit length */
+    int xbits;          /* extra bits */
+    ush f;              /* frequency */
+    int overflow = 0;   /* number of elements with bit length too large */
+
+    for (bits = 0; bits <= MAX_BITS; bits++) s->bl_count[bits] = 0;
+
+    /* In a first pass, compute the optimal bit lengths (which may
+     * overflow in the case of the bit length tree).
+     */
+    tree[s->heap[s->heap_max]].Len = 0; /* root of the heap */
+
+    for (h = s->heap_max+1; h < HEAP_SIZE; h++) {
+        n = s->heap[h];
+        bits = tree[tree[n].Dad].Len + 1;
+        if (bits > max_length) bits = max_length, overflow++;
+        tree[n].Len = (ush)bits;
+        /* We overwrite tree[n].Dad which is no longer needed */
+
+        if (n > max_code) continue; /* not a leaf node */
+
+        s->bl_count[bits]++;
+        xbits = 0;
+        if (n >= base) xbits = extra[n-base];
+        f = tree[n].Freq;
+        s->opt_len += (ulg)f * (bits + xbits);
+        if (stree) s->static_len += (ulg)f * (stree[n].Len + xbits);
+    }
+    if (overflow == 0) return;
+
+    Trace((stderr,"\nbit length overflow\n"));
+    /* This happens for example on obj2 and pic of the Calgary corpus */
+
+    /* Find the first bit length which could increase: */
+    do {
+        bits = max_length-1;
+        while (s->bl_count[bits] == 0) bits--;
+        s->bl_count[bits]--;      /* move one leaf down the tree */
+        s->bl_count[bits+1] += 2; /* move one overflow item as its brother */
+        s->bl_count[max_length]--;
+        /* The brother of the overflow item also moves one step up,
+         * but this does not affect bl_count[max_length]
+         */
+        overflow -= 2;
+    } while (overflow > 0);
+
+    /* Now recompute all bit lengths, scanning in increasing frequency.
+     * h is still equal to HEAP_SIZE. (It is simpler to reconstruct all
+     * lengths instead of fixing only the wrong ones. This idea is taken
+     * from 'ar' written by Haruhiko Okumura.)
+     */
+    for (bits = max_length; bits != 0; bits--) {
+        n = s->bl_count[bits];
+        while (n != 0) {
+            m = s->heap[--h];
+            if (m > max_code) continue;
+            if ((unsigned) tree[m].Len != (unsigned) bits) {
+                Trace((stderr,"code %d bits %d->%d\n", m, tree[m].Len, bits));
+                s->opt_len += ((long)bits - (long)tree[m].Len)
+                              *(long)tree[m].Freq;
+                tree[m].Len = (ush)bits;
+            }
+            n--;
+        }
+    }
+}
+
+/* ===========================================================================
+ * Generate the codes for a given tree and bit counts (which need not be
+ * optimal).
+ * IN assertion: the array bl_count contains the bit length statistics for
+ * the given tree and the field len is set for all tree elements.
+ * OUT assertion: the field code is set for all tree elements of non
+ *     zero code length.
+ */
+local void gen_codes (tree, max_code, bl_count)
+    ct_data *tree;             /* the tree to decorate */
+    int max_code;              /* largest code with non zero frequency */
+    ushf *bl_count;            /* number of codes at each bit length */
+{
+    ush next_code[MAX_BITS+1]; /* next code value for each bit length */
+    ush code = 0;              /* running code value */
+    int bits;                  /* bit index */
+    int n;                     /* code index */
+
+    /* The distribution counts are first used to generate the code values
+     * without bit reversal.
+     */
+    for (bits = 1; bits <= MAX_BITS; bits++) {
+        next_code[bits] = code = (code + bl_count[bits-1]) << 1;
+    }
+    /* Check that the bit counts in bl_count are consistent. The last code
+     * must be all ones.
+     */
+    Assert (code + bl_count[MAX_BITS]-1 == (1<<MAX_BITS)-1,
+            "inconsistent bit counts");
+    Tracev((stderr,"\ngen_codes: max_code %d ", max_code));
+
+    for (n = 0;  n <= max_code; n++) {
+        int len = tree[n].Len;
+        if (len == 0) continue;
+        /* Now reverse the bits */
+        tree[n].Code = bi_reverse(next_code[len]++, len);
+
+        Tracecv(tree != static_ltree, (stderr,"\nn %3d %c l %2d c %4x (%x) ",
+             n, (isgraph(n) ? n : ' '), len, tree[n].Code, next_code[len]-1));
+    }
+}
+
+/* ===========================================================================
+ * Construct one Huffman tree and assigns the code bit strings and lengths.
+ * Update the total bit length for the current block.
+ * IN assertion: the field freq is set for all tree elements.
+ * OUT assertions: the fields len and code are set to the optimal bit length
+ *     and corresponding code. The length opt_len is updated; static_len is
+ *     also updated if stree is not null. The field max_code is set.
+ */
+local void build_tree(s, desc)
+    deflate_state *s;
+    tree_desc *desc; /* the tree descriptor */
+{
+    ct_data *tree         = desc->dyn_tree;
+    const ct_data *stree  = desc->stat_desc->static_tree;
+    int elems             = desc->stat_desc->elems;
+    int n, m;          /* iterate over heap elements */
+    int max_code = -1; /* largest code with non zero frequency */
+    int node;          /* new node being created */
+
+    /* Construct the initial heap, with least frequent element in
+     * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n+1].
+     * heap[0] is not used.
+     */
+    s->heap_len = 0, s->heap_max = HEAP_SIZE;
+
+    for (n = 0; n < elems; n++) {
+        if (tree[n].Freq != 0) {
+            s->heap[++(s->heap_len)] = max_code = n;
+            s->depth[n] = 0;
+        } else {
+            tree[n].Len = 0;
+        }
+    }
+
+    /* The pkzip format requires that at least one distance code exists,
+     * and that at least one bit should be sent even if there is only one
+     * possible code. So to avoid special checks later on we force at least
+     * two codes of non zero frequency.
+     */
+    while (s->heap_len < 2) {
+        node = s->heap[++(s->heap_len)] = (max_code < 2 ? ++max_code : 0);
+        tree[node].Freq = 1;
+        s->depth[node] = 0;
+        s->opt_len--; if (stree) s->static_len -= stree[node].Len;
+        /* node is 0 or 1 so it does not have extra bits */
+    }
+    desc->max_code = max_code;
+
+    /* The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree,
+     * establish sub-heaps of increasing lengths:
+     */
+    for (n = s->heap_len/2; n >= 1; n--) pqdownheap(s, tree, n);
+
+    /* Construct the Huffman tree by repeatedly combining the least two
+     * frequent nodes.
+     */
+    node = elems;              /* next internal node of the tree */
+    do {
+        pqremove(s, tree, n);  /* n = node of least frequency */
+        m = s->heap[SMALLEST]; /* m = node of next least frequency */
+
+        s->heap[--(s->heap_max)] = n; /* keep the nodes sorted by frequency */
+        s->heap[--(s->heap_max)] = m;
+
+        /* Create a new node father of n and m */
+        tree[node].Freq = tree[n].Freq + tree[m].Freq;
+        s->depth[node] = (uch)((s->depth[n] >= s->depth[m] ?
+                                s->depth[n] : s->depth[m]) + 1);
+        tree[n].Dad = tree[m].Dad = (ush)node;
+#ifdef DUMP_BL_TREE
+        if (tree == s->bl_tree) {
+            fprintf(stderr,"\nnode %d(%d), sons %d(%d) %d(%d)",
+                    node, tree[node].Freq, n, tree[n].Freq, m, tree[m].Freq);
+        }
+#endif
+        /* and insert the new node in the heap */
+        s->heap[SMALLEST] = node++;
+        pqdownheap(s, tree, SMALLEST);
+
+    } while (s->heap_len >= 2);
+
+    s->heap[--(s->heap_max)] = s->heap[SMALLEST];
+
+    /* At this point, the fields freq and dad are set. We can now
+     * generate the bit lengths.
+     */
+    gen_bitlen(s, (tree_desc *)desc);
+
+    /* The field len is now set, we can generate the bit codes */
+    gen_codes ((ct_data *)tree, max_code, s->bl_count);
+}
+
+/* ===========================================================================
+ * Scan a literal or distance tree to determine the frequencies of the codes
+ * in the bit length tree.
+ */
+local void scan_tree (s, tree, max_code)
+    deflate_state *s;
+    ct_data *tree;   /* the tree to be scanned */
+    int max_code;    /* and its largest code of non zero frequency */
+{
+    int n;                     /* iterates over all tree elements */
+    int prevlen = -1;          /* last emitted length */
+    int curlen;                /* length of current code */
+    int nextlen = tree[0].Len; /* length of next code */
+    int count = 0;             /* repeat count of the current code */
+    int max_count = 7;         /* max repeat count */
+    int min_count = 4;         /* min repeat count */
+
+    if (nextlen == 0) max_count = 138, min_count = 3;
+    tree[max_code+1].Len = (ush)0xffff; /* guard */
+
+    for (n = 0; n <= max_code; n++) {
+        curlen = nextlen; nextlen = tree[n+1].Len;
+        if (++count < max_count && curlen == nextlen) {
+            continue;
+        } else if (count < min_count) {
+            s->bl_tree[curlen].Freq += count;
+        } else if (curlen != 0) {
+            if (curlen != prevlen) s->bl_tree[curlen].Freq++;
+            s->bl_tree[REP_3_6].Freq++;
+        } else if (count <= 10) {
+            s->bl_tree[REPZ_3_10].Freq++;
+        } else {
+            s->bl_tree[REPZ_11_138].Freq++;
+        }
+        count = 0; prevlen = curlen;
+        if (nextlen == 0) {
+            max_count = 138, min_count = 3;
+        } else if (curlen == nextlen) {
+            max_count = 6, min_count = 3;
+        } else {
+            max_count = 7, min_count = 4;
+        }
+    }
+}
+
+/* ===========================================================================
+ * Send a literal or distance tree in compressed form, using the codes in
+ * bl_tree.
+ */
+local void send_tree (s, tree, max_code)
+    deflate_state *s;
+    ct_data *tree; /* the tree to be scanned */
+    int max_code;       /* and its largest code of non zero frequency */
+{
+    int n;                     /* iterates over all tree elements */
+    int prevlen = -1;          /* last emitted length */
+    int curlen;                /* length of current code */
+    int nextlen = tree[0].Len; /* length of next code */
+    int count = 0;             /* repeat count of the current code */
+    int max_count = 7;         /* max repeat count */
+    int min_count = 4;         /* min repeat count */
+
+    /* tree[max_code+1].Len = -1; */  /* guard already set */
+    if (nextlen == 0) max_count = 138, min_count = 3;
+
+    for (n = 0; n <= max_code; n++) {
+        curlen = nextlen; nextlen = tree[n+1].Len;
+        if (++count < max_count && curlen == nextlen) {
+            continue;
+        } else if (count < min_count) {
+            do { send_code(s, curlen, s->bl_tree); } while (--count != 0);
+
+        } else if (curlen != 0) {
+            if (curlen != prevlen) {
+                send_code(s, curlen, s->bl_tree); count--;
+            }
+            Assert(count >= 3 && count <= 6, " 3_6?");
+            send_code(s, REP_3_6, s->bl_tree); send_bits(s, count-3, 2);
+
+        } else if (count <= 10) {
+            send_code(s, REPZ_3_10, s->bl_tree); send_bits(s, count-3, 3);
+
+        } else {
+            send_code(s, REPZ_11_138, s->bl_tree); send_bits(s, count-11, 7);
+        }
+        count = 0; prevlen = curlen;
+        if (nextlen == 0) {
+            max_count = 138, min_count = 3;
+        } else if (curlen == nextlen) {
+            max_count = 6, min_count = 3;
+        } else {
+            max_count = 7, min_count = 4;
+        }
+    }
+}
+
+/* ===========================================================================
+ * Construct the Huffman tree for the bit lengths and return the index in
+ * bl_order of the last bit length code to send.
+ */
+local int build_bl_tree(s)
+    deflate_state *s;
+{
+    int max_blindex;  /* index of last bit length code of non zero freq */
+
+    /* Determine the bit length frequencies for literal and distance trees */
+    scan_tree(s, (ct_data *)s->dyn_ltree, s->l_desc.max_code);
+    scan_tree(s, (ct_data *)s->dyn_dtree, s->d_desc.max_code);
+
+    /* Build the bit length tree: */
+    build_tree(s, (tree_desc *)(&(s->bl_desc)));
+    /* opt_len now includes the length of the tree representations, except
+     * the lengths of the bit lengths codes and the 5+5+4 bits for the counts.
+     */
+
+    /* Determine the number of bit length codes to send. The pkzip format
+     * requires that at least 4 bit length codes be sent. (appnote.txt says
+     * 3 but the actual value used is 4.)
+     */
+    for (max_blindex = BL_CODES-1; max_blindex >= 3; max_blindex--) {
+        if (s->bl_tree[bl_order[max_blindex]].Len != 0) break;
+    }
+    /* Update opt_len to include the bit length tree and counts */
+    s->opt_len += 3*(max_blindex+1) + 5+5+4;
+    Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld",
+            s->opt_len, s->static_len));
+
+    return max_blindex;
+}
+
+/* ===========================================================================
+ * Send the header for a block using dynamic Huffman trees: the counts, the
+ * lengths of the bit length codes, the literal tree and the distance tree.
+ * IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4.
+ */
+local void send_all_trees(s, lcodes, dcodes, blcodes)
+    deflate_state *s;
+    int lcodes, dcodes, blcodes; /* number of codes for each tree */
+{
+    int rank;                    /* index in bl_order */
+
+    Assert (lcodes >= 257 && dcodes >= 1 && blcodes >= 4, "not enough codes");
+    Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES,
+            "too many codes");
+    Tracev((stderr, "\nbl counts: "));
+    send_bits(s, lcodes-257, 5); /* not +255 as stated in appnote.txt */
+    send_bits(s, dcodes-1,   5);
+    send_bits(s, blcodes-4,  4); /* not -3 as stated in appnote.txt */
+    for (rank = 0; rank < blcodes; rank++) {
+        Tracev((stderr, "\nbl code %2d ", bl_order[rank]));
+        send_bits(s, s->bl_tree[bl_order[rank]].Len, 3);
+    }
+    Tracev((stderr, "\nbl tree: sent %ld", s->bits_sent));
+
+    send_tree(s, (ct_data *)s->dyn_ltree, lcodes-1); /* literal tree */
+    Tracev((stderr, "\nlit tree: sent %ld", s->bits_sent));
+
+    send_tree(s, (ct_data *)s->dyn_dtree, dcodes-1); /* distance tree */
+    Tracev((stderr, "\ndist tree: sent %ld", s->bits_sent));
+}
+
+/* ===========================================================================
+ * Send a stored block
+ */
+void ZLIB_INTERNAL _tr_stored_block(s, buf, stored_len, last)
+    deflate_state *s;
+    charf *buf;       /* input block */
+    ulg stored_len;   /* length of input block */
+    int last;         /* one if this is the last block for a file */
+{
+    send_bits(s, (STORED_BLOCK<<1)+last, 3);    /* send block type */
+#ifdef DEBUG
+    s->compressed_len = (s->compressed_len + 3 + 7) & (ulg)~7L;
+    s->compressed_len += (stored_len + 4) << 3;
+#endif
+    copy_block(s, buf, (unsigned)stored_len, 1); /* with header */
+}
+
+/* ===========================================================================
+ * Send one empty static block to give enough lookahead for inflate.
+ * This takes 10 bits, of which 7 may remain in the bit buffer.
+ * The current inflate code requires 9 bits of lookahead. If the
+ * last two codes for the previous block (real code plus EOB) were coded
+ * on 5 bits or less, inflate may have only 5+3 bits of lookahead to decode
+ * the last real code. In this case we send two empty static blocks instead
+ * of one. (There are no problems if the previous block is stored or fixed.)
+ * To simplify the code, we assume the worst case of last real code encoded
+ * on one bit only.
+ */
+void ZLIB_INTERNAL _tr_align(s)
+    deflate_state *s;
+{
+    send_bits(s, STATIC_TREES<<1, 3);
+    send_code(s, END_BLOCK, static_ltree);
+#ifdef DEBUG
+    s->compressed_len += 10L; /* 3 for block type, 7 for EOB */
+#endif
+    bi_flush(s);
+    /* Of the 10 bits for the empty block, we have already sent
+     * (10 - bi_valid) bits. The lookahead for the last real code (before
+     * the EOB of the previous block) was thus at least one plus the length
+     * of the EOB plus what we have just sent of the empty static block.
+     */
+    if (1 + s->last_eob_len + 10 - s->bi_valid < 9) {
+        send_bits(s, STATIC_TREES<<1, 3);
+        send_code(s, END_BLOCK, static_ltree);
+#ifdef DEBUG
+        s->compressed_len += 10L;
+#endif
+        bi_flush(s);
+    }
+    s->last_eob_len = 7;
+}
+
+/* ===========================================================================
+ * Determine the best encoding for the current block: dynamic trees, static
+ * trees or store, and output the encoded block to the zip file.
+ */
+void ZLIB_INTERNAL _tr_flush_block(s, buf, stored_len, last)
+    deflate_state *s;
+    charf *buf;       /* input block, or NULL if too old */
+    ulg stored_len;   /* length of input block */
+    int last;         /* one if this is the last block for a file */
+{
+    ulg opt_lenb, static_lenb; /* opt_len and static_len in bytes */
+    int max_blindex = 0;  /* index of last bit length code of non zero freq */
+
+    /* Build the Huffman trees unless a stored block is forced */
+    if (s->level > 0) {
+
+        /* Check if the file is binary or text */
+        if (s->strm->data_type == Z_UNKNOWN)
+            s->strm->data_type = detect_data_type(s);
+
+        /* Construct the literal and distance trees */
+        build_tree(s, (tree_desc *)(&(s->l_desc)));
+        Tracev((stderr, "\nlit data: dyn %ld, stat %ld", s->opt_len,
+                s->static_len));
+
+        build_tree(s, (tree_desc *)(&(s->d_desc)));
+        Tracev((stderr, "\ndist data: dyn %ld, stat %ld", s->opt_len,
+                s->static_len));
+        /* At this point, opt_len and static_len are the total bit lengths of
+         * the compressed block data, excluding the tree representations.
+         */
+
+        /* Build the bit length tree for the above two trees, and get the index
+         * in bl_order of the last bit length code to send.
+         */
+        max_blindex = build_bl_tree(s);
+
+        /* Determine the best encoding. Compute the block lengths in bytes. */
+        opt_lenb = (s->opt_len+3+7)>>3;
+        static_lenb = (s->static_len+3+7)>>3;
+
+        Tracev((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u ",
+                opt_lenb, s->opt_len, static_lenb, s->static_len, stored_len,
+                s->last_lit));
+
+        if (static_lenb <= opt_lenb) opt_lenb = static_lenb;
+
+    } else {
+        Assert(buf != (char*)0, "lost buf");
+        opt_lenb = static_lenb = stored_len + 5; /* force a stored block */
+    }
+
+#ifdef FORCE_STORED
+    if (buf != (char*)0) { /* force stored block */
+#else
+    if (stored_len+4 <= opt_lenb && buf != (char*)0) {
+                       /* 4: two words for the lengths */
+#endif
+        /* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE.
+         * Otherwise we can't have processed more than WSIZE input bytes since
+         * the last block flush, because compression would have been
+         * successful. If LIT_BUFSIZE <= WSIZE, it is never too late to
+         * transform a block into a stored block.
+         */
+        _tr_stored_block(s, buf, stored_len, last);
+
+#ifdef FORCE_STATIC
+    } else if (static_lenb >= 0) { /* force static trees */
+#else
+    } else if (s->strategy == Z_FIXED || static_lenb == opt_lenb) {
+#endif
+        send_bits(s, (STATIC_TREES<<1)+last, 3);
+        compress_block(s, (ct_data *)static_ltree, (ct_data *)static_dtree);
+#ifdef DEBUG
+        s->compressed_len += 3 + s->static_len;
+#endif
+    } else {
+        send_bits(s, (DYN_TREES<<1)+last, 3);
+        send_all_trees(s, s->l_desc.max_code+1, s->d_desc.max_code+1,
+                       max_blindex+1);
+        compress_block(s, (ct_data *)s->dyn_ltree, (ct_data *)s->dyn_dtree);
+#ifdef DEBUG
+        s->compressed_len += 3 + s->opt_len;
+#endif
+    }
+    Assert (s->compressed_len == s->bits_sent, "bad compressed size");
+    /* The above check is made mod 2^32, for files larger than 512 MB
+     * and uLong implemented on 32 bits.
+     */
+    init_block(s);
+
+    if (last) {
+        bi_windup(s);
+#ifdef DEBUG
+        s->compressed_len += 7;  /* align on byte boundary */
+#endif
+    }
+    Tracev((stderr,"\ncomprlen %lu(%lu) ", s->compressed_len>>3,
+           s->compressed_len-7*last));
+}
+
+/* ===========================================================================
+ * Save the match info and tally the frequency counts. Return true if
+ * the current block must be flushed.
+ */
+int ZLIB_INTERNAL _tr_tally (s, dist, lc)
+    deflate_state *s;
+    unsigned dist;  /* distance of matched string */
+    unsigned lc;    /* match length-MIN_MATCH or unmatched char (if dist==0) */
+{
+    s->d_buf[s->last_lit] = (ush)dist;
+    s->l_buf[s->last_lit++] = (uch)lc;
+    if (dist == 0) {
+        /* lc is the unmatched char */
+        s->dyn_ltree[lc].Freq++;
+    } else {
+        s->matches++;
+        /* Here, lc is the match length - MIN_MATCH */
+        dist--;             /* dist = match distance - 1 */
+        Assert((ush)dist < (ush)MAX_DIST(s) &&
+               (ush)lc <= (ush)(MAX_MATCH-MIN_MATCH) &&
+               (ush)d_code(dist) < (ush)D_CODES,  "_tr_tally: bad match");
+
+        s->dyn_ltree[_length_code[lc]+LITERALS+1].Freq++;
+        s->dyn_dtree[d_code(dist)].Freq++;
+    }
+
+#ifdef TRUNCATE_BLOCK
+    /* Try to guess if it is profitable to stop the current block here */
+    if ((s->last_lit & 0x1fff) == 0 && s->level > 2) {
+        /* Compute an upper bound for the compressed length */
+        ulg out_length = (ulg)s->last_lit*8L;
+        ulg in_length = (ulg)((long)s->strstart - s->block_start);
+        int dcode;
+        for (dcode = 0; dcode < D_CODES; dcode++) {
+            out_length += (ulg)s->dyn_dtree[dcode].Freq *
+                (5L+extra_dbits[dcode]);
+        }
+        out_length >>= 3;
+        Tracev((stderr,"\nlast_lit %u, in %ld, out ~%ld(%ld%%) ",
+               s->last_lit, in_length, out_length,
+               100L - out_length*100L/in_length));
+        if (s->matches < s->last_lit/2 && out_length < in_length/2) return 1;
+    }
+#endif
+    return (s->last_lit == s->lit_bufsize-1);
+    /* We avoid equality with lit_bufsize because of wraparound at 64K
+     * on 16 bit machines and because stored blocks are restricted to
+     * 64K-1 bytes.
+     */
+}
+
+/* ===========================================================================
+ * Send the block data compressed using the given Huffman trees
+ */
+local void compress_block(s, ltree, dtree)
+    deflate_state *s;
+    ct_data *ltree; /* literal tree */
+    ct_data *dtree; /* distance tree */
+{
+    unsigned dist;      /* distance of matched string */
+    int lc;             /* match length or unmatched char (if dist == 0) */
+    unsigned lx = 0;    /* running index in l_buf */
+    unsigned code;      /* the code to send */
+    int extra;          /* number of extra bits to send */
+
+    if (s->last_lit != 0) do {
+        dist = s->d_buf[lx];
+        lc = s->l_buf[lx++];
+        if (dist == 0) {
+            send_code(s, lc, ltree); /* send a literal byte */
+            Tracecv(isgraph(lc), (stderr," '%c' ", lc));
+        } else {
+            /* Here, lc is the match length - MIN_MATCH */
+            code = _length_code[lc];
+            send_code(s, code+LITERALS+1, ltree); /* send the length code */
+            extra = extra_lbits[code];
+            if (extra != 0) {
+                lc -= base_length[code];
+                send_bits(s, lc, extra);       /* send the extra length bits */
+            }
+            dist--; /* dist is now the match distance - 1 */
+            code = d_code(dist);
+            Assert (code < D_CODES, "bad d_code");
+
+            send_code(s, code, dtree);       /* send the distance code */
+            extra = extra_dbits[code];
+            if (extra != 0) {
+                dist -= base_dist[code];
+                send_bits(s, dist, extra);   /* send the extra distance bits */
+            }
+        } /* literal or match pair ? */
+
+        /* Check that the overlay between pending_buf and d_buf+l_buf is ok: */
+        Assert((uInt)(s->pending) < s->lit_bufsize + 2*lx,
+               "pendingBuf overflow");
+
+    } while (lx < s->last_lit);
+
+    send_code(s, END_BLOCK, ltree);
+    s->last_eob_len = ltree[END_BLOCK].Len;
+}
+
+/* ===========================================================================
+ * Check if the data type is TEXT or BINARY, using the following algorithm:
+ * - TEXT if the two conditions below are satisfied:
+ *    a) There are no non-portable control characters belonging to the
+ *       "black list" (0..6, 14..25, 28..31).
+ *    b) There is at least one printable character belonging to the
+ *       "white list" (9 {TAB}, 10 {LF}, 13 {CR}, 32..255).
+ * - BINARY otherwise.
+ * - The following partially-portable control characters form a
+ *   "gray list" that is ignored in this detection algorithm:
+ *   (7 {BEL}, 8 {BS}, 11 {VT}, 12 {FF}, 26 {SUB}, 27 {ESC}).
+ * IN assertion: the fields Freq of dyn_ltree are set.
+ */
+local int detect_data_type(s)
+    deflate_state *s;
+{
+    /* black_mask is the bit mask of black-listed bytes
+     * set bits 0..6, 14..25, and 28..31
+     * 0xf3ffc07f = binary 11110011111111111100000001111111
+     */
+    unsigned long black_mask = 0xf3ffc07fUL;
+    int n;
+
+    /* Check for non-textual ("black-listed") bytes. */
+    for (n = 0; n <= 31; n++, black_mask >>= 1)
+        if ((black_mask & 1) && (s->dyn_ltree[n].Freq != 0))
+            return Z_BINARY;
+
+    /* Check for textual ("white-listed") bytes. */
+    if (s->dyn_ltree[9].Freq != 0 || s->dyn_ltree[10].Freq != 0
+            || s->dyn_ltree[13].Freq != 0)
+        return Z_TEXT;
+    for (n = 32; n < LITERALS; n++)
+        if (s->dyn_ltree[n].Freq != 0)
+            return Z_TEXT;
+
+    /* There are no "black-listed" or "white-listed" bytes:
+     * this stream either is empty or has tolerated ("gray-listed") bytes only.
+     */
+    return Z_BINARY;
+}
+
+/* ===========================================================================
+ * Reverse the first len bits of a code, using straightforward code (a faster
+ * method would use a table)
+ * IN assertion: 1 <= len <= 15
+ */
+local unsigned bi_reverse(code, len)
+    unsigned code; /* the value to invert */
+    int len;       /* its bit length */
+{
+    register unsigned res = 0;
+    do {
+        res |= code & 1;
+        code >>= 1, res <<= 1;
+    } while (--len > 0);
+    return res >> 1;
+}
+
+/* ===========================================================================
+ * Flush the bit buffer, keeping at most 7 bits in it.
+ */
+local void bi_flush(s)
+    deflate_state *s;
+{
+    if (s->bi_valid == 16) {
+        put_short(s, s->bi_buf);
+        s->bi_buf = 0;
+        s->bi_valid = 0;
+    } else if (s->bi_valid >= 8) {
+        put_byte(s, (Byte)s->bi_buf);
+        s->bi_buf >>= 8;
+        s->bi_valid -= 8;
+    }
+}
+
+/* ===========================================================================
+ * Flush the bit buffer and align the output on a byte boundary
+ */
+local void bi_windup(s)
+    deflate_state *s;
+{
+    if (s->bi_valid > 8) {
+        put_short(s, s->bi_buf);
+    } else if (s->bi_valid > 0) {
+        put_byte(s, (Byte)s->bi_buf);
+    }
+    s->bi_buf = 0;
+    s->bi_valid = 0;
+#ifdef DEBUG
+    s->bits_sent = (s->bits_sent+7) & ~7;
+#endif
+}
+
+/* ===========================================================================
+ * Copy a stored block, storing first the length and its
+ * one's complement if requested.
+ */
+local void copy_block(s, buf, len, header)
+    deflate_state *s;
+    charf    *buf;    /* the input data */
+    unsigned len;     /* its length */
+    int      header;  /* true if block header must be written */
+{
+    bi_windup(s);        /* align on byte boundary */
+    s->last_eob_len = 8; /* enough lookahead for inflate */
+
+    if (header) {
+        put_short(s, (ush)len);
+        put_short(s, (ush)~len);
+#ifdef DEBUG
+        s->bits_sent += 2*16;
+#endif
+    }
+#ifdef DEBUG
+    s->bits_sent += (ulg)len<<3;
+#endif
+    while (len--) {
+        put_byte(s, *buf++);
+    }
+}
diff --git a/src/plugins/cfitsio/trees.h b/src/plugins/cfitsio/trees.h
new file mode 100644
index 0000000..d35639d
--- /dev/null
+++ b/src/plugins/cfitsio/trees.h
@@ -0,0 +1,128 @@
+/* header created automatically with -DGEN_TREES_H */
+
+local const ct_data static_ltree[L_CODES+2] = {
+{{ 12},{  8}}, {{140},{  8}}, {{ 76},{  8}}, {{204},{  8}}, {{ 44},{  8}},
+{{172},{  8}}, {{108},{  8}}, {{236},{  8}}, {{ 28},{  8}}, {{156},{  8}},
+{{ 92},{  8}}, {{220},{  8}}, {{ 60},{  8}}, {{188},{  8}}, {{124},{  8}},
+{{252},{  8}}, {{  2},{  8}}, {{130},{  8}}, {{ 66},{  8}}, {{194},{  8}},
+{{ 34},{  8}}, {{162},{  8}}, {{ 98},{  8}}, {{226},{  8}}, {{ 18},{  8}},
+{{146},{  8}}, {{ 82},{  8}}, {{210},{  8}}, {{ 50},{  8}}, {{178},{  8}},
+{{114},{  8}}, {{242},{  8}}, {{ 10},{  8}}, {{138},{  8}}, {{ 74},{  8}},
+{{202},{  8}}, {{ 42},{  8}}, {{170},{  8}}, {{106},{  8}}, {{234},{  8}},
+{{ 26},{  8}}, {{154},{  8}}, {{ 90},{  8}}, {{218},{  8}}, {{ 58},{  8}},
+{{186},{  8}}, {{122},{  8}}, {{250},{  8}}, {{  6},{  8}}, {{134},{  8}},
+{{ 70},{  8}}, {{198},{  8}}, {{ 38},{  8}}, {{166},{  8}}, {{102},{  8}},
+{{230},{  8}}, {{ 22},{  8}}, {{150},{  8}}, {{ 86},{  8}}, {{214},{  8}},
+{{ 54},{  8}}, {{182},{  8}}, {{118},{  8}}, {{246},{  8}}, {{ 14},{  8}},
+{{142},{  8}}, {{ 78},{  8}}, {{206},{  8}}, {{ 46},{  8}}, {{174},{  8}},
+{{110},{  8}}, {{238},{  8}}, {{ 30},{  8}}, {{158},{  8}}, {{ 94},{  8}},
+{{222},{  8}}, {{ 62},{  8}}, {{190},{  8}}, {{126},{  8}}, {{254},{  8}},
+{{  1},{  8}}, {{129},{  8}}, {{ 65},{  8}}, {{193},{  8}}, {{ 33},{  8}},
+{{161},{  8}}, {{ 97},{  8}}, {{225},{  8}}, {{ 17},{  8}}, {{145},{  8}},
+{{ 81},{  8}}, {{209},{  8}}, {{ 49},{  8}}, {{177},{  8}}, {{113},{  8}},
+{{241},{  8}}, {{  9},{  8}}, {{137},{  8}}, {{ 73},{  8}}, {{201},{  8}},
+{{ 41},{  8}}, {{169},{  8}}, {{105},{  8}}, {{233},{  8}}, {{ 25},{  8}},
+{{153},{  8}}, {{ 89},{  8}}, {{217},{  8}}, {{ 57},{  8}}, {{185},{  8}},
+{{121},{  8}}, {{249},{  8}}, {{  5},{  8}}, {{133},{  8}}, {{ 69},{  8}},
+{{197},{  8}}, {{ 37},{  8}}, {{165},{  8}}, {{101},{  8}}, {{229},{  8}},
+{{ 21},{  8}}, {{149},{  8}}, {{ 85},{  8}}, {{213},{  8}}, {{ 53},{  8}},
+{{181},{  8}}, {{117},{  8}}, {{245},{  8}}, {{ 13},{  8}}, {{141},{  8}},
+{{ 77},{  8}}, {{205},{  8}}, {{ 45},{  8}}, {{173},{  8}}, {{109},{  8}},
+{{237},{  8}}, {{ 29},{  8}}, {{157},{  8}}, {{ 93},{  8}}, {{221},{  8}},
+{{ 61},{  8}}, {{189},{  8}}, {{125},{  8}}, {{253},{  8}}, {{ 19},{  9}},
+{{275},{  9}}, {{147},{  9}}, {{403},{  9}}, {{ 83},{  9}}, {{339},{  9}},
+{{211},{  9}}, {{467},{  9}}, {{ 51},{  9}}, {{307},{  9}}, {{179},{  9}},
+{{435},{  9}}, {{115},{  9}}, {{371},{  9}}, {{243},{  9}}, {{499},{  9}},
+{{ 11},{  9}}, {{267},{  9}}, {{139},{  9}}, {{395},{  9}}, {{ 75},{  9}},
+{{331},{  9}}, {{203},{  9}}, {{459},{  9}}, {{ 43},{  9}}, {{299},{  9}},
+{{171},{  9}}, {{427},{  9}}, {{107},{  9}}, {{363},{  9}}, {{235},{  9}},
+{{491},{  9}}, {{ 27},{  9}}, {{283},{  9}}, {{155},{  9}}, {{411},{  9}},
+{{ 91},{  9}}, {{347},{  9}}, {{219},{  9}}, {{475},{  9}}, {{ 59},{  9}},
+{{315},{  9}}, {{187},{  9}}, {{443},{  9}}, {{123},{  9}}, {{379},{  9}},
+{{251},{  9}}, {{507},{  9}}, {{  7},{  9}}, {{263},{  9}}, {{135},{  9}},
+{{391},{  9}}, {{ 71},{  9}}, {{327},{  9}}, {{199},{  9}}, {{455},{  9}},
+{{ 39},{  9}}, {{295},{  9}}, {{167},{  9}}, {{423},{  9}}, {{103},{  9}},
+{{359},{  9}}, {{231},{  9}}, {{487},{  9}}, {{ 23},{  9}}, {{279},{  9}},
+{{151},{  9}}, {{407},{  9}}, {{ 87},{  9}}, {{343},{  9}}, {{215},{  9}},
+{{471},{  9}}, {{ 55},{  9}}, {{311},{  9}}, {{183},{  9}}, {{439},{  9}},
+{{119},{  9}}, {{375},{  9}}, {{247},{  9}}, {{503},{  9}}, {{ 15},{  9}},
+{{271},{  9}}, {{143},{  9}}, {{399},{  9}}, {{ 79},{  9}}, {{335},{  9}},
+{{207},{  9}}, {{463},{  9}}, {{ 47},{  9}}, {{303},{  9}}, {{175},{  9}},
+{{431},{  9}}, {{111},{  9}}, {{367},{  9}}, {{239},{  9}}, {{495},{  9}},
+{{ 31},{  9}}, {{287},{  9}}, {{159},{  9}}, {{415},{  9}}, {{ 95},{  9}},
+{{351},{  9}}, {{223},{  9}}, {{479},{  9}}, {{ 63},{  9}}, {{319},{  9}},
+{{191},{  9}}, {{447},{  9}}, {{127},{  9}}, {{383},{  9}}, {{255},{  9}},
+{{511},{  9}}, {{  0},{  7}}, {{ 64},{  7}}, {{ 32},{  7}}, {{ 96},{  7}},
+{{ 16},{  7}}, {{ 80},{  7}}, {{ 48},{  7}}, {{112},{  7}}, {{  8},{  7}},
+{{ 72},{  7}}, {{ 40},{  7}}, {{104},{  7}}, {{ 24},{  7}}, {{ 88},{  7}},
+{{ 56},{  7}}, {{120},{  7}}, {{  4},{  7}}, {{ 68},{  7}}, {{ 36},{  7}},
+{{100},{  7}}, {{ 20},{  7}}, {{ 84},{  7}}, {{ 52},{  7}}, {{116},{  7}},
+{{  3},{  8}}, {{131},{  8}}, {{ 67},{  8}}, {{195},{  8}}, {{ 35},{  8}},
+{{163},{  8}}, {{ 99},{  8}}, {{227},{  8}}
+};
+
+local const ct_data static_dtree[D_CODES] = {
+{{ 0},{ 5}}, {{16},{ 5}}, {{ 8},{ 5}}, {{24},{ 5}}, {{ 4},{ 5}},
+{{20},{ 5}}, {{12},{ 5}}, {{28},{ 5}}, {{ 2},{ 5}}, {{18},{ 5}},
+{{10},{ 5}}, {{26},{ 5}}, {{ 6},{ 5}}, {{22},{ 5}}, {{14},{ 5}},
+{{30},{ 5}}, {{ 1},{ 5}}, {{17},{ 5}}, {{ 9},{ 5}}, {{25},{ 5}},
+{{ 5},{ 5}}, {{21},{ 5}}, {{13},{ 5}}, {{29},{ 5}}, {{ 3},{ 5}},
+{{19},{ 5}}, {{11},{ 5}}, {{27},{ 5}}, {{ 7},{ 5}}, {{23},{ 5}}
+};
+
+const uch ZLIB_INTERNAL _dist_code[DIST_CODE_LEN] = {
+ 0,  1,  2,  3,  4,  4,  5,  5,  6,  6,  6,  6,  7,  7,  7,  7,  8,  8,  8,  8,
+ 8,  8,  8,  8,  9,  9,  9,  9,  9,  9,  9,  9, 10, 10, 10, 10, 10, 10, 10, 10,
+10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,
+11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
+12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13,
+13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,
+13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14,
+14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15,
+15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
+15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,  0,  0, 16, 17,
+18, 18, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 22,
+23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+24, 24, 24, 24, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
+26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
+26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27,
+27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
+28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
+28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28,
+28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
+29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
+29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29,
+29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29
+};
+
+const uch ZLIB_INTERNAL _length_code[MAX_MATCH-MIN_MATCH+1]= {
+ 0,  1,  2,  3,  4,  5,  6,  7,  8,  8,  9,  9, 10, 10, 11, 11, 12, 12, 12, 12,
+13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 16, 16, 16, 16,
+17, 17, 17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 19, 19, 19, 19,
+19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,
+21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22,
+22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23, 23, 23, 23, 23, 23, 23, 23,
+23, 23, 23, 23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
+25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
+25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 26, 26, 26, 26, 26, 26, 26, 26,
+26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
+26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,
+27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 28
+};
+
+local const int base_length[LENGTH_CODES] = {
+0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56,
+64, 80, 96, 112, 128, 160, 192, 224, 0
+};
+
+local const int base_dist[D_CODES] = {
+    0,     1,     2,     3,     4,     6,     8,    12,    16,    24,
+   32,    48,    64,    96,   128,   192,   256,   384,   512,   768,
+ 1024,  1536,  2048,  3072,  4096,  6144,  8192, 12288, 16384, 24576
+};
+
diff --git a/src/plugins/cfitsio/uncompr.c b/src/plugins/cfitsio/uncompr.c
new file mode 100644
index 0000000..769f83e
--- /dev/null
+++ b/src/plugins/cfitsio/uncompr.c
@@ -0,0 +1,57 @@
+/* uncompr.c -- decompress a memory buffer
+ * Copyright (C) 1995-2003, 2010 Jean-loup Gailly.
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+#define ZLIB_INTERNAL
+#include "zlib.h"
+
+/* ===========================================================================
+     Decompresses the source buffer into the destination buffer.  sourceLen is
+   the byte length of the source buffer. Upon entry, destLen is the total
+   size of the destination buffer, which must be large enough to hold the
+   entire uncompressed data. (The size of the uncompressed data must have
+   been saved previously by the compressor and transmitted to the decompressor
+   by some mechanism outside the scope of this compression library.)
+   Upon exit, destLen is the actual size of the compressed buffer.
+
+     uncompress returns Z_OK if success, Z_MEM_ERROR if there was not
+   enough memory, Z_BUF_ERROR if there was not enough room in the output
+   buffer, or Z_DATA_ERROR if the input data was corrupted.
+*/
+int ZEXPORT uncompress (dest, destLen, source, sourceLen)
+    Bytef *dest;
+    uLongf *destLen;
+    const Bytef *source;
+    uLong sourceLen;
+{
+    z_stream stream;
+    int err;
+
+    stream.next_in = (Bytef*)source;
+    stream.avail_in = (uInt)sourceLen;
+    /* Check for source > 64K on 16-bit machine: */
+    if ((uLong)stream.avail_in != sourceLen) return Z_BUF_ERROR;
+
+    stream.next_out = dest;
+    stream.avail_out = (uInt)*destLen;
+    if ((uLong)stream.avail_out != *destLen) return Z_BUF_ERROR;
+
+    stream.zalloc = (alloc_func)0;
+    stream.zfree = (free_func)0;
+
+    err = inflateInit(&stream);
+    if (err != Z_OK) return err;
+
+    err = inflate(&stream, Z_FINISH);
+    if (err != Z_STREAM_END) {
+        inflateEnd(&stream);
+        if (err == Z_NEED_DICT || (err == Z_BUF_ERROR && stream.avail_in == 0))
+            return Z_DATA_ERROR;
+        return err;
+    }
+    *destLen = stream.total_out;
+
+    err = inflateEnd(&stream);
+    return err;
+}
diff --git a/src/plugins/cfitsio/wcssub.c b/src/plugins/cfitsio/wcssub.c
new file mode 100644
index 0000000..afb8e5c
--- /dev/null
+++ b/src/plugins/cfitsio/wcssub.c
@@ -0,0 +1,1043 @@
+#include <stdlib.h>
+#include <math.h>
+#include <string.h>
+#include "fitsio2.h"
+
+/*--------------------------------------------------------------------------*/
+int fits_read_wcstab(
+   fitsfile   *fptr, /* I - FITS file pointer           */
+   int  nwtb,        /* Number of arrays to be read from the binary table(s) */
+   wtbarr *wtb,      /* Address of the first element of an array of wtbarr
+                         typedefs.  This wtbarr typedef is defined below to
+                         match the wtbarr struct defined in WCSLIB.  An array
+                         of such structs returned by the WCSLIB function
+                         wcstab(). */
+   int  *status)
+
+/*
+*   Author: Mark Calabretta, Australia Telescope National Facility
+*   http://www.atnf.csiro.au/~mcalabre/index.html
+*
+*   fits_read_wcstab() extracts arrays from a binary table required in
+*   constructing -TAB coordinates.  This helper routine is intended for
+*   use by routines in the WCSLIB library when dealing with the -TAB table
+*   look up WCS convention.
+*/
+
+{
+   int  anynul, colnum, hdunum, iwtb, m, naxis, nostat;
+   long *naxes = 0, nelem;
+   wtbarr *wtbp;
+
+
+   if (*status) return *status;
+
+   if (fptr == 0) {
+      return (*status = NULL_INPUT_PTR);
+   }
+
+   if (nwtb == 0) return 0;
+
+   /* Zero the array pointers. */
+   wtbp = wtb;
+   for (iwtb = 0; iwtb < nwtb; iwtb++, wtbp++) {
+     *wtbp->arrayp = 0x0;
+   }
+
+   /* Save HDU number so that we can move back to it later. */
+   fits_get_hdu_num(fptr, &hdunum);
+
+   wtbp = wtb;
+   for (iwtb = 0; iwtb < nwtb; iwtb++, wtbp++) {
+      /* Move to the required binary table extension. */
+      if (fits_movnam_hdu(fptr, BINARY_TBL, (char *)(wtbp->extnam),
+          wtbp->extver, status)) {
+         goto cleanup;
+      }
+
+      /* Locate the table column. */
+      if (fits_get_colnum(fptr, CASEINSEN, (char *)(wtbp->ttype), &colnum,
+          status)) {
+         goto cleanup;
+      }
+
+      /* Get the array dimensions and check for consistency. */
+      if (wtbp->ndim < 1) {
+         *status = NEG_AXIS;
+         goto cleanup;
+      }
+
+      if (!(naxes = calloc(wtbp->ndim, sizeof(long)))) {
+         *status = MEMORY_ALLOCATION;
+         goto cleanup;
+      }
+
+      if (fits_read_tdim(fptr, colnum, wtbp->ndim, &naxis, naxes, status)) {
+         goto cleanup;
+      }
+
+      if (naxis != wtbp->ndim) {
+         if (wtbp->kind == 'c' && wtbp->ndim == 2) {
+            /* Allow TDIMn to be omitted for degenerate coordinate arrays. */
+            naxis = 2;
+            naxes[1] = naxes[0];
+            naxes[0] = 1;
+         } else {
+            *status = BAD_TDIM;
+            goto cleanup;
+         }
+      }
+
+      if (wtbp->kind == 'c') {
+         /* Coordinate array; calculate the array size. */
+         nelem = naxes[0];
+         for (m = 0; m < naxis-1; m++) {
+            *(wtbp->dimlen + m) = naxes[m+1];
+            nelem *= naxes[m+1];
+         }
+      } else {
+         /* Index vector; check length. */
+         if ((nelem = naxes[0]) != *(wtbp->dimlen)) {
+            /* N.B. coordinate array precedes the index vectors. */
+            *status = BAD_TDIM;
+            goto cleanup;
+         }
+      }
+
+      free(naxes);
+      naxes = 0;
+
+      /* Allocate memory for the array. */
+      if (!(*wtbp->arrayp = calloc((size_t)nelem, sizeof(double)))) {
+         *status = MEMORY_ALLOCATION;
+         goto cleanup;
+      }
+
+      /* Read the array from the table. */
+      if (fits_read_col_dbl(fptr, colnum, wtbp->row, 1L, nelem, 0.0,
+          *wtbp->arrayp, &anynul, status)) {
+         goto cleanup;
+      }
+   }
+
+cleanup:
+   /* Move back to the starting HDU. */
+   nostat = 0;
+   fits_movabs_hdu(fptr, hdunum, 0, &nostat);
+
+   /* Release allocated memory. */
+   if (naxes) free(naxes);
+   if (*status) {
+      wtbp = wtb;
+      for (iwtb = 0; iwtb < nwtb; iwtb++, wtbp++) {
+         if (*wtbp->arrayp) free(*wtbp->arrayp);
+      }
+   }
+
+   return *status;
+}
+/*--------------------------------------------------------------------------*/
+int ffgiwcs(fitsfile *fptr,  /* I - FITS file pointer                    */
+           char **header,   /* O - pointer to the WCS related keywords  */
+           int *status)     /* IO - error status                        */
+/*
+  int fits_get_image_wcs_keys 
+  return a string containing all the image WCS header keywords.
+  This string is then used as input to the wcsinit WCSlib routine.
+  
+  THIS ROUTINE IS DEPRECATED. USE fits_hdr2str INSTEAD
+*/
+{
+    int hdutype;
+
+    if (*status > 0)
+        return(*status);
+
+    fits_get_hdu_type(fptr, &hdutype, status);
+    if (hdutype != IMAGE_HDU)
+    {
+      ffpmsg(
+     "Error in ffgiwcs. This HDU is not an image. Can't read WCS keywords");
+      return(*status = NOT_IMAGE);
+    }
+
+    /* read header keywords into a long string of chars */
+    if (ffh2st(fptr, header, status) > 0)
+    {
+        ffpmsg("error creating string of image WCS keywords (ffgiwcs)");
+        return(*status);
+    }
+
+    return(*status);
+}
+
+/*--------------------------------------------------------------------------*/
+int ffgics(fitsfile *fptr,    /* I - FITS file pointer           */
+           double *xrval,     /* O - X reference value           */
+           double *yrval,     /* O - Y reference value           */
+           double *xrpix,     /* O - X reference pixel           */
+           double *yrpix,     /* O - Y reference pixel           */
+           double *xinc,      /* O - X increment per pixel       */
+           double *yinc,      /* O - Y increment per pixel       */
+           double *rot,       /* O - rotation angle (degrees)    */
+           char *type,        /* O - type of projection ('-tan') */
+           int *status)       /* IO - error status               */
+/*
+       read the values of the celestial coordinate system keywords.
+       These values may be used as input to the subroutines that
+       calculate celestial coordinates. (ffxypx, ffwldp)
+
+       Modified in Nov 1999 to convert the CD matrix keywords back
+       to the old CDELTn form, and to swap the axes if the dec-like
+       axis is given first, and to assume default values if any of the
+       keywords are not present.
+*/
+{
+    int tstat = 0, cd_exists = 0, pc_exists = 0;
+    char ctype[FLEN_VALUE];
+    double cd11 = 0.0, cd21 = 0.0, cd22 = 0.0, cd12 = 0.0;
+    double pc11 = 1.0, pc21 = 0.0, pc22 = 1.0, pc12 = 0.0;
+    double pi =  3.1415926535897932;
+    double phia, phib, temp;
+    double toler = .0002;  /* tolerance for angles to agree (radians) */
+                           /*   (= approximately 0.01 degrees) */
+
+    if (*status > 0)
+       return(*status);
+
+    tstat = 0;
+    if (ffgkyd(fptr, "CRVAL1", xrval, NULL, &tstat))
+       *xrval = 0.;
+
+    tstat = 0;
+    if (ffgkyd(fptr, "CRVAL2", yrval, NULL, &tstat))
+       *yrval = 0.;
+
+    tstat = 0;
+    if (ffgkyd(fptr, "CRPIX1", xrpix, NULL, &tstat))
+        *xrpix = 0.;
+
+    tstat = 0;
+    if (ffgkyd(fptr, "CRPIX2", yrpix, NULL, &tstat))
+        *yrpix = 0.;
+
+    /* look for CDELTn first, then CDi_j keywords */
+    tstat = 0;
+    if (ffgkyd(fptr, "CDELT1", xinc, NULL, &tstat))
+    {
+        /* CASE 1: no CDELTn keyword, so look for the CD matrix */
+        tstat = 0;
+        if (ffgkyd(fptr, "CD1_1", &cd11, NULL, &tstat))
+            tstat = 0;  /* reset keyword not found error */
+        else
+            cd_exists = 1;  /* found at least 1 CD_ keyword */
+
+        if (ffgkyd(fptr, "CD2_1", &cd21, NULL, &tstat))
+            tstat = 0;  /* reset keyword not found error */
+        else
+            cd_exists = 1;  /* found at least 1 CD_ keyword */
+
+        if (ffgkyd(fptr, "CD1_2", &cd12, NULL, &tstat))
+            tstat = 0;  /* reset keyword not found error */
+        else
+            cd_exists = 1;  /* found at least 1 CD_ keyword */
+
+        if (ffgkyd(fptr, "CD2_2", &cd22, NULL, &tstat))
+            tstat = 0;  /* reset keyword not found error */
+        else
+            cd_exists = 1;  /* found at least 1 CD_ keyword */
+
+        if (cd_exists)  /* convert CDi_j back to CDELTn */
+        {
+            /* there are 2 ways to compute the angle: */
+            phia = atan2( cd21, cd11);
+            phib = atan2(-cd12, cd22);
+
+            /* ensure that phia <= phib */
+            temp = minvalue(phia, phib);
+            phib = maxvalue(phia, phib);
+            phia = temp;
+
+            /* there is a possible 180 degree ambiguity in the angles */
+            /* so add 180 degress to the smaller value if the values  */
+            /* differ by more than 90 degrees = pi/2 radians.         */
+            /* (Later, we may decide to take the other solution by    */
+            /* subtracting 180 degrees from the larger value).        */
+
+            if ((phib - phia) > (pi / 2.))
+               phia += pi;
+
+            if (fabs(phia - phib) > toler) 
+            {
+               /* angles don't agree, so looks like there is some skewness */
+               /* between the axes.  Return with an error to be safe. */
+               *status = APPROX_WCS_KEY;
+            }
+      
+            phia = (phia + phib) /2.;  /* use the average of the 2 values */
+            *xinc = cd11 / cos(phia);
+            *yinc = cd22 / cos(phia);
+            *rot = phia * 180. / pi;
+
+            /* common usage is to have a positive yinc value.  If it is */
+            /* negative, then subtract 180 degrees from rot and negate  */
+            /* both xinc and yinc.  */
+
+            if (*yinc < 0)
+            {
+                *xinc = -(*xinc);
+                *yinc = -(*yinc);
+                *rot = *rot - 180.;
+            }
+        }
+        else   /* no CD matrix keywords either */
+        {
+            *xinc = 1.;
+
+            /* there was no CDELT1 keyword, but check for CDELT2 just in case */
+            tstat = 0;
+            if (ffgkyd(fptr, "CDELT2", yinc, NULL, &tstat))
+                *yinc = 1.;
+
+            tstat = 0;
+            if (ffgkyd(fptr, "CROTA2", rot, NULL, &tstat))
+                *rot=0.;
+        }
+    }
+    else  /* Case 2: CDELTn + optional PC matrix */
+    {
+        if (ffgkyd(fptr, "CDELT2", yinc, NULL, &tstat))
+            *yinc = 1.;
+
+        tstat = 0;
+        if (ffgkyd(fptr, "CROTA2", rot, NULL, &tstat))
+        {
+            *rot=0.;
+
+            /* no CROTA2 keyword, so look for the PC matrix */
+            tstat = 0;
+            if (ffgkyd(fptr, "PC1_1", &pc11, NULL, &tstat))
+                tstat = 0;  /* reset keyword not found error */
+            else
+                pc_exists = 1;  /* found at least 1 PC_ keyword */
+
+            if (ffgkyd(fptr, "PC2_1", &pc21, NULL, &tstat))
+                tstat = 0;  /* reset keyword not found error */
+            else
+                pc_exists = 1;  /* found at least 1 PC_ keyword */
+
+            if (ffgkyd(fptr, "PC1_2", &pc12, NULL, &tstat))
+                tstat = 0;  /* reset keyword not found error */
+            else
+                pc_exists = 1;  /* found at least 1 PC_ keyword */
+
+            if (ffgkyd(fptr, "PC2_2", &pc22, NULL, &tstat))
+                tstat = 0;  /* reset keyword not found error */
+            else
+                pc_exists = 1;  /* found at least 1 PC_ keyword */
+
+            if (pc_exists)  /* convert PCi_j back to CDELTn */
+            {
+                /* there are 2 ways to compute the angle: */
+                phia = atan2( pc21, pc11);
+                phib = atan2(-pc12, pc22);
+
+                /* ensure that phia <= phib */
+                temp = minvalue(phia, phib);
+                phib = maxvalue(phia, phib);
+                phia = temp;
+
+                /* there is a possible 180 degree ambiguity in the angles */
+                /* so add 180 degress to the smaller value if the values  */
+                /* differ by more than 90 degrees = pi/2 radians.         */
+                /* (Later, we may decide to take the other solution by    */
+                /* subtracting 180 degrees from the larger value).        */
+
+                if ((phib - phia) > (pi / 2.))
+                   phia += pi;
+
+                if (fabs(phia - phib) > toler) 
+                {
+                  /* angles don't agree, so looks like there is some skewness */
+                  /* between the axes.  Return with an error to be safe. */
+                  *status = APPROX_WCS_KEY;
+                }
+      
+                phia = (phia + phib) /2.;  /* use the average of the 2 values */
+                *rot = phia * 180. / pi;
+            }
+        }
+    }
+
+    /* get the type of projection, if any */
+    tstat = 0;
+    if (ffgkys(fptr, "CTYPE1", ctype, NULL, &tstat))
+         type[0] = '\0';
+    else
+    {
+        /* copy the projection type string */
+        strncpy(type, &ctype[4], 4);
+        type[4] = '\0';
+
+        /* check if RA and DEC are inverted */
+        if (!strncmp(ctype, "DEC-", 4) || !strncmp(ctype+1, "LAT", 3))
+        {
+            /* the latitudinal axis is given first, so swap them */
+
+/*
+ this case was removed on 12/9.  Apparently not correct.
+
+            if ((*xinc / *yinc) < 0. )  
+                *rot = -90. - (*rot);
+            else
+*/
+            *rot = 90. - (*rot);
+
+            /* Empirical tests with ds9 show the y-axis sign must be negated */
+            /* and the xinc and yinc values must NOT be swapped. */
+            *yinc = -(*yinc);
+
+            temp = *xrval;
+            *xrval = *yrval;
+            *yrval = temp;
+        }   
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgicsa(fitsfile *fptr,    /* I - FITS file pointer           */
+           char version,      /* I - character code of desired version *(/
+	                      /*     A - Z or blank */
+           double *xrval,     /* O - X reference value           */
+           double *yrval,     /* O - Y reference value           */
+           double *xrpix,     /* O - X reference pixel           */
+           double *yrpix,     /* O - Y reference pixel           */
+           double *xinc,      /* O - X increment per pixel       */
+           double *yinc,      /* O - Y increment per pixel       */
+           double *rot,       /* O - rotation angle (degrees)    */
+           char *type,        /* O - type of projection ('-tan') */
+           int *status)       /* IO - error status               */
+/*
+       read the values of the celestial coordinate system keywords.
+       These values may be used as input to the subroutines that
+       calculate celestial coordinates. (ffxypx, ffwldp)
+
+       Modified in Nov 1999 to convert the CD matrix keywords back
+       to the old CDELTn form, and to swap the axes if the dec-like
+       axis is given first, and to assume default values if any of the
+       keywords are not present.
+*/
+{
+    int tstat = 0, cd_exists = 0, pc_exists = 0;
+    char ctype[FLEN_VALUE], keyname[FLEN_VALUE], alt[2];
+    double cd11 = 0.0, cd21 = 0.0, cd22 = 0.0, cd12 = 0.0;
+    double pc11 = 1.0, pc21 = 0.0, pc22 = 1.0, pc12 = 0.0;
+    double pi =  3.1415926535897932;
+    double phia, phib, temp;
+    double toler = .0002;  /* tolerance for angles to agree (radians) */
+                           /*   (= approximately 0.01 degrees) */
+
+    if (*status > 0)
+       return(*status);
+
+    if (version == ' ') {
+      ffgics(fptr, xrval, yrval, xrpix, yrpix, xinc, yinc, rot, type, status);
+      return (*status);
+    }
+
+    if (version > 'Z' || version < 'A') {
+      ffpmsg("ffgicsa: illegal WCS version code (must be A - Z or blank)");
+      return(*status = WCS_ERROR);
+    }
+
+    alt[0] = version;
+    alt[1] = '\0';
+    
+    tstat = 0;
+    strcpy(keyname, "CRVAL1");
+    strcat(keyname, alt);
+    if (ffgkyd(fptr, keyname, xrval, NULL, &tstat))
+       *xrval = 0.;
+
+    tstat = 0;
+    strcpy(keyname, "CRVAL2");
+    strcat(keyname, alt);
+    if (ffgkyd(fptr, keyname, yrval, NULL, &tstat))
+       *yrval = 0.;
+
+    tstat = 0;
+    strcpy(keyname, "CRPIX1");
+    strcat(keyname, alt);
+    if (ffgkyd(fptr, keyname, xrpix, NULL, &tstat))
+        *xrpix = 0.;
+
+    tstat = 0;
+    strcpy(keyname, "CRPIX2");
+    strcat(keyname, alt);
+     if (ffgkyd(fptr, keyname, yrpix, NULL, &tstat))
+        *yrpix = 0.;
+
+    /* look for CDELTn first, then CDi_j keywords */
+    tstat = 0;
+    strcpy(keyname, "CDELT1");
+    strcat(keyname, alt);
+    if (ffgkyd(fptr, keyname, xinc, NULL, &tstat))
+    {
+        /* CASE 1: no CDELTn keyword, so look for the CD matrix */
+        tstat = 0;
+        strcpy(keyname, "CD1_1");
+        strcat(keyname, alt);
+        if (ffgkyd(fptr, keyname, &cd11, NULL, &tstat))
+            tstat = 0;  /* reset keyword not found error */
+        else
+            cd_exists = 1;  /* found at least 1 CD_ keyword */
+
+        strcpy(keyname, "CD2_1");
+        strcat(keyname, alt);
+        if (ffgkyd(fptr, keyname, &cd21, NULL, &tstat))
+            tstat = 0;  /* reset keyword not found error */
+        else
+            cd_exists = 1;  /* found at least 1 CD_ keyword */
+
+        strcpy(keyname, "CD1_2");
+        strcat(keyname, alt);
+        if (ffgkyd(fptr, keyname, &cd12, NULL, &tstat))
+            tstat = 0;  /* reset keyword not found error */
+        else
+            cd_exists = 1;  /* found at least 1 CD_ keyword */
+
+        strcpy(keyname, "CD2_2");
+        strcat(keyname, alt);
+        if (ffgkyd(fptr, keyname, &cd22, NULL, &tstat))
+            tstat = 0;  /* reset keyword not found error */
+        else
+            cd_exists = 1;  /* found at least 1 CD_ keyword */
+
+        if (cd_exists)  /* convert CDi_j back to CDELTn */
+        {
+            /* there are 2 ways to compute the angle: */
+            phia = atan2( cd21, cd11);
+            phib = atan2(-cd12, cd22);
+
+            /* ensure that phia <= phib */
+            temp = minvalue(phia, phib);
+            phib = maxvalue(phia, phib);
+            phia = temp;
+
+            /* there is a possible 180 degree ambiguity in the angles */
+            /* so add 180 degress to the smaller value if the values  */
+            /* differ by more than 90 degrees = pi/2 radians.         */
+            /* (Later, we may decide to take the other solution by    */
+            /* subtracting 180 degrees from the larger value).        */
+
+            if ((phib - phia) > (pi / 2.))
+               phia += pi;
+
+            if (fabs(phia - phib) > toler) 
+            {
+               /* angles don't agree, so looks like there is some skewness */
+               /* between the axes.  Return with an error to be safe. */
+               *status = APPROX_WCS_KEY;
+            }
+      
+            phia = (phia + phib) /2.;  /* use the average of the 2 values */
+            *xinc = cd11 / cos(phia);
+            *yinc = cd22 / cos(phia);
+            *rot = phia * 180. / pi;
+
+            /* common usage is to have a positive yinc value.  If it is */
+            /* negative, then subtract 180 degrees from rot and negate  */
+            /* both xinc and yinc.  */
+
+            if (*yinc < 0)
+            {
+                *xinc = -(*xinc);
+                *yinc = -(*yinc);
+                *rot = *rot - 180.;
+            }
+        }
+        else   /* no CD matrix keywords either */
+        {
+            *xinc = 1.;
+
+            /* there was no CDELT1 keyword, but check for CDELT2 just in case */
+            tstat = 0;
+            strcpy(keyname, "CDELT2");
+            strcat(keyname, alt);
+            if (ffgkyd(fptr, keyname, yinc, NULL, &tstat))
+                *yinc = 1.;
+
+            tstat = 0;
+            strcpy(keyname, "CROTA2");
+            strcat(keyname, alt);
+            if (ffgkyd(fptr, keyname, rot, NULL, &tstat))
+                *rot=0.;
+        }
+    }
+    else  /* Case 2: CDELTn + optional PC matrix */
+    {
+        strcpy(keyname, "CDELT2");
+        strcat(keyname, alt);
+        if (ffgkyd(fptr, keyname, yinc, NULL, &tstat))
+            *yinc = 1.;
+
+        tstat = 0;
+        strcpy(keyname, "CROTA2");
+        strcat(keyname, alt);
+        if (ffgkyd(fptr, keyname, rot, NULL, &tstat))
+        {
+            *rot=0.;
+
+            /* no CROTA2 keyword, so look for the PC matrix */
+            tstat = 0;
+            strcpy(keyname, "PC1_1");
+            strcat(keyname, alt);
+            if (ffgkyd(fptr, keyname, &pc11, NULL, &tstat))
+                tstat = 0;  /* reset keyword not found error */
+            else
+                pc_exists = 1;  /* found at least 1 PC_ keyword */
+
+            strcpy(keyname, "PC2_1");
+            strcat(keyname, alt);
+            if (ffgkyd(fptr, keyname, &pc21, NULL, &tstat))
+                tstat = 0;  /* reset keyword not found error */
+            else
+                pc_exists = 1;  /* found at least 1 PC_ keyword */
+
+            strcpy(keyname, "PC1_2");
+            strcat(keyname, alt);
+            if (ffgkyd(fptr, keyname, &pc12, NULL, &tstat))
+                tstat = 0;  /* reset keyword not found error */
+            else
+                pc_exists = 1;  /* found at least 1 PC_ keyword */
+
+            strcpy(keyname, "PC2_2");
+            strcat(keyname, alt);
+            if (ffgkyd(fptr, keyname, &pc22, NULL, &tstat))
+                tstat = 0;  /* reset keyword not found error */
+            else
+                pc_exists = 1;  /* found at least 1 PC_ keyword */
+
+            if (pc_exists)  /* convert PCi_j back to CDELTn */
+            {
+                /* there are 2 ways to compute the angle: */
+                phia = atan2( pc21, pc11);
+                phib = atan2(-pc12, pc22);
+
+                /* ensure that phia <= phib */
+                temp = minvalue(phia, phib);
+                phib = maxvalue(phia, phib);
+                phia = temp;
+
+                /* there is a possible 180 degree ambiguity in the angles */
+                /* so add 180 degress to the smaller value if the values  */
+                /* differ by more than 90 degrees = pi/2 radians.         */
+                /* (Later, we may decide to take the other solution by    */
+                /* subtracting 180 degrees from the larger value).        */
+
+                if ((phib - phia) > (pi / 2.))
+                   phia += pi;
+
+                if (fabs(phia - phib) > toler) 
+                {
+                  /* angles don't agree, so looks like there is some skewness */
+                  /* between the axes.  Return with an error to be safe. */
+                  *status = APPROX_WCS_KEY;
+                }
+      
+                phia = (phia + phib) /2.;  /* use the average of the 2 values */
+                *rot = phia * 180. / pi;
+            }
+        }
+    }
+
+    /* get the type of projection, if any */
+    tstat = 0;
+    strcpy(keyname, "CTYPE1");
+    strcat(keyname, alt);
+    if (ffgkys(fptr, keyname, ctype, NULL, &tstat))
+         type[0] = '\0';
+    else
+    {
+        /* copy the projection type string */
+        strncpy(type, &ctype[4], 4);
+        type[4] = '\0';
+
+        /* check if RA and DEC are inverted */
+        if (!strncmp(ctype, "DEC-", 4) || !strncmp(ctype+1, "LAT", 3))
+        {
+            /* the latitudinal axis is given first, so swap them */
+
+            *rot = 90. - (*rot);
+
+            /* Empirical tests with ds9 show the y-axis sign must be negated */
+            /* and the xinc and yinc values must NOT be swapped. */
+            *yinc = -(*yinc);
+
+            temp = *xrval;
+            *xrval = *yrval;
+            *yrval = temp;
+        }   
+    }
+
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgtcs(fitsfile *fptr,    /* I - FITS file pointer           */
+           int xcol,          /* I - column containing the RA coordinate  */
+           int ycol,          /* I - column containing the DEC coordinate */
+           double *xrval,     /* O - X reference value           */
+           double *yrval,     /* O - Y reference value           */
+           double *xrpix,     /* O - X reference pixel           */
+           double *yrpix,     /* O - Y reference pixel           */
+           double *xinc,      /* O - X increment per pixel       */
+           double *yinc,      /* O - Y increment per pixel       */
+           double *rot,       /* O - rotation angle (degrees)    */
+           char *type,        /* O - type of projection ('-sin') */
+           int *status)       /* IO - error status               */
+/*
+       read the values of the celestial coordinate system keywords
+       from a FITS table where the X and Y or RA and DEC coordinates
+       are stored in separate column.  Do this by converting the
+       table to a temporary FITS image, then reading the keywords
+       from the image file.
+       These values may be used as input to the subroutines that
+       calculate celestial coordinates. (ffxypx, ffwldp)
+*/
+{
+    int colnum[2];
+    long naxes[2];
+    fitsfile *tptr;
+
+    if (*status > 0)
+       return(*status);
+
+    colnum[0] = xcol;
+    colnum[1] = ycol;
+    naxes[0] = 10;
+    naxes[1] = 10;
+
+    /* create temporary  FITS file, in memory */
+    ffinit(&tptr, "mem://", status);
+    
+    /* create a temporary image; the datatype and size are not important */
+    ffcrim(tptr, 32, 2, naxes, status);
+    
+    /* now copy the relevant keywords from the table to the image */
+    fits_copy_pixlist2image(fptr, tptr, 9, 2, colnum, status);
+
+    /* write default WCS keywords, if they are not present */
+    fits_write_keys_histo(fptr, tptr, 2, colnum, status);
+
+    if (*status > 0)
+       return(*status);
+         
+    /* read the WCS keyword values from the temporary image */
+    ffgics(tptr, xrval, yrval, xrpix, yrpix, xinc, yinc, rot, type, status); 
+
+    if (*status > 0)
+    {
+      ffpmsg
+      ("ffgtcs could not find all the celestial coordinate keywords");
+      return(*status = NO_WCS_KEY); 
+    }
+
+    /* delete the temporary file */
+    fits_delete_file(tptr, status);
+    
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int ffgtwcs(fitsfile *fptr,  /* I - FITS file pointer              */
+           int xcol,        /* I - column number for the X column  */
+           int ycol,        /* I - column number for the Y column  */
+           char **header,   /* O - string of all the WCS keywords  */
+           int *status)     /* IO - error status                   */
+/*
+  int fits_get_table_wcs_keys
+  Return string containing all the WCS keywords appropriate for the 
+  pair of X and Y columns containing the coordinate
+  of each event in an event list table.  This string may then be passed
+  to Doug Mink's WCS library wcsinit routine, to create and initialize the
+  WCS structure.  The calling routine must free the header character string
+  when it is no longer needed. 
+
+  THIS ROUTINE IS DEPRECATED. USE fits_hdr2str INSTEAD
+*/
+{
+    int hdutype, ncols, tstatus, length;
+    int naxis1 = 1, naxis2 = 1;
+    long tlmin, tlmax;
+    char keyname[FLEN_KEYWORD];
+    char valstring[FLEN_VALUE];
+    char comm[2];
+    char *cptr;
+    /*  construct a string of 80 blanks, for adding fill to the keywords */
+                 /*  12345678901234567890123456789012345678901234567890123456789012345678901234567890 */
+    char blanks[] = "                                                                                ";
+
+    if (*status > 0)
+        return(*status);
+
+    fits_get_hdu_type(fptr, &hdutype, status);
+    if (hdutype == IMAGE_HDU)
+    {
+        ffpmsg("Can't read table WSC keywords. This HDU is not a table");
+        return(*status = NOT_TABLE);
+    }
+
+    fits_get_num_cols(fptr, &ncols, status);
+    
+    if (xcol < 1 || xcol > ncols)
+    {
+        ffpmsg("illegal X axis column number in fftwcs");
+        return(*status = BAD_COL_NUM);
+    }
+
+    if (ycol < 1 || ycol > ncols)
+    {
+        ffpmsg("illegal Y axis column number in fftwcs");
+        return(*status = BAD_COL_NUM);
+    }
+
+    /* allocate character string for all the WCS keywords */
+    *header = calloc(1, 2401);  /* room for up to 30 keywords */
+    if (*header == 0)
+    {
+        ffpmsg("error allocating memory for WCS header keywords (fftwcs)");
+        return(*status = MEMORY_ALLOCATION);
+    }
+
+    cptr = *header;
+    comm[0] = '\0';
+    
+    tstatus = 0;
+    ffkeyn("TLMIN",xcol,keyname,status);
+    ffgkyj(fptr,keyname, &tlmin,NULL,&tstatus);
+
+    if (!tstatus)
+    {
+        ffkeyn("TLMAX",xcol,keyname,status);
+        ffgkyj(fptr,keyname, &tlmax,NULL,&tstatus);
+    }
+
+    if (!tstatus)
+    {
+        naxis1 = tlmax - tlmin + 1;
+    }
+
+    tstatus = 0;
+    ffkeyn("TLMIN",ycol,keyname,status);
+    ffgkyj(fptr,keyname, &tlmin,NULL,&tstatus);
+
+    if (!tstatus)
+    {
+        ffkeyn("TLMAX",ycol,keyname,status);
+        ffgkyj(fptr,keyname, &tlmax,NULL,&tstatus);
+    }
+
+    if (!tstatus)
+    {
+        naxis2 = tlmax - tlmin + 1;
+    }
+
+    /*            123456789012345678901234567890    */
+    strcat(cptr, "NAXIS   =                    2");
+    strncat(cptr, blanks, 50);
+    cptr += 80;
+
+    ffi2c(naxis1, valstring, status);   /* convert to formatted string */
+    ffmkky("NAXIS1", valstring, comm, cptr, status);  /* construct the keyword*/
+    strncat(cptr, blanks, 50);  /* pad with blanks */
+    cptr += 80;
+
+    strcpy(keyname, "NAXIS2");
+    ffi2c(naxis2, valstring, status);   /* convert to formatted string */
+    ffmkky(keyname, valstring, comm, cptr, status);  /* construct the keyword*/
+    strncat(cptr, blanks, 50);  /* pad with blanks */
+    cptr += 80;
+
+    /* read the required header keywords (use defaults if not found) */
+
+    /*  CTYPE1 keyword */
+    tstatus = 0;
+    ffkeyn("TCTYP",xcol,keyname,status);
+    if (ffgkey(fptr, keyname, valstring, NULL, &tstatus) )
+       valstring[0] =  '\0';
+    ffmkky("CTYPE1", valstring, comm, cptr, status);  /* construct the keyword*/
+    length = strlen(cptr);
+    strncat(cptr, blanks, 80 - length);  /* pad with blanks */
+    cptr += 80;
+
+    /*  CTYPE2 keyword */
+    tstatus = 0;
+    ffkeyn("TCTYP",ycol,keyname,status);
+    if (ffgkey(fptr, keyname, valstring, NULL, &tstatus) )
+       valstring[0] =  '\0';
+    ffmkky("CTYPE2", valstring, comm, cptr, status);  /* construct the keyword*/
+    length = strlen(cptr);
+    strncat(cptr, blanks, 80 - length);  /* pad with blanks */
+    cptr += 80;
+
+    /*  CRPIX1 keyword */
+    tstatus = 0;
+    ffkeyn("TCRPX",xcol,keyname,status);
+    if (ffgkey(fptr, keyname, valstring, NULL, &tstatus) )
+       strcpy(valstring, "1");
+    ffmkky("CRPIX1", valstring, comm, cptr, status);  /* construct the keyword*/
+    strncat(cptr, blanks, 50);  /* pad with blanks */
+    cptr += 80;
+
+    /*  CRPIX2 keyword */
+    tstatus = 0;
+    ffkeyn("TCRPX",ycol,keyname,status);
+    if (ffgkey(fptr, keyname, valstring, NULL, &tstatus) )
+       strcpy(valstring, "1");
+    ffmkky("CRPIX2", valstring, comm, cptr, status);  /* construct the keyword*/
+    strncat(cptr, blanks, 50);  /* pad with blanks */
+    cptr += 80;
+
+    /*  CRVAL1 keyword */
+    tstatus = 0;
+    ffkeyn("TCRVL",xcol,keyname,status);
+    if (ffgkey(fptr, keyname, valstring, NULL, &tstatus) )
+       strcpy(valstring, "1");
+    ffmkky("CRVAL1", valstring, comm, cptr, status);  /* construct the keyword*/
+    strncat(cptr, blanks, 50);  /* pad with blanks */
+    cptr += 80;
+
+    /*  CRVAL2 keyword */
+    tstatus = 0;
+    ffkeyn("TCRVL",ycol,keyname,status);
+    if (ffgkey(fptr, keyname, valstring, NULL, &tstatus) )
+       strcpy(valstring, "1");
+    ffmkky("CRVAL2", valstring, comm, cptr, status);  /* construct the keyword*/
+    strncat(cptr, blanks, 50);  /* pad with blanks */
+    cptr += 80;
+
+    /*  CDELT1 keyword */
+    tstatus = 0;
+    ffkeyn("TCDLT",xcol,keyname,status);
+    if (ffgkey(fptr, keyname, valstring, NULL, &tstatus) )
+       strcpy(valstring, "1");
+    ffmkky("CDELT1", valstring, comm, cptr, status);  /* construct the keyword*/
+    strncat(cptr, blanks, 50);  /* pad with blanks */
+    cptr += 80;
+
+    /*  CDELT2 keyword */
+    tstatus = 0;
+    ffkeyn("TCDLT",ycol,keyname,status);
+    if (ffgkey(fptr, keyname, valstring, NULL, &tstatus) )
+       strcpy(valstring, "1");
+    ffmkky("CDELT2", valstring, comm, cptr, status);  /* construct the keyword*/
+    strncat(cptr, blanks, 50);  /* pad with blanks */
+    cptr += 80;
+
+    /* the following keywords may not exist */
+
+    /*  CROTA2 keyword */
+    tstatus = 0;
+    ffkeyn("TCROT",ycol,keyname,status);
+    if (ffgkey(fptr, keyname, valstring, NULL, &tstatus) == 0 )
+    {
+        ffmkky("CROTA2", valstring, comm, cptr, status);  /* construct keyword*/
+        strncat(cptr, blanks, 50);  /* pad with blanks */
+        cptr += 80;
+    }
+
+    /*  EPOCH keyword */
+    tstatus = 0;
+    if (ffgkey(fptr, "EPOCH", valstring, NULL, &tstatus) == 0 )
+    {
+        ffmkky("EPOCH", valstring, comm, cptr, status);  /* construct keyword*/
+        length = strlen(cptr);
+        strncat(cptr, blanks, 80 - length);  /* pad with blanks */
+        cptr += 80;
+    }
+
+    /*  EQUINOX keyword */
+    tstatus = 0;
+    if (ffgkey(fptr, "EQUINOX", valstring, NULL, &tstatus) == 0 )
+    {
+        ffmkky("EQUINOX", valstring, comm, cptr, status); /* construct keyword*/
+        length = strlen(cptr);
+        strncat(cptr, blanks, 80 - length);  /* pad with blanks */
+        cptr += 80;
+    }
+
+    /*  RADECSYS keyword */
+    tstatus = 0;
+    if (ffgkey(fptr, "RADECSYS", valstring, NULL, &tstatus) == 0 )
+    {
+        ffmkky("RADECSYS", valstring, comm, cptr, status); /*construct keyword*/
+        length = strlen(cptr);
+        strncat(cptr, blanks, 80 - length);  /* pad with blanks */
+        cptr += 80;
+    }
+
+    /*  TELESCOPE keyword */
+    tstatus = 0;
+    if (ffgkey(fptr, "TELESCOP", valstring, NULL, &tstatus) == 0 )
+    {
+        ffmkky("TELESCOP", valstring, comm, cptr, status); 
+        length = strlen(cptr);
+        strncat(cptr, blanks, 80 - length);  /* pad with blanks */
+        cptr += 80;
+    }
+
+    /*  INSTRUME keyword */
+    tstatus = 0;
+    if (ffgkey(fptr, "INSTRUME", valstring, NULL, &tstatus) == 0 )
+    {
+        ffmkky("INSTRUME", valstring, comm, cptr, status);  
+        length = strlen(cptr);
+        strncat(cptr, blanks, 80 - length);  /* pad with blanks */
+        cptr += 80;
+    }
+
+    /*  DETECTOR keyword */
+    tstatus = 0;
+    if (ffgkey(fptr, "DETECTOR", valstring, NULL, &tstatus) == 0 )
+    {
+        ffmkky("DETECTOR", valstring, comm, cptr, status);  
+        length = strlen(cptr);
+        strncat(cptr, blanks, 80 - length);  /* pad with blanks */
+        cptr += 80;
+    }
+
+    /*  MJD-OBS keyword */
+    tstatus = 0;
+    if (ffgkey(fptr, "MJD-OBS", valstring, NULL, &tstatus) == 0 )
+    {
+        ffmkky("MJD-OBS", valstring, comm, cptr, status);  
+        length = strlen(cptr);
+        strncat(cptr, blanks, 80 - length);  /* pad with blanks */
+        cptr += 80;
+    }
+
+    /*  DATE-OBS keyword */
+    tstatus = 0;
+    if (ffgkey(fptr, "DATE-OBS", valstring, NULL, &tstatus) == 0 )
+    {
+        ffmkky("DATE-OBS", valstring, comm, cptr, status);  
+        length = strlen(cptr);
+        strncat(cptr, blanks, 80 - length);  /* pad with blanks */
+        cptr += 80;
+    }
+
+    /*  DATE keyword */
+    tstatus = 0;
+    if (ffgkey(fptr, "DATE", valstring, NULL, &tstatus) == 0 )
+    {
+        ffmkky("DATE", valstring, comm, cptr, status);  
+        length = strlen(cptr);
+        strncat(cptr, blanks, 80 - length);  /* pad with blanks */
+        cptr += 80;
+    }
+
+    strcat(cptr, "END");
+    strncat(cptr, blanks, 77);
+
+    return(*status);
+}
diff --git a/src/plugins/cfitsio/wcsutil.c b/src/plugins/cfitsio/wcsutil.c
new file mode 100644
index 0000000..01d80f9
--- /dev/null
+++ b/src/plugins/cfitsio/wcsutil.c
@@ -0,0 +1,502 @@
+#include <math.h>
+#include "fitsio2.h"
+#define D2R 0.01745329252
+#define TWOPI 6.28318530717959
+
+/*--------------------------------------------------------------------------*/
+int ffwldp(double xpix, double ypix, double xref, double yref,
+      double xrefpix, double yrefpix, double xinc, double yinc, double rot,
+      char *type, double *xpos, double *ypos, int *status)
+
+/* This routine is based on the classic AIPS WCS routine. 
+
+   It converts from pixel location to RA,Dec for 9 projective geometries:
+   "-CAR", "-SIN", "-TAN", "-ARC", "-NCP", "-GLS", "-MER", "-AIT" and "-STG".
+*/
+
+/*-----------------------------------------------------------------------*/
+/* routine to determine accurate position for pixel coordinates          */
+/* returns 0 if successful otherwise:                                    */
+/* 501 = angle too large for projection;                                 */
+/* does: -CAR, -SIN, -TAN, -ARC, -NCP, -GLS, -MER, -AIT  -STG projections*/
+/* Input:                                                                */
+/*   f   xpix    x pixel number  (RA or long without rotation)           */
+/*   f   ypiy    y pixel number  (dec or lat without rotation)           */
+/*   d   xref    x reference coordinate value (deg)                      */
+/*   d   yref    y reference coordinate value (deg)                      */
+/*   f   xrefpix x reference pixel                                       */
+/*   f   yrefpix y reference pixel                                       */
+/*   f   xinc    x coordinate increment (deg)                            */
+/*   f   yinc    y coordinate increment (deg)                            */
+/*   f   rot     rotation (deg)  (from N through E)                      */
+/*   c  *type    projection type code e.g. "-SIN";                       */
+/* Output:                                                               */
+/*   d   *xpos   x (RA) coordinate (deg)                                 */
+/*   d   *ypos   y (dec) coordinate (deg)                                */
+/*-----------------------------------------------------------------------*/
+ {double cosr, sinr, dx, dy, dz, temp, x, y, z;
+  double sins, coss, dect, rat, dt, l, m, mg, da, dd, cos0, sin0;
+  double dec0, ra0;
+  double geo1, geo2, geo3;
+  double deps = 1.0e-5;
+  char *cptr;
+  
+  if (*status > 0)
+     return(*status);
+
+/*   Offset from ref pixel  */
+  dx = (xpix-xrefpix) * xinc;
+  dy = (ypix-yrefpix) * yinc;
+
+/*   Take out rotation  */
+  cosr = cos(rot * D2R);
+  sinr = sin(rot * D2R);
+  if (rot != 0.0) {
+     temp = dx * cosr - dy * sinr;
+     dy = dy * cosr + dx * sinr;
+     dx = temp;
+  }
+
+/* convert to radians  */
+  ra0 = xref * D2R;
+  dec0 = yref * D2R;
+
+  l = dx * D2R;
+  m = dy * D2R;
+  sins = l*l + m*m;
+  cos0 = cos(dec0);
+  sin0 = sin(dec0);
+
+  if (*type != '-') {  /* unrecognized projection code */
+     return(*status = 504);
+  }
+
+    cptr = type + 1;
+
+    if (*cptr == 'C') { /* linear -CAR */
+      if (*(cptr + 1) != 'A' ||  *(cptr + 2) != 'R') {
+         return(*status = 504);
+      }
+      rat =  ra0 + l;
+      dect = dec0 + m;
+
+    } else if (*cptr == 'T') {  /* -TAN */
+      if (*(cptr + 1) != 'A' ||  *(cptr + 2) != 'N') {
+         return(*status = 504);
+      }
+      x = cos0*cos(ra0) - l*sin(ra0) - m*cos(ra0)*sin0;
+      y = cos0*sin(ra0) + l*cos(ra0) - m*sin(ra0)*sin0;
+      z = sin0                       + m*         cos0;
+      rat  = atan2( y, x );
+      dect = atan ( z / sqrt(x*x+y*y) );
+
+    } else if (*cptr == 'S') {
+
+      if (*(cptr + 1) == 'I' &&  *(cptr + 2) == 'N') { /* -SIN */
+          if (sins>1.0)
+	    return(*status = 501);
+          coss = sqrt (1.0 - sins);
+          dt = sin0 * coss + cos0 * m;
+          if ((dt>1.0) || (dt<-1.0))
+	    return(*status = 501);
+          dect = asin (dt);
+          rat = cos0 * coss - sin0 * m;
+          if ((rat==0.0) && (l==0.0))
+	    return(*status = 501);
+          rat = atan2 (l, rat) + ra0;
+
+       } else if (*(cptr + 1) == 'T' &&  *(cptr + 2) == 'G') {  /* -STG Sterographic*/
+          dz = (4.0 - sins) / (4.0 + sins);
+          if (fabs(dz)>1.0)
+	    return(*status = 501);
+          dect = dz * sin0 + m * cos0 * (1.0+dz) / 2.0;
+          if (fabs(dect)>1.0)
+	    return(*status = 501);
+          dect = asin (dect);
+          rat = cos(dect);
+          if (fabs(rat)<deps)
+	    return(*status = 501);
+          rat = l * (1.0+dz) / (2.0 * rat);
+          if (fabs(rat)>1.0)
+	    return(*status = 501);
+          rat = asin (rat);
+          mg = 1.0 + sin(dect) * sin0 + cos(dect) * cos0 * cos(rat);
+          if (fabs(mg)<deps)
+	    return(*status = 501);
+          mg = 2.0 * (sin(dect) * cos0 - cos(dect) * sin0 * cos(rat)) / mg;
+          if (fabs(mg-m)>deps)
+	    rat = TWOPI /2.0 - rat;
+          rat = ra0 + rat;
+        } else  {
+          return(*status = 504);
+        }
+ 
+    } else if (*cptr == 'A') {
+
+      if (*(cptr + 1) == 'R' &&  *(cptr + 2) == 'C') { /* ARC */
+          if (sins>=TWOPI*TWOPI/4.0)
+	    return(*status = 501);
+          sins = sqrt(sins);
+          coss = cos (sins);
+          if (sins!=0.0)
+	    sins = sin (sins) / sins;
+          else
+	    sins = 1.0;
+          dt = m * cos0 * sins + sin0 * coss;
+          if ((dt>1.0) || (dt<-1.0))
+	    return(*status = 501);
+          dect = asin (dt);
+          da = coss - dt * sin0;
+          dt = l * sins * cos0;
+          if ((da==0.0) && (dt==0.0))
+	    return(*status = 501);
+          rat = ra0 + atan2 (dt, da);
+
+      } else if (*(cptr + 1) == 'I' &&  *(cptr + 2) == 'T') {  /* -AIT Aitoff */
+          dt = yinc*cosr + xinc*sinr;
+          if (dt==0.0)
+	    dt = 1.0;
+          dt = dt * D2R;
+          dy = yref * D2R;
+          dx = sin(dy+dt)/sqrt((1.0+cos(dy+dt))/2.0) -
+	      sin(dy)/sqrt((1.0+cos(dy))/2.0);
+          if (dx==0.0)
+	    dx = 1.0;
+          geo2 = dt / dx;
+          dt = xinc*cosr - yinc* sinr;
+          if (dt==0.0)
+	    dt = 1.0;
+          dt = dt * D2R;
+          dx = 2.0 * cos(dy) * sin(dt/2.0);
+          if (dx==0.0) dx = 1.0;
+          geo1 = dt * sqrt((1.0+cos(dy)*cos(dt/2.0))/2.0) / dx;
+          geo3 = geo2 * sin(dy) / sqrt((1.0+cos(dy))/2.0);
+          rat = ra0;
+          dect = dec0;
+          if ((l != 0.0) || (m != 0.0)) {
+            dz = 4.0 - l*l/(4.0*geo1*geo1) - ((m+geo3)/geo2)*((m+geo3)/geo2) ;
+            if ((dz>4.0) || (dz<2.0)) return(*status = 501);
+            dz = 0.5 * sqrt (dz);
+            dd = (m+geo3) * dz / geo2;
+            if (fabs(dd)>1.0) return(*status = 501);
+            dd = asin (dd);
+            if (fabs(cos(dd))<deps) return(*status = 501);
+            da = l * dz / (2.0 * geo1 * cos(dd));
+            if (fabs(da)>1.0) return(*status = 501);
+            da = asin (da);
+            rat = ra0 + 2.0 * da;
+            dect = dd;
+          }
+        } else  {
+          return(*status = 504);
+        }
+ 
+    } else if (*cptr == 'N') { /* -NCP North celestial pole*/
+      if (*(cptr + 1) != 'C' ||  *(cptr + 2) != 'P') {
+         return(*status = 504);
+      }
+      dect = cos0 - m * sin0;
+      if (dect==0.0)
+        return(*status = 501);
+      rat = ra0 + atan2 (l, dect);
+      dt = cos (rat-ra0);
+      if (dt==0.0)
+        return(*status = 501);
+      dect = dect / dt;
+      if ((dect>1.0) || (dect<-1.0))
+        return(*status = 501);
+      dect = acos (dect);
+      if (dec0<0.0) dect = -dect;
+
+    } else if (*cptr == 'G') {   /* -GLS global sinusoid */
+      if (*(cptr + 1) != 'L' ||  *(cptr + 2) != 'S') {
+         return(*status = 504);
+      }
+      dect = dec0 + m;
+      if (fabs(dect)>TWOPI/4.0)
+        return(*status = 501);
+      coss = cos (dect);
+      if (fabs(l)>TWOPI*coss/2.0)
+        return(*status = 501);
+      rat = ra0;
+      if (coss>deps) rat = rat + l / coss;
+
+    } else if (*cptr == 'M') {  /* -MER mercator*/
+      if (*(cptr + 1) != 'E' ||  *(cptr + 2) != 'R') {
+         return(*status = 504);
+      }
+      dt = yinc * cosr + xinc * sinr;
+      if (dt==0.0) dt = 1.0;
+      dy = (yref/2.0 + 45.0) * D2R;
+      dx = dy + dt / 2.0 * D2R;
+      dy = log (tan (dy));
+      dx = log (tan (dx));
+      geo2 = dt * D2R / (dx - dy);
+      geo3 = geo2 * dy;
+      geo1 = cos (yref*D2R);
+      if (geo1<=0.0) geo1 = 1.0;
+      rat = l / geo1 + ra0;
+      if (fabs(rat - ra0) > TWOPI)
+        return(*status = 501);
+      dt = 0.0;
+      if (geo2!=0.0) dt = (m + geo3) / geo2;
+      dt = exp (dt);
+      dect = 2.0 * atan (dt) - TWOPI / 4.0;
+
+    } else  {
+      return(*status = 504);
+    }
+
+  /*  correct for RA rollover  */
+  if (rat-ra0>TWOPI/2.0) rat = rat - TWOPI;
+  if (rat-ra0<-TWOPI/2.0) rat = rat + TWOPI;
+  if (rat < 0.0) rat += TWOPI;
+
+  /*  convert to degrees  */
+  *xpos  = rat  / D2R;
+  *ypos  = dect  / D2R;
+  return(*status);
+} 
+/*--------------------------------------------------------------------------*/
+int ffxypx(double xpos, double ypos, double xref, double yref, 
+      double xrefpix, double yrefpix, double xinc, double yinc, double rot,
+      char *type, double *xpix, double *ypix, int *status)
+
+/* This routine is based on the classic AIPS WCS routine. 
+
+   It converts from RA,Dec to pixel location to for 9 projective geometries:
+   "-CAR", "-SIN", "-TAN", "-ARC", "-NCP", "-GLS", "-MER", "-AIT" and "-STG".
+*/
+/*-----------------------------------------------------------------------*/
+/* routine to determine accurate pixel coordinates for an RA and Dec     */
+/* returns 0 if successful otherwise:                                    */
+/* 501 = angle too large for projection;                                 */
+/* 502 = bad values                                                      */
+/* does: -SIN, -TAN, -ARC, -NCP, -GLS, -MER, -AIT projections            */
+/* anything else is linear                                               */
+/* Input:                                                                */
+/*   d   xpos    x (RA) coordinate (deg)                                 */
+/*   d   ypos    y (dec) coordinate (deg)                                */
+/*   d   xref    x reference coordinate value (deg)                      */
+/*   d   yref    y reference coordinate value (deg)                      */
+/*   f   xrefpix x reference pixel                                       */
+/*   f   yrefpix y reference pixel                                       */
+/*   f   xinc    x coordinate increment (deg)                            */
+/*   f   yinc    y coordinate increment (deg)                            */
+/*   f   rot     rotation (deg)  (from N through E)                      */
+/*   c  *type    projection type code e.g. "-SIN";                       */
+/* Output:                                                               */
+/*   f  *xpix    x pixel number  (RA or long without rotation)           */
+/*   f  *ypiy    y pixel number  (dec or lat without rotation)           */
+/*-----------------------------------------------------------------------*/
+ {
+  double dx, dy, dz, r, ra0, dec0, ra, dec, coss, sins, dt, da, dd, sint;
+  double l, m, geo1, geo2, geo3, sinr, cosr, cos0, sin0;
+  double deps=1.0e-5;
+  char *cptr;
+
+  if (*type != '-') {  /* unrecognized projection code */
+     return(*status = 504);
+  }
+
+  cptr = type + 1;
+
+  dt = (xpos - xref);
+  if (dt >  180) xpos -= 360;
+  if (dt < -180) xpos += 360;
+  /* NOTE: changing input argument xpos is OK (call-by-value in C!) */
+
+  /* default values - linear */
+  dx = xpos - xref;
+  dy = ypos - yref;
+
+  /*  Correct for rotation */
+  r = rot * D2R;
+  cosr = cos (r);
+  sinr = sin (r);
+  dz = dx*cosr + dy*sinr;
+  dy = dy*cosr - dx*sinr;
+  dx = dz;
+
+  /*     check axis increments - bail out if either 0 */
+  if ((xinc==0.0) || (yinc==0.0)) {*xpix=0.0; *ypix=0.0;
+    return(*status = 502);}
+
+  /*     convert to pixels  */
+  *xpix = dx / xinc + xrefpix;
+  *ypix = dy / yinc + yrefpix;
+
+  if (*cptr == 'C') { /* linear -CAR */
+      if (*(cptr + 1) != 'A' ||  *(cptr + 2) != 'R') {
+         return(*status = 504);
+      }
+
+      return(*status);  /* done if linear */
+  }
+
+  /* Non linear position */
+  ra0 = xref * D2R;
+  dec0 = yref * D2R;
+  ra = xpos * D2R;
+  dec = ypos * D2R;
+
+  /* compute direction cosine */
+  coss = cos (dec);
+  sins = sin (dec);
+  cos0 = cos (dec0);
+  sin0 = sin (dec0);
+  l = sin(ra-ra0) * coss;
+  sint = sins * sin0 + coss * cos0 * cos(ra-ra0);
+
+    /* process by case  */
+    if (*cptr == 'T') {  /* -TAN tan */
+         if (*(cptr + 1) != 'A' ||  *(cptr + 2) != 'N') {
+           return(*status = 504);
+         }
+
+         if (sint<=0.0)
+	   return(*status = 501);
+         if( cos0<0.001 ) {
+            /* Do a first order expansion around pole */
+            m = (coss * cos(ra-ra0)) / (sins * sin0);
+            m = (-m + cos0 * (1.0 + m*m)) / sin0;
+         } else {
+            m = ( sins/sint - sin0 ) / cos0;
+         }
+	 if( fabs(sin(ra0)) < 0.3 ) {
+	    l  = coss*sin(ra)/sint - cos0*sin(ra0) + m*sin(ra0)*sin0;
+	    l /= cos(ra0);
+	 } else {
+	    l  = coss*cos(ra)/sint - cos0*cos(ra0) + m*cos(ra0)*sin0;
+	    l /= -sin(ra0);
+	 }
+
+    } else if (*cptr == 'S') {
+
+      if (*(cptr + 1) == 'I' &&  *(cptr + 2) == 'N') { /* -SIN */
+         if (sint<0.0)
+	   return(*status = 501);
+         m = sins * cos(dec0) - coss * sin(dec0) * cos(ra-ra0);
+
+      } else if (*(cptr + 1) == 'T' &&  *(cptr + 2) == 'G') {  /* -STG Sterographic*/
+         da = ra - ra0;
+         if (fabs(dec)>TWOPI/4.0)
+	   return(*status = 501);
+         dd = 1.0 + sins * sin(dec0) + coss * cos(dec0) * cos(da);
+         if (fabs(dd)<deps)
+	   return(*status = 501);
+         dd = 2.0 / dd;
+         l = l * dd;
+         m = dd * (sins * cos(dec0) - coss * sin(dec0) * cos(da));
+
+        } else  {
+          return(*status = 504);
+        }
+ 
+    } else if (*cptr == 'A') {
+
+      if (*(cptr + 1) == 'R' &&  *(cptr + 2) == 'C') { /* ARC */
+         m = sins * sin(dec0) + coss * cos(dec0) * cos(ra-ra0);
+         if (m<-1.0) m = -1.0;
+         if (m>1.0) m = 1.0;
+         m = acos (m);
+         if (m!=0) 
+            m = m / sin(m);
+         else
+            m = 1.0;
+         l = l * m;
+         m = (sins * cos(dec0) - coss * sin(dec0) * cos(ra-ra0)) * m;
+
+      } else if (*(cptr + 1) == 'I' &&  *(cptr + 2) == 'T') {  /* -AIT Aitoff */
+         da = (ra - ra0) / 2.0;
+         if (fabs(da)>TWOPI/4.0)
+	     return(*status = 501);
+         dt = yinc*cosr + xinc*sinr;
+         if (dt==0.0) dt = 1.0;
+         dt = dt * D2R;
+         dy = yref * D2R;
+         dx = sin(dy+dt)/sqrt((1.0+cos(dy+dt))/2.0) -
+             sin(dy)/sqrt((1.0+cos(dy))/2.0);
+         if (dx==0.0) dx = 1.0;
+         geo2 = dt / dx;
+         dt = xinc*cosr - yinc* sinr;
+         if (dt==0.0) dt = 1.0;
+         dt = dt * D2R;
+         dx = 2.0 * cos(dy) * sin(dt/2.0);
+         if (dx==0.0) dx = 1.0;
+         geo1 = dt * sqrt((1.0+cos(dy)*cos(dt/2.0))/2.0) / dx;
+         geo3 = geo2 * sin(dy) / sqrt((1.0+cos(dy))/2.0);
+         dt = sqrt ((1.0 + cos(dec) * cos(da))/2.0);
+         if (fabs(dt)<deps)
+	     return(*status = 503);
+         l = 2.0 * geo1 * cos(dec) * sin(da) / dt;
+         m = geo2 * sin(dec) / dt - geo3;
+
+        } else  {
+          return(*status = 504);
+        }
+ 
+    } else if (*cptr == 'N') { /* -NCP North celestial pole*/
+         if (*(cptr + 1) != 'C' ||  *(cptr + 2) != 'P') {
+             return(*status = 504);
+         }
+
+         if (dec0==0.0) 
+	     return(*status = 501);  /* can't stand the equator */
+         else
+	   m = (cos(dec0) - coss * cos(ra-ra0)) / sin(dec0);
+
+    } else if (*cptr == 'G') {   /* -GLS global sinusoid */
+         if (*(cptr + 1) != 'L' ||  *(cptr + 2) != 'S') {
+             return(*status = 504);
+         }
+
+         dt = ra - ra0;
+         if (fabs(dec)>TWOPI/4.0)
+	   return(*status = 501);
+         if (fabs(dec0)>TWOPI/4.0)
+	   return(*status = 501);
+         m = dec - dec0;
+         l = dt * coss;
+
+    } else if (*cptr == 'M') {  /* -MER mercator*/
+         if (*(cptr + 1) != 'E' ||  *(cptr + 2) != 'R') {
+             return(*status = 504);
+         }
+
+         dt = yinc * cosr + xinc * sinr;
+         if (dt==0.0) dt = 1.0;
+         dy = (yref/2.0 + 45.0) * D2R;
+         dx = dy + dt / 2.0 * D2R;
+         dy = log (tan (dy));
+         dx = log (tan (dx));
+         geo2 = dt * D2R / (dx - dy);
+         geo3 = geo2 * dy;
+         geo1 = cos (yref*D2R);
+         if (geo1<=0.0) geo1 = 1.0;
+         dt = ra - ra0;
+         l = geo1 * dt;
+         dt = dec / 2.0 + TWOPI / 8.0;
+         dt = tan (dt);
+         if (dt<deps)
+	   return(*status = 502);
+         m = geo2 * log (dt) - geo3;
+
+    } else  {
+      return(*status = 504);
+    }
+
+    /*   convert to degrees  */
+    dx = l / D2R;
+    dy = m / D2R;
+
+    /*  Correct for rotation */
+    dz = dx*cosr + dy*sinr;
+    dy = dy*cosr - dx*sinr;
+    dx = dz;
+
+    /*     convert to pixels  */
+    *xpix = dx / xinc + xrefpix;
+    *ypix = dy / yinc + yrefpix;
+    return(*status);
+}
diff --git a/src/plugins/cfitsio/zcompress.c b/src/plugins/cfitsio/zcompress.c
new file mode 100644
index 0000000..b8d7e79
--- /dev/null
+++ b/src/plugins/cfitsio/zcompress.c
@@ -0,0 +1,504 @@
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include "zlib.h"  
+
+unsigned int GZBUFSIZE = 115200;  /* 40 FITS blocks */
+int BUFFINCR = 28800;  /* 10 FITS blocks */
+
+/* prototype for the following functions */
+int uncompress2mem(char *filename, 
+             FILE *diskfile, 
+             char **buffptr, 
+             size_t *buffsize, 
+             void *(*mem_realloc)(void *p, size_t newsize),
+             size_t *filesize,
+             int *status);
+
+int uncompress2mem_from_mem(                                                
+             char *inmemptr,     
+             size_t inmemsize, 
+             char **buffptr,  
+             size_t *buffsize,  
+             void *(*mem_realloc)(void *p, size_t newsize), 
+             size_t *filesize,  
+             int *status);
+
+int uncompress2file(char *filename, 
+             FILE *indiskfile, 
+             FILE *outdiskfile, 
+             int *status);
+
+
+int compress2mem_from_mem(                                                
+             char *inmemptr,     
+             size_t inmemsize, 
+             char **buffptr,  
+             size_t *buffsize,  
+             void *(*mem_realloc)(void *p, size_t newsize), 
+             size_t *filesize,  
+             int *status);
+
+int compress2file_from_mem(                                                
+             char *inmemptr,     
+             size_t inmemsize, 
+             FILE *outdiskfile, 
+             size_t *filesize,   /* O - size of file, in bytes              */
+             int *status);
+
+
+/*--------------------------------------------------------------------------*/
+int uncompress2mem(char *filename,  /* name of input file                 */
+             FILE *diskfile,     /* I - file pointer                        */
+             char **buffptr,   /* IO - memory pointer                     */
+             size_t *buffsize,   /* IO - size of buffer, in bytes           */
+             void *(*mem_realloc)(void *p, size_t newsize), /* function     */
+             size_t *filesize,   /* O - size of file, in bytes              */
+             int *status)        /* IO - error status                       */
+
+/*
+  Uncompress the disk file into memory.  Fill whatever amount of memory has
+  already been allocated, then realloc more memory, using the supplied
+  input function, if necessary.
+*/
+{
+    int err, len;
+    char *filebuff;
+    z_stream d_stream;   /* decompression stream */
+
+    if (*status > 0) 
+        return(*status); 
+
+    /* Allocate memory to hold compressed bytes read from the file. */
+    filebuff = (char*)malloc(GZBUFSIZE);
+    if (!filebuff) return(*status = 113); /* memory error */
+
+    d_stream.zalloc = (alloc_func)0;
+    d_stream.zfree = (free_func)0;
+    d_stream.opaque = (voidpf)0;
+    d_stream.next_out = (unsigned char*) *buffptr;
+    d_stream.avail_out = *buffsize;
+
+    /* Initialize the decompression.  The argument (15+16) tells the
+       decompressor that we are to use the gzip algorithm */
+
+    err = inflateInit2(&d_stream, (15+16));
+    if (err != Z_OK) return(*status = 414);
+
+    /* loop through the file, reading a buffer and uncompressing it */
+    for (;;)
+    {
+        len = fread(filebuff, 1, GZBUFSIZE, diskfile);
+	if (ferror(diskfile)) {
+              inflateEnd(&d_stream);
+              free(filebuff);
+              return(*status = 414);
+	}
+
+        if (len == 0) break;  /* no more data */
+
+        d_stream.next_in = (unsigned char*)filebuff;
+        d_stream.avail_in = len;
+
+        for (;;) {
+            /* uncompress as much of the input as will fit in the output */
+            err = inflate(&d_stream, Z_NO_FLUSH);
+
+            if (err == Z_STREAM_END ) { /* We reached the end of the input */
+	        break; 
+            } else if (err == Z_OK ) { 
+
+                if (!d_stream.avail_in) break; /* need more input */
+		
+                /* need more space in output buffer */
+                if (mem_realloc) {   
+                    *buffptr = mem_realloc(*buffptr,*buffsize + BUFFINCR);
+                    if (*buffptr == NULL){
+                        inflateEnd(&d_stream);
+                        free(filebuff);
+                        return(*status = 414);  /* memory allocation failed */
+                    }
+
+                    d_stream.avail_out = BUFFINCR;
+                    d_stream.next_out = (unsigned char*) (*buffptr + *buffsize);
+                    *buffsize = *buffsize + BUFFINCR;
+                } else  { /* error: no realloc function available */
+                    inflateEnd(&d_stream);
+                    free(filebuff);
+                    return(*status = 414);
+                }
+            } else {  /* some other error */
+                inflateEnd(&d_stream);
+                free(filebuff);
+                return(*status = 414);
+            }
+        }
+	
+	if (feof(diskfile))  break;
+
+        d_stream.next_out = (unsigned char*) (*buffptr + d_stream.total_out);
+        d_stream.avail_out = *buffsize - d_stream.total_out;
+    }
+
+    /* Set the output file size to be the total output data */
+    *filesize = d_stream.total_out;
+    
+    free(filebuff); /* free temporary output data buffer */
+    
+    err = inflateEnd(&d_stream); /* End the decompression */
+    if (err != Z_OK) return(*status = 414);
+  
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int uncompress2mem_from_mem(                                                
+             char *inmemptr,     /* I - memory pointer to compressed bytes */
+             size_t inmemsize,   /* I - size of input compressed file      */
+             char **buffptr,   /* IO - memory pointer                      */
+             size_t *buffsize,   /* IO - size of buffer, in bytes           */
+             void *(*mem_realloc)(void *p, size_t newsize), /* function     */
+             size_t *filesize,   /* O - size of file, in bytes              */
+             int *status)        /* IO - error status                       */
+
+/*
+  Uncompress the file in memory into memory.  Fill whatever amount of memory has
+  already been allocated, then realloc more memory, using the supplied
+  input function, if necessary.
+*/
+{
+    int err; 
+    z_stream d_stream;   /* decompression stream */
+
+    if (*status > 0) 
+        return(*status); 
+
+    d_stream.zalloc = (alloc_func)0;
+    d_stream.zfree = (free_func)0;
+    d_stream.opaque = (voidpf)0;
+
+    /* Initialize the decompression.  The argument (15+16) tells the
+       decompressor that we are to use the gzip algorithm */
+    err = inflateInit2(&d_stream, (15+16));
+    if (err != Z_OK) return(*status = 414);
+
+    d_stream.next_in = (unsigned char*)inmemptr;
+    d_stream.avail_in = inmemsize;
+
+    d_stream.next_out = (unsigned char*) *buffptr;
+    d_stream.avail_out = *buffsize;
+
+    for (;;) {
+        /* uncompress as much of the input as will fit in the output */
+        err = inflate(&d_stream, Z_NO_FLUSH);
+
+        if (err == Z_STREAM_END) { /* We reached the end of the input */
+	    break; 
+        } else if (err == Z_OK ) { /* need more space in output buffer */
+
+            if (mem_realloc) {   
+                *buffptr = mem_realloc(*buffptr,*buffsize + BUFFINCR);
+                if (*buffptr == NULL){
+                    inflateEnd(&d_stream);
+                    return(*status = 414);  /* memory allocation failed */
+                }
+
+                d_stream.avail_out = BUFFINCR;
+                d_stream.next_out = (unsigned char*) (*buffptr + *buffsize);
+                *buffsize = *buffsize + BUFFINCR;
+
+            } else  { /* error: no realloc function available */
+                inflateEnd(&d_stream);
+                return(*status = 414);
+            }
+        } else {  /* some other error */
+            inflateEnd(&d_stream);
+            return(*status = 414);
+        }
+    }
+
+    /* Set the output file size to be the total output data */
+    if (filesize) *filesize = d_stream.total_out;
+
+    /* End the decompression */
+    err = inflateEnd(&d_stream);
+
+    if (err != Z_OK) return(*status = 414);
+    
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int uncompress2file(char *filename,  /* name of input file                  */
+             FILE *indiskfile,     /* I - input file pointer                */
+             FILE *outdiskfile,    /* I - output file pointer               */
+             int *status)        /* IO - error status                       */
+/*
+  Uncompress the file into another file. 
+*/
+{
+    int err, len;
+    unsigned long bytes_out = 0;
+    char *infilebuff, *outfilebuff;
+    z_stream d_stream;   /* decompression stream */
+
+    if (*status > 0) 
+        return(*status); 
+
+    /* Allocate buffers to hold compressed and uncompressed */
+    infilebuff = (char*)malloc(GZBUFSIZE);
+    if (!infilebuff) return(*status = 113); /* memory error */
+
+    outfilebuff = (char*)malloc(GZBUFSIZE);
+    if (!outfilebuff) return(*status = 113); /* memory error */
+
+    d_stream.zalloc = (alloc_func)0;
+    d_stream.zfree = (free_func)0;
+    d_stream.opaque = (voidpf)0;
+
+    d_stream.next_out = (unsigned char*) outfilebuff;
+    d_stream.avail_out = GZBUFSIZE;
+
+    /* Initialize the decompression.  The argument (15+16) tells the
+       decompressor that we are to use the gzip algorithm */
+
+    err = inflateInit2(&d_stream, (15+16));
+    if (err != Z_OK) return(*status = 414);
+
+    /* loop through the file, reading a buffer and uncompressing it */
+    for (;;)
+    {
+        len = fread(infilebuff, 1, GZBUFSIZE, indiskfile);
+	if (ferror(indiskfile)) {
+              inflateEnd(&d_stream);
+              free(infilebuff);
+              free(outfilebuff);
+              return(*status = 414);
+	}
+
+        if (len == 0) break;  /* no more data */
+
+        d_stream.next_in = (unsigned char*)infilebuff;
+        d_stream.avail_in = len;
+
+        for (;;) {
+            /* uncompress as much of the input as will fit in the output */
+            err = inflate(&d_stream, Z_NO_FLUSH);
+
+            if (err == Z_STREAM_END ) { /* We reached the end of the input */
+	        break; 
+            } else if (err == Z_OK ) { 
+
+                if (!d_stream.avail_in) break; /* need more input */
+		
+                /* flush out the full output buffer */
+                if ((int)fwrite(outfilebuff, 1, GZBUFSIZE, outdiskfile) != GZBUFSIZE) {
+                    inflateEnd(&d_stream);
+                    free(infilebuff);
+                    free(outfilebuff);
+                    return(*status = 414);
+                }
+                bytes_out += GZBUFSIZE;
+                d_stream.next_out = (unsigned char*) outfilebuff;
+                d_stream.avail_out = GZBUFSIZE;
+
+            } else {  /* some other error */
+                inflateEnd(&d_stream);
+                free(infilebuff);
+                free(outfilebuff);
+                return(*status = 414);
+            }
+        }
+	
+	if (feof(indiskfile))  break;
+    }
+
+    /* write out any remaining bytes in the buffer */
+    if (d_stream.total_out > bytes_out) {
+        if ((int)fwrite(outfilebuff, 1, (d_stream.total_out - bytes_out), outdiskfile) 
+	    != (d_stream.total_out - bytes_out)) {
+            inflateEnd(&d_stream);
+            free(infilebuff);
+            free(outfilebuff);
+            return(*status = 414);
+        }
+    }
+
+    free(infilebuff); /* free temporary output data buffer */
+    free(outfilebuff); /* free temporary output data buffer */
+
+    err = inflateEnd(&d_stream); /* End the decompression */
+    if (err != Z_OK) return(*status = 414);
+  
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int compress2mem_from_mem(                                                
+             char *inmemptr,     /* I - memory pointer to uncompressed bytes */
+             size_t inmemsize,   /* I - size of input uncompressed file      */
+             char **buffptr,   /* IO - memory pointer for compressed file    */
+             size_t *buffsize,   /* IO - size of buffer, in bytes           */
+             void *(*mem_realloc)(void *p, size_t newsize), /* function     */
+             size_t *filesize,   /* O - size of file, in bytes              */
+             int *status)        /* IO - error status                       */
+
+/*
+  Compress the file into memory.  Fill whatever amount of memory has
+  already been allocated, then realloc more memory, using the supplied
+  input function, if necessary.
+*/
+{
+    int err;
+    z_stream c_stream;  /* compression stream */
+
+    if (*status > 0)
+        return(*status);
+
+    c_stream.zalloc = (alloc_func)0;
+    c_stream.zfree = (free_func)0;
+    c_stream.opaque = (voidpf)0;
+
+    /* Initialize the compression.  The argument (15+16) tells the 
+       compressor that we are to use the gzip algorythm.
+       Also use Z_BEST_SPEED for maximum speed with very minor loss
+       in compression factor. */
+    err = deflateInit2(&c_stream, Z_BEST_SPEED, Z_DEFLATED,
+                       (15+16), 8, Z_DEFAULT_STRATEGY);
+
+    if (err != Z_OK) return(*status = 413);
+
+    c_stream.next_in = (unsigned char*)inmemptr;
+    c_stream.avail_in = inmemsize;
+
+    c_stream.next_out = (unsigned char*) *buffptr;
+    c_stream.avail_out = *buffsize;
+
+    for (;;) {
+        /* compress as much of the input as will fit in the output */
+        err = deflate(&c_stream, Z_FINISH);
+
+        if (err == Z_STREAM_END) {  /* We reached the end of the input */
+	   break;
+        } else if (err == Z_OK ) { /* need more space in output buffer */
+
+            if (mem_realloc) {   
+                *buffptr = mem_realloc(*buffptr,*buffsize + BUFFINCR);
+                if (*buffptr == NULL){
+                    deflateEnd(&c_stream);
+                    return(*status = 413);  /* memory allocation failed */
+                }
+
+                c_stream.avail_out = BUFFINCR;
+                c_stream.next_out = (unsigned char*) (*buffptr + *buffsize);
+                *buffsize = *buffsize + BUFFINCR;
+
+            } else  { /* error: no realloc function available */
+                deflateEnd(&c_stream);
+                return(*status = 413);
+            }
+        } else {  /* some other error */
+            deflateEnd(&c_stream);
+            return(*status = 413);
+        }
+    }
+
+    /* Set the output file size to be the total output data */
+    if (filesize) *filesize = c_stream.total_out;
+
+    /* End the compression */
+    err = deflateEnd(&c_stream);
+
+    if (err != Z_OK) return(*status = 413);
+     
+    return(*status);
+}
+/*--------------------------------------------------------------------------*/
+int compress2file_from_mem(                                                
+             char *inmemptr,     /* I - memory pointer to uncompressed bytes */
+             size_t inmemsize,   /* I - size of input uncompressed file      */
+             FILE *outdiskfile, 
+             size_t *filesize,   /* O - size of file, in bytes              */
+             int *status)
+
+/*
+  Compress the memory file into disk file. 
+*/
+{
+    int err;
+    unsigned long bytes_out = 0;
+    char  *outfilebuff;
+    z_stream c_stream;  /* compression stream */
+
+    if (*status > 0)
+        return(*status);
+
+    /* Allocate buffer to hold compressed bytes */
+    outfilebuff = (char*)malloc(GZBUFSIZE);
+    if (!outfilebuff) return(*status = 113); /* memory error */
+
+    c_stream.zalloc = (alloc_func)0;
+    c_stream.zfree = (free_func)0;
+    c_stream.opaque = (voidpf)0;
+
+    /* Initialize the compression.  The argument (15+16) tells the 
+       compressor that we are to use the gzip algorythm.
+       Also use Z_BEST_SPEED for maximum speed with very minor loss
+       in compression factor. */
+    err = deflateInit2(&c_stream, Z_BEST_SPEED, Z_DEFLATED,
+                       (15+16), 8, Z_DEFAULT_STRATEGY);
+
+    if (err != Z_OK) return(*status = 413);
+
+    c_stream.next_in = (unsigned char*)inmemptr;
+    c_stream.avail_in = inmemsize;
+
+    c_stream.next_out = (unsigned char*) outfilebuff;
+    c_stream.avail_out = GZBUFSIZE;
+
+    for (;;) {
+        /* compress as much of the input as will fit in the output */
+        err = deflate(&c_stream, Z_FINISH);
+
+        if (err == Z_STREAM_END) {  /* We reached the end of the input */
+	   break;
+        } else if (err == Z_OK ) { /* need more space in output buffer */
+
+            /* flush out the full output buffer */
+            if ((int)fwrite(outfilebuff, 1, GZBUFSIZE, outdiskfile) != GZBUFSIZE) {
+                deflateEnd(&c_stream);
+                free(outfilebuff);
+                return(*status = 413);
+            }
+            bytes_out += GZBUFSIZE;
+            c_stream.next_out = (unsigned char*) outfilebuff;
+            c_stream.avail_out = GZBUFSIZE;
+
+
+        } else {  /* some other error */
+            deflateEnd(&c_stream);
+            free(outfilebuff);
+            return(*status = 413);
+        }
+    }
+
+    /* write out any remaining bytes in the buffer */
+    if (c_stream.total_out > bytes_out) {
+        if ((int)fwrite(outfilebuff, 1, (c_stream.total_out - bytes_out), outdiskfile) 
+	    != (c_stream.total_out - bytes_out)) {
+            deflateEnd(&c_stream);
+            free(outfilebuff);
+            return(*status = 413);
+        }
+    }
+
+    free(outfilebuff); /* free temporary output data buffer */
+
+    /* Set the output file size to be the total output data */
+    if (filesize) *filesize = c_stream.total_out;
+
+    /* End the compression */
+    err = deflateEnd(&c_stream);
+
+    if (err != Z_OK) return(*status = 413);
+     
+    return(*status);
+}
diff --git a/src/plugins/cfitsio/zconf.h b/src/plugins/cfitsio/zconf.h
new file mode 100644
index 0000000..142c330
--- /dev/null
+++ b/src/plugins/cfitsio/zconf.h
@@ -0,0 +1,426 @@
+/* zconf.h -- configuration of the zlib compression library
+ * Copyright (C) 1995-2010 Jean-loup Gailly.
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+#ifndef ZCONF_H
+#define ZCONF_H
+
+/*
+ * If you *really* need a unique prefix for all types and library functions,
+ * compile with -DZ_PREFIX. The "standard" zlib should be compiled without it.
+ * Even better than compiling with -DZ_PREFIX would be to use configure to set
+ * this permanently in zconf.h using "./configure --zprefix".
+ */
+#ifdef Z_PREFIX     /* may be set to #if 1 by ./configure */
+
+/* all linked symbols */
+#  define _dist_code            z__dist_code
+#  define _length_code          z__length_code
+#  define _tr_align             z__tr_align
+#  define _tr_flush_block       z__tr_flush_block
+#  define _tr_init              z__tr_init
+#  define _tr_stored_block      z__tr_stored_block
+#  define _tr_tally             z__tr_tally
+#  define adler32               z_adler32
+#  define adler32_combine       z_adler32_combine
+#  define adler32_combine64     z_adler32_combine64
+#  define compress              z_compress
+#  define compress2             z_compress2
+#  define compressBound         z_compressBound
+#  define crc32                 z_crc32
+#  define crc32_combine         z_crc32_combine
+#  define crc32_combine64       z_crc32_combine64
+#  define deflate               z_deflate
+#  define deflateBound          z_deflateBound
+#  define deflateCopy           z_deflateCopy
+#  define deflateEnd            z_deflateEnd
+#  define deflateInit2_         z_deflateInit2_
+#  define deflateInit_          z_deflateInit_
+#  define deflateParams         z_deflateParams
+#  define deflatePrime          z_deflatePrime
+#  define deflateReset          z_deflateReset
+#  define deflateSetDictionary  z_deflateSetDictionary
+#  define deflateSetHeader      z_deflateSetHeader
+#  define deflateTune           z_deflateTune
+#  define deflate_copyright     z_deflate_copyright
+#  define get_crc_table         z_get_crc_table
+#  define gz_error              z_gz_error
+#  define gz_intmax             z_gz_intmax
+#  define gz_strwinerror        z_gz_strwinerror
+#  define gzbuffer              z_gzbuffer
+#  define gzclearerr            z_gzclearerr
+#  define gzclose               z_gzclose
+#  define gzclose_r             z_gzclose_r
+#  define gzclose_w             z_gzclose_w
+#  define gzdirect              z_gzdirect
+#  define gzdopen               z_gzdopen
+#  define gzeof                 z_gzeof
+#  define gzerror               z_gzerror
+#  define gzflush               z_gzflush
+#  define gzgetc                z_gzgetc
+#  define gzgets                z_gzgets
+#  define gzoffset              z_gzoffset
+#  define gzoffset64            z_gzoffset64
+#  define gzopen                z_gzopen
+#  define gzopen64              z_gzopen64
+#  define gzprintf              z_gzprintf
+#  define gzputc                z_gzputc
+#  define gzputs                z_gzputs
+#  define gzread                z_gzread
+#  define gzrewind              z_gzrewind
+#  define gzseek                z_gzseek
+#  define gzseek64              z_gzseek64
+#  define gzsetparams           z_gzsetparams
+#  define gztell                z_gztell
+#  define gztell64              z_gztell64
+#  define gzungetc              z_gzungetc
+#  define gzwrite               z_gzwrite
+#  define inflate               z_inflate
+#  define inflateBack           z_inflateBack
+#  define inflateBackEnd        z_inflateBackEnd
+#  define inflateBackInit_      z_inflateBackInit_
+#  define inflateCopy           z_inflateCopy
+#  define inflateEnd            z_inflateEnd
+#  define inflateGetHeader      z_inflateGetHeader
+#  define inflateInit2_         z_inflateInit2_
+#  define inflateInit_          z_inflateInit_
+#  define inflateMark           z_inflateMark
+#  define inflatePrime          z_inflatePrime
+#  define inflateReset          z_inflateReset
+#  define inflateReset2         z_inflateReset2
+#  define inflateSetDictionary  z_inflateSetDictionary
+#  define inflateSync           z_inflateSync
+#  define inflateSyncPoint      z_inflateSyncPoint
+#  define inflateUndermine      z_inflateUndermine
+#  define inflate_copyright     z_inflate_copyright
+#  define inflate_fast          z_inflate_fast
+#  define inflate_table         z_inflate_table
+#  define uncompress            z_uncompress
+#  define zError                z_zError
+#  define zcalloc               z_zcalloc
+#  define zcfree                z_zcfree
+#  define zlibCompileFlags      z_zlibCompileFlags
+#  define zlibVersion           z_zlibVersion
+
+/* all zlib typedefs in zlib.h and zconf.h */
+#  define Byte                  z_Byte
+#  define Bytef                 z_Bytef
+#  define alloc_func            z_alloc_func
+#  define charf                 z_charf
+#  define free_func             z_free_func
+#  define gzFile                z_gzFile
+#  define gz_header             z_gz_header
+#  define gz_headerp            z_gz_headerp
+#  define in_func               z_in_func
+#  define intf                  z_intf
+#  define out_func              z_out_func
+#  define uInt                  z_uInt
+#  define uIntf                 z_uIntf
+#  define uLong                 z_uLong
+#  define uLongf                z_uLongf
+#  define voidp                 z_voidp
+#  define voidpc                z_voidpc
+#  define voidpf                z_voidpf
+
+/* all zlib structs in zlib.h and zconf.h */
+#  define gz_header_s           z_gz_header_s
+#  define internal_state        z_internal_state
+
+#endif
+
+#if defined(__MSDOS__) && !defined(MSDOS)
+#  define MSDOS
+#endif
+#if (defined(OS_2) || defined(__OS2__)) && !defined(OS2)
+#  define OS2
+#endif
+#if defined(_WINDOWS) && !defined(WINDOWS)
+#  define WINDOWS
+#endif
+#if defined(_WIN32) || defined(_WIN32_WCE) || defined(__WIN32__)
+#  ifndef WIN32
+#    define WIN32
+#  endif
+#endif
+#if (defined(MSDOS) || defined(OS2) || defined(WINDOWS)) && !defined(WIN32)
+#  if !defined(__GNUC__) && !defined(__FLAT__) && !defined(__386__)
+#    ifndef SYS16BIT
+#      define SYS16BIT
+#    endif
+#  endif
+#endif
+
+/*
+ * Compile with -DMAXSEG_64K if the alloc function cannot allocate more
+ * than 64k bytes at a time (needed on systems with 16-bit int).
+ */
+#ifdef SYS16BIT
+#  define MAXSEG_64K
+#endif
+#ifdef MSDOS
+#  define UNALIGNED_OK
+#endif
+
+#ifdef __STDC_VERSION__
+#  ifndef STDC
+#    define STDC
+#  endif
+#  if __STDC_VERSION__ >= 199901L
+#    ifndef STDC99
+#      define STDC99
+#    endif
+#  endif
+#endif
+#if !defined(STDC) && (defined(__STDC__) || defined(__cplusplus))
+#  define STDC
+#endif
+#if !defined(STDC) && (defined(__GNUC__) || defined(__BORLANDC__))
+#  define STDC
+#endif
+#if !defined(STDC) && (defined(MSDOS) || defined(WINDOWS) || defined(WIN32))
+#  define STDC
+#endif
+#if !defined(STDC) && (defined(OS2) || defined(__HOS_AIX__))
+#  define STDC
+#endif
+
+#if defined(__OS400__) && !defined(STDC)    /* iSeries (formerly AS/400). */
+#  define STDC
+#endif
+
+#ifndef STDC
+#  ifndef const /* cannot use !defined(STDC) && !defined(const) on Mac */
+#    define const       /* note: need a more gentle solution here */
+#  endif
+#endif
+
+/* Some Mac compilers merge all .h files incorrectly: */
+#if defined(__MWERKS__)||defined(applec)||defined(THINK_C)||defined(__SC__)
+#  define NO_DUMMY_DECL
+#endif
+
+/* Maximum value for memLevel in deflateInit2 */
+#ifndef MAX_MEM_LEVEL
+#  ifdef MAXSEG_64K
+#    define MAX_MEM_LEVEL 8
+#  else
+#    define MAX_MEM_LEVEL 9
+#  endif
+#endif
+
+/* Maximum value for windowBits in deflateInit2 and inflateInit2.
+ * WARNING: reducing MAX_WBITS makes minigzip unable to extract .gz files
+ * created by gzip. (Files created by minigzip can still be extracted by
+ * gzip.)
+ */
+#ifndef MAX_WBITS
+#  define MAX_WBITS   15 /* 32K LZ77 window */
+#endif
+
+/* The memory requirements for deflate are (in bytes):
+            (1 << (windowBits+2)) +  (1 << (memLevel+9))
+ that is: 128K for windowBits=15  +  128K for memLevel = 8  (default values)
+ plus a few kilobytes for small objects. For example, if you want to reduce
+ the default memory requirements from 256K to 128K, compile with
+     make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7"
+ Of course this will generally degrade compression (there's no free lunch).
+
+   The memory requirements for inflate are (in bytes) 1 << windowBits
+ that is, 32K for windowBits=15 (default value) plus a few kilobytes
+ for small objects.
+*/
+
+                        /* Type declarations */
+
+#ifndef OF /* function prototypes */
+#  ifdef STDC
+#    define OF(args)  args
+#  else
+#    define OF(args)  ()
+#  endif
+#endif
+
+/* The following definitions for FAR are needed only for MSDOS mixed
+ * model programming (small or medium model with some far allocations).
+ * This was tested only with MSC; for other MSDOS compilers you may have
+ * to define NO_MEMCPY in zutil.h.  If you don't need the mixed model,
+ * just define FAR to be empty.
+ */
+#ifdef SYS16BIT
+#  if defined(M_I86SM) || defined(M_I86MM)
+     /* MSC small or medium model */
+#    define SMALL_MEDIUM
+#    ifdef _MSC_VER
+#      define FAR _far
+#    else
+#      define FAR far
+#    endif
+#  endif
+#  if (defined(__SMALL__) || defined(__MEDIUM__))
+     /* Turbo C small or medium model */
+#    define SMALL_MEDIUM
+#    ifdef __BORLANDC__
+#      define FAR _far
+#    else
+#      define FAR far
+#    endif
+#  endif
+#endif
+
+#if defined(WINDOWS) || defined(WIN32)
+   /* If building or using zlib as a DLL, define ZLIB_DLL.
+    * This is not mandatory, but it offers a little performance increase.
+    */
+#  ifdef ZLIB_DLL
+#    if defined(WIN32) && (!defined(__BORLANDC__) || (__BORLANDC__ >= 0x500))
+#      ifdef ZLIB_INTERNAL
+#        define ZEXTERN extern __declspec(dllexport)
+#      else
+#        define ZEXTERN extern __declspec(dllimport)
+#      endif
+#    endif
+#  endif  /* ZLIB_DLL */
+   /* If building or using zlib with the WINAPI/WINAPIV calling convention,
+    * define ZLIB_WINAPI.
+    * Caution: the standard ZLIB1.DLL is NOT compiled using ZLIB_WINAPI.
+    */
+#  ifdef ZLIB_WINAPI
+#    ifdef FAR
+#      undef FAR
+#    endif
+#    include <windows.h>
+     /* No need for _export, use ZLIB.DEF instead. */
+     /* For complete Windows compatibility, use WINAPI, not __stdcall. */
+#    define ZEXPORT WINAPI
+#    ifdef WIN32
+#      define ZEXPORTVA WINAPIV
+#    else
+#      define ZEXPORTVA FAR CDECL
+#    endif
+#  endif
+#endif
+
+#if defined (__BEOS__)
+#  ifdef ZLIB_DLL
+#    ifdef ZLIB_INTERNAL
+#      define ZEXPORT   __declspec(dllexport)
+#      define ZEXPORTVA __declspec(dllexport)
+#    else
+#      define ZEXPORT   __declspec(dllimport)
+#      define ZEXPORTVA __declspec(dllimport)
+#    endif
+#  endif
+#endif
+
+#ifndef ZEXTERN
+#  define ZEXTERN extern
+#endif
+#ifndef ZEXPORT
+#  define ZEXPORT
+#endif
+#ifndef ZEXPORTVA
+#  define ZEXPORTVA
+#endif
+
+#ifndef FAR
+#  define FAR
+#endif
+
+#if !defined(__MACTYPES__)
+typedef unsigned char  Byte;  /* 8 bits */
+#endif
+typedef unsigned int   uInt;  /* 16 bits or more */
+typedef unsigned long  uLong; /* 32 bits or more */
+
+#ifdef SMALL_MEDIUM
+   /* Borland C/C++ and some old MSC versions ignore FAR inside typedef */
+#  define Bytef Byte FAR
+#else
+   typedef Byte  FAR Bytef;
+#endif
+typedef char  FAR charf;
+typedef int   FAR intf;
+typedef uInt  FAR uIntf;
+typedef uLong FAR uLongf;
+
+#ifdef STDC
+   typedef void const *voidpc;
+   typedef void FAR   *voidpf;
+   typedef void       *voidp;
+#else
+   typedef Byte const *voidpc;
+   typedef Byte FAR   *voidpf;
+   typedef Byte       *voidp;
+#endif
+
+#if !defined(MSDOS) && !defined(WINDOWS) && !defined(WIN32)
+#  define Z_HAVE_UNISTD_H
+#endif
+
+#ifdef STDC
+#  include <sys/types.h>    /* for off_t */
+#endif
+
+/* a little trick to accommodate both "#define _LARGEFILE64_SOURCE" and
+ * "#define _LARGEFILE64_SOURCE 1" as requesting 64-bit operations, (even
+ * though the former does not conform to the LFS document), but considering
+ * both "#undef _LARGEFILE64_SOURCE" and "#define _LARGEFILE64_SOURCE 0" as
+ * equivalently requesting no 64-bit operations
+ */
+#if -_LARGEFILE64_SOURCE - -1 == 1
+#  undef _LARGEFILE64_SOURCE
+#endif
+
+#if defined(Z_HAVE_UNISTD_H) || defined(_LARGEFILE64_SOURCE)
+#  include <unistd.h>       /* for SEEK_* and off_t */
+#  ifdef VMS
+#    include <unixio.h>     /* for off_t */
+#  endif
+#  ifndef z_off_t
+#    define z_off_t off_t
+#  endif
+#endif
+
+#ifndef SEEK_SET
+#  define SEEK_SET        0       /* Seek from beginning of file.  */
+#  define SEEK_CUR        1       /* Seek from current position.  */
+#  define SEEK_END        2       /* Set file pointer to EOF plus "offset" */
+#endif
+
+#ifndef z_off_t
+#  define z_off_t long
+#endif
+
+#if defined(_LARGEFILE64_SOURCE) && _LFS64_LARGEFILE-0
+#  define z_off64_t off64_t
+#else
+#  define z_off64_t z_off_t
+#endif
+
+#if defined(__OS400__)
+#  define NO_vsnprintf
+#endif
+
+#if defined(__MVS__)
+#  define NO_vsnprintf
+#endif
+
+/* MVS linker does not support external names larger than 8 bytes */
+#if defined(__MVS__)
+  #pragma map(deflateInit_,"DEIN")
+  #pragma map(deflateInit2_,"DEIN2")
+  #pragma map(deflateEnd,"DEEND")
+  #pragma map(deflateBound,"DEBND")
+  #pragma map(inflateInit_,"ININ")
+  #pragma map(inflateInit2_,"ININ2")
+  #pragma map(inflateEnd,"INEND")
+  #pragma map(inflateSync,"INSY")
+  #pragma map(inflateSetDictionary,"INSEDI")
+  #pragma map(compressBound,"CMBND")
+  #pragma map(inflate_table,"INTABL")
+  #pragma map(inflate_fast,"INFA")
+  #pragma map(inflate_copyright,"INCOPY")
+#endif
+
+#endif /* ZCONF_H */
diff --git a/src/plugins/cfitsio/zlib.h b/src/plugins/cfitsio/zlib.h
new file mode 100644
index 0000000..bfbba83
--- /dev/null
+++ b/src/plugins/cfitsio/zlib.h
@@ -0,0 +1,1613 @@
+/* zlib.h -- interface of the 'zlib' general purpose compression library
+  version 1.2.5, April 19th, 2010
+
+  Copyright (C) 1995-2010 Jean-loup Gailly and Mark Adler
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+
+  Jean-loup Gailly        Mark Adler
+  jloup gzip org          madler alumni caltech edu
+
+
+  The data format used by the zlib library is described by RFCs (Request for
+  Comments) 1950 to 1952 in the files http://www.ietf.org/rfc/rfc1950.txt
+  (zlib format), rfc1951.txt (deflate format) and rfc1952.txt (gzip format).
+*/
+
+#ifndef ZLIB_H
+#define ZLIB_H
+
+#include "zconf.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define ZLIB_VERSION "1.2.5"
+#define ZLIB_VERNUM 0x1250
+#define ZLIB_VER_MAJOR 1
+#define ZLIB_VER_MINOR 2
+#define ZLIB_VER_REVISION 5
+#define ZLIB_VER_SUBREVISION 0
+
+/*
+    The 'zlib' compression library provides in-memory compression and
+  decompression functions, including integrity checks of the uncompressed data.
+  This version of the library supports only one compression method (deflation)
+  but other algorithms will be added later and will have the same stream
+  interface.
+
+    Compression can be done in a single step if the buffers are large enough,
+  or can be done by repeated calls of the compression function.  In the latter
+  case, the application must provide more input and/or consume the output
+  (providing more output space) before each call.
+
+    The compressed data format used by default by the in-memory functions is
+  the zlib format, which is a zlib wrapper documented in RFC 1950, wrapped
+  around a deflate stream, which is itself documented in RFC 1951.
+
+    The library also supports reading and writing files in gzip (.gz) format
+  with an interface similar to that of stdio using the functions that start
+  with "gz".  The gzip format is different from the zlib format.  gzip is a
+  gzip wrapper, documented in RFC 1952, wrapped around a deflate stream.
+
+    This library can optionally read and write gzip streams in memory as well.
+
+    The zlib format was designed to be compact and fast for use in memory
+  and on communications channels.  The gzip format was designed for single-
+  file compression on file systems, has a larger header than zlib to maintain
+  directory information, and uses a different, slower check method than zlib.
+
+    The library does not install any signal handler.  The decoder checks
+  the consistency of the compressed data, so the library should never crash
+  even in case of corrupted input.
+*/
+
+typedef voidpf (*alloc_func) OF((voidpf opaque, uInt items, uInt size));
+typedef void   (*free_func)  OF((voidpf opaque, voidpf address));
+
+struct internal_state;
+
+typedef struct z_stream_s {
+    Bytef    *next_in;  /* next input byte */
+    uInt     avail_in;  /* number of bytes available at next_in */
+    uLong    total_in;  /* total nb of input bytes read so far */
+
+    Bytef    *next_out; /* next output byte should be put there */
+    uInt     avail_out; /* remaining free space at next_out */
+    uLong    total_out; /* total nb of bytes output so far */
+
+    char     *msg;      /* last error message, NULL if no error */
+    struct internal_state FAR *state; /* not visible by applications */
+
+    alloc_func zalloc;  /* used to allocate the internal state */
+    free_func  zfree;   /* used to free the internal state */
+    voidpf     opaque;  /* private data object passed to zalloc and zfree */
+
+    int     data_type;  /* best guess about the data type: binary or text */
+    uLong   adler;      /* adler32 value of the uncompressed data */
+    uLong   reserved;   /* reserved for future use */
+} z_stream;
+
+typedef z_stream FAR *z_streamp;
+
+/*
+     gzip header information passed to and from zlib routines.  See RFC 1952
+  for more details on the meanings of these fields.
+*/
+typedef struct gz_header_s {
+    int     text;       /* true if compressed data believed to be text */
+    uLong   time;       /* modification time */
+    int     xflags;     /* extra flags (not used when writing a gzip file) */
+    int     os;         /* operating system */
+    Bytef   *extra;     /* pointer to extra field or Z_NULL if none */
+    uInt    extra_len;  /* extra field length (valid if extra != Z_NULL) */
+    uInt    extra_max;  /* space at extra (only when reading header) */
+    Bytef   *name;      /* pointer to zero-terminated file name or Z_NULL */
+    uInt    name_max;   /* space at name (only when reading header) */
+    Bytef   *comment;   /* pointer to zero-terminated comment or Z_NULL */
+    uInt    comm_max;   /* space at comment (only when reading header) */
+    int     hcrc;       /* true if there was or will be a header crc */
+    int     done;       /* true when done reading gzip header (not used
+                           when writing a gzip file) */
+} gz_header;
+
+typedef gz_header FAR *gz_headerp;
+
+/*
+     The application must update next_in and avail_in when avail_in has dropped
+   to zero.  It must update next_out and avail_out when avail_out has dropped
+   to zero.  The application must initialize zalloc, zfree and opaque before
+   calling the init function.  All other fields are set by the compression
+   library and must not be updated by the application.
+
+     The opaque value provided by the application will be passed as the first
+   parameter for calls of zalloc and zfree.  This can be useful for custom
+   memory management.  The compression library attaches no meaning to the
+   opaque value.
+
+     zalloc must return Z_NULL if there is not enough memory for the object.
+   If zlib is used in a multi-threaded application, zalloc and zfree must be
+   thread safe.
+
+     On 16-bit systems, the functions zalloc and zfree must be able to allocate
+   exactly 65536 bytes, but will not be required to allocate more than this if
+   the symbol MAXSEG_64K is defined (see zconf.h).  WARNING: On MSDOS, pointers
+   returned by zalloc for objects of exactly 65536 bytes *must* have their
+   offset normalized to zero.  The default allocation function provided by this
+   library ensures this (see zutil.c).  To reduce memory requirements and avoid
+   any allocation of 64K objects, at the expense of compression ratio, compile
+   the library with -DMAX_WBITS=14 (see zconf.h).
+
+     The fields total_in and total_out can be used for statistics or progress
+   reports.  After compression, total_in holds the total size of the
+   uncompressed data and may be saved for use in the decompressor (particularly
+   if the decompressor wants to decompress everything in a single step).
+*/
+
+                        /* constants */
+
+#define Z_NO_FLUSH      0
+#define Z_PARTIAL_FLUSH 1
+#define Z_SYNC_FLUSH    2
+#define Z_FULL_FLUSH    3
+#define Z_FINISH        4
+#define Z_BLOCK         5
+#define Z_TREES         6
+/* Allowed flush values; see deflate() and inflate() below for details */
+
+#define Z_OK            0
+#define Z_STREAM_END    1
+#define Z_NEED_DICT     2
+#define Z_ERRNO        (-1)
+#define Z_STREAM_ERROR (-2)
+#define Z_DATA_ERROR   (-3)
+#define Z_MEM_ERROR    (-4)
+#define Z_BUF_ERROR    (-5)
+#define Z_VERSION_ERROR (-6)
+/* Return codes for the compression/decompression functions. Negative values
+ * are errors, positive values are used for special but normal events.
+ */
+
+#define Z_NO_COMPRESSION         0
+#define Z_BEST_SPEED             1
+#define Z_BEST_COMPRESSION       9
+#define Z_DEFAULT_COMPRESSION  (-1)
+/* compression levels */
+
+#define Z_FILTERED            1
+#define Z_HUFFMAN_ONLY        2
+#define Z_RLE                 3
+#define Z_FIXED               4
+#define Z_DEFAULT_STRATEGY    0
+/* compression strategy; see deflateInit2() below for details */
+
+#define Z_BINARY   0
+#define Z_TEXT     1
+#define Z_ASCII    Z_TEXT   /* for compatibility with 1.2.2 and earlier */
+#define Z_UNKNOWN  2
+/* Possible values of the data_type field (though see inflate()) */
+
+#define Z_DEFLATED   8
+/* The deflate compression method (the only one supported in this version) */
+
+#define Z_NULL  0  /* for initializing zalloc, zfree, opaque */
+
+#define zlib_version zlibVersion()
+/* for compatibility with versions < 1.0.2 */
+
+
+                        /* basic functions */
+
+ZEXTERN const char * ZEXPORT zlibVersion OF((void));
+/* The application can compare zlibVersion and ZLIB_VERSION for consistency.
+   If the first character differs, the library code actually used is not
+   compatible with the zlib.h header file used by the application.  This check
+   is automatically made by deflateInit and inflateInit.
+ */
+
+/*
+ZEXTERN int ZEXPORT deflateInit OF((z_streamp strm, int level));
+
+     Initializes the internal stream state for compression.  The fields
+   zalloc, zfree and opaque must be initialized before by the caller.  If
+   zalloc and zfree are set to Z_NULL, deflateInit updates them to use default
+   allocation functions.
+
+     The compression level must be Z_DEFAULT_COMPRESSION, or between 0 and 9:
+   1 gives best speed, 9 gives best compression, 0 gives no compression at all
+   (the input data is simply copied a block at a time).  Z_DEFAULT_COMPRESSION
+   requests a default compromise between speed and compression (currently
+   equivalent to level 6).
+
+     deflateInit returns Z_OK if success, Z_MEM_ERROR if there was not enough
+   memory, Z_STREAM_ERROR if level is not a valid compression level, or
+   Z_VERSION_ERROR if the zlib library version (zlib_version) is incompatible
+   with the version assumed by the caller (ZLIB_VERSION).  msg is set to null
+   if there is no error message.  deflateInit does not perform any compression:
+   this will be done by deflate().
+*/
+
+
+ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush));
+/*
+    deflate compresses as much data as possible, and stops when the input
+  buffer becomes empty or the output buffer becomes full.  It may introduce
+  some output latency (reading input without producing any output) except when
+  forced to flush.
+
+    The detailed semantics are as follows.  deflate performs one or both of the
+  following actions:
+
+  - Compress more input starting at next_in and update next_in and avail_in
+    accordingly.  If not all input can be processed (because there is not
+    enough room in the output buffer), next_in and avail_in are updated and
+    processing will resume at this point for the next call of deflate().
+
+  - Provide more output starting at next_out and update next_out and avail_out
+    accordingly.  This action is forced if the parameter flush is non zero.
+    Forcing flush frequently degrades the compression ratio, so this parameter
+    should be set only when necessary (in interactive applications).  Some
+    output may be provided even if flush is not set.
+
+    Before the call of deflate(), the application should ensure that at least
+  one of the actions is possible, by providing more input and/or consuming more
+  output, and updating avail_in or avail_out accordingly; avail_out should
+  never be zero before the call.  The application can consume the compressed
+  output when it wants, for example when the output buffer is full (avail_out
+  == 0), or after each call of deflate().  If deflate returns Z_OK and with
+  zero avail_out, it must be called again after making room in the output
+  buffer because there might be more output pending.
+
+    Normally the parameter flush is set to Z_NO_FLUSH, which allows deflate to
+  decide how much data to accumulate before producing output, in order to
+  maximize compression.
+
+    If the parameter flush is set to Z_SYNC_FLUSH, all pending output is
+  flushed to the output buffer and the output is aligned on a byte boundary, so
+  that the decompressor can get all input data available so far.  (In
+  particular avail_in is zero after the call if enough output space has been
+  provided before the call.) Flushing may degrade compression for some
+  compression algorithms and so it should be used only when necessary.  This
+  completes the current deflate block and follows it with an empty stored block
+  that is three bits plus filler bits to the next byte, followed by four bytes
+  (00 00 ff ff).
+
+    If flush is set to Z_PARTIAL_FLUSH, all pending output is flushed to the
+  output buffer, but the output is not aligned to a byte boundary.  All of the
+  input data so far will be available to the decompressor, as for Z_SYNC_FLUSH.
+  This completes the current deflate block and follows it with an empty fixed
+  codes block that is 10 bits long.  This assures that enough bytes are output
+  in order for the decompressor to finish the block before the empty fixed code
+  block.
+
+    If flush is set to Z_BLOCK, a deflate block is completed and emitted, as
+  for Z_SYNC_FLUSH, but the output is not aligned on a byte boundary, and up to
+  seven bits of the current block are held to be written as the next byte after
+  the next deflate block is completed.  In this case, the decompressor may not
+  be provided enough bits at this point in order to complete decompression of
+  the data provided so far to the compressor.  It may need to wait for the next
+  block to be emitted.  This is for advanced applications that need to control
+  the emission of deflate blocks.
+
+    If flush is set to Z_FULL_FLUSH, all output is flushed as with
+  Z_SYNC_FLUSH, and the compression state is reset so that decompression can
+  restart from this point if previous compressed data has been damaged or if
+  random access is desired.  Using Z_FULL_FLUSH too often can seriously degrade
+  compression.
+
+    If deflate returns with avail_out == 0, this function must be called again
+  with the same value of the flush parameter and more output space (updated
+  avail_out), until the flush is complete (deflate returns with non-zero
+  avail_out).  In the case of a Z_FULL_FLUSH or Z_SYNC_FLUSH, make sure that
+  avail_out is greater than six to avoid repeated flush markers due to
+  avail_out == 0 on return.
+
+    If the parameter flush is set to Z_FINISH, pending input is processed,
+  pending output is flushed and deflate returns with Z_STREAM_END if there was
+  enough output space; if deflate returns with Z_OK, this function must be
+  called again with Z_FINISH and more output space (updated avail_out) but no
+  more input data, until it returns with Z_STREAM_END or an error.  After
+  deflate has returned Z_STREAM_END, the only possible operations on the stream
+  are deflateReset or deflateEnd.
+
+    Z_FINISH can be used immediately after deflateInit if all the compression
+  is to be done in a single step.  In this case, avail_out must be at least the
+  value returned by deflateBound (see below).  If deflate does not return
+  Z_STREAM_END, then it must be called again as described above.
+
+    deflate() sets strm->adler to the adler32 checksum of all input read
+  so far (that is, total_in bytes).
+
+    deflate() may update strm->data_type if it can make a good guess about
+  the input data type (Z_BINARY or Z_TEXT).  In doubt, the data is considered
+  binary.  This field is only for information purposes and does not affect the
+  compression algorithm in any manner.
+
+    deflate() returns Z_OK if some progress has been made (more input
+  processed or more output produced), Z_STREAM_END if all input has been
+  consumed and all output has been produced (only when flush is set to
+  Z_FINISH), Z_STREAM_ERROR if the stream state was inconsistent (for example
+  if next_in or next_out was Z_NULL), Z_BUF_ERROR if no progress is possible
+  (for example avail_in or avail_out was zero).  Note that Z_BUF_ERROR is not
+  fatal, and deflate() can be called again with more input and more output
+  space to continue compressing.
+*/
+
+
+ZEXTERN int ZEXPORT deflateEnd OF((z_streamp strm));
+/*
+     All dynamically allocated data structures for this stream are freed.
+   This function discards any unprocessed input and does not flush any pending
+   output.
+
+     deflateEnd returns Z_OK if success, Z_STREAM_ERROR if the
+   stream state was inconsistent, Z_DATA_ERROR if the stream was freed
+   prematurely (some input or output was discarded).  In the error case, msg
+   may be set but then points to a static string (which must not be
+   deallocated).
+*/
+
+
+/*
+ZEXTERN int ZEXPORT inflateInit OF((z_streamp strm));
+
+     Initializes the internal stream state for decompression.  The fields
+   next_in, avail_in, zalloc, zfree and opaque must be initialized before by
+   the caller.  If next_in is not Z_NULL and avail_in is large enough (the
+   exact value depends on the compression method), inflateInit determines the
+   compression method from the zlib header and allocates all data structures
+   accordingly; otherwise the allocation will be deferred to the first call of
+   inflate.  If zalloc and zfree are set to Z_NULL, inflateInit updates them to
+   use default allocation functions.
+
+     inflateInit returns Z_OK if success, Z_MEM_ERROR if there was not enough
+   memory, Z_VERSION_ERROR if the zlib library version is incompatible with the
+   version assumed by the caller, or Z_STREAM_ERROR if the parameters are
+   invalid, such as a null pointer to the structure.  msg is set to null if
+   there is no error message.  inflateInit does not perform any decompression
+   apart from possibly reading the zlib header if present: actual decompression
+   will be done by inflate().  (So next_in and avail_in may be modified, but
+   next_out and avail_out are unused and unchanged.) The current implementation
+   of inflateInit() does not process any header information -- that is deferred
+   until inflate() is called.
+*/
+
+
+ZEXTERN int ZEXPORT inflate OF((z_streamp strm, int flush));
+/*
+    inflate decompresses as much data as possible, and stops when the input
+  buffer becomes empty or the output buffer becomes full.  It may introduce
+  some output latency (reading input without producing any output) except when
+  forced to flush.
+
+  The detailed semantics are as follows.  inflate performs one or both of the
+  following actions:
+
+  - Decompress more input starting at next_in and update next_in and avail_in
+    accordingly.  If not all input can be processed (because there is not
+    enough room in the output buffer), next_in is updated and processing will
+    resume at this point for the next call of inflate().
+
+  - Provide more output starting at next_out and update next_out and avail_out
+    accordingly.  inflate() provides as much output as possible, until there is
+    no more input data or no more space in the output buffer (see below about
+    the flush parameter).
+
+    Before the call of inflate(), the application should ensure that at least
+  one of the actions is possible, by providing more input and/or consuming more
+  output, and updating the next_* and avail_* values accordingly.  The
+  application can consume the uncompressed output when it wants, for example
+  when the output buffer is full (avail_out == 0), or after each call of
+  inflate().  If inflate returns Z_OK and with zero avail_out, it must be
+  called again after making room in the output buffer because there might be
+  more output pending.
+
+    The flush parameter of inflate() can be Z_NO_FLUSH, Z_SYNC_FLUSH, Z_FINISH,
+  Z_BLOCK, or Z_TREES.  Z_SYNC_FLUSH requests that inflate() flush as much
+  output as possible to the output buffer.  Z_BLOCK requests that inflate()
+  stop if and when it gets to the next deflate block boundary.  When decoding
+  the zlib or gzip format, this will cause inflate() to return immediately
+  after the header and before the first block.  When doing a raw inflate,
+  inflate() will go ahead and process the first block, and will return when it
+  gets to the end of that block, or when it runs out of data.
+
+    The Z_BLOCK option assists in appending to or combining deflate streams.
+  Also to assist in this, on return inflate() will set strm->data_type to the
+  number of unused bits in the last byte taken from strm->next_in, plus 64 if
+  inflate() is currently decoding the last block in the deflate stream, plus
+  128 if inflate() returned immediately after decoding an end-of-block code or
+  decoding the complete header up to just before the first byte of the deflate
+  stream.  The end-of-block will not be indicated until all of the uncompressed
+  data from that block has been written to strm->next_out.  The number of
+  unused bits may in general be greater than seven, except when bit 7 of
+  data_type is set, in which case the number of unused bits will be less than
+  eight.  data_type is set as noted here every time inflate() returns for all
+  flush options, and so can be used to determine the amount of currently
+  consumed input in bits.
+
+    The Z_TREES option behaves as Z_BLOCK does, but it also returns when the
+  end of each deflate block header is reached, before any actual data in that
+  block is decoded.  This allows the caller to determine the length of the
+  deflate block header for later use in random access within a deflate block.
+  256 is added to the value of strm->data_type when inflate() returns
+  immediately after reaching the end of the deflate block header.
+
+    inflate() should normally be called until it returns Z_STREAM_END or an
+  error.  However if all decompression is to be performed in a single step (a
+  single call of inflate), the parameter flush should be set to Z_FINISH.  In
+  this case all pending input is processed and all pending output is flushed;
+  avail_out must be large enough to hold all the uncompressed data.  (The size
+  of the uncompressed data may have been saved by the compressor for this
+  purpose.) The next operation on this stream must be inflateEnd to deallocate
+  the decompression state.  The use of Z_FINISH is never required, but can be
+  used to inform inflate that a faster approach may be used for the single
+  inflate() call.
+
+     In this implementation, inflate() always flushes as much output as
+  possible to the output buffer, and always uses the faster approach on the
+  first call.  So the only effect of the flush parameter in this implementation
+  is on the return value of inflate(), as noted below, or when it returns early
+  because Z_BLOCK or Z_TREES is used.
+
+     If a preset dictionary is needed after this call (see inflateSetDictionary
+  below), inflate sets strm->adler to the adler32 checksum of the dictionary
+  chosen by the compressor and returns Z_NEED_DICT; otherwise it sets
+  strm->adler to the adler32 checksum of all output produced so far (that is,
+  total_out bytes) and returns Z_OK, Z_STREAM_END or an error code as described
+  below.  At the end of the stream, inflate() checks that its computed adler32
+  checksum is equal to that saved by the compressor and returns Z_STREAM_END
+  only if the checksum is correct.
+
+    inflate() can decompress and check either zlib-wrapped or gzip-wrapped
+  deflate data.  The header type is detected automatically, if requested when
+  initializing with inflateInit2().  Any information contained in the gzip
+  header is not retained, so applications that need that information should
+  instead use raw inflate, see inflateInit2() below, or inflateBack() and
+  perform their own processing of the gzip header and trailer.
+
+    inflate() returns Z_OK if some progress has been made (more input processed
+  or more output produced), Z_STREAM_END if the end of the compressed data has
+  been reached and all uncompressed output has been produced, Z_NEED_DICT if a
+  preset dictionary is needed at this point, Z_DATA_ERROR if the input data was
+  corrupted (input stream not conforming to the zlib format or incorrect check
+  value), Z_STREAM_ERROR if the stream structure was inconsistent (for example
+  next_in or next_out was Z_NULL), Z_MEM_ERROR if there was not enough memory,
+  Z_BUF_ERROR if no progress is possible or if there was not enough room in the
+  output buffer when Z_FINISH is used.  Note that Z_BUF_ERROR is not fatal, and
+  inflate() can be called again with more input and more output space to
+  continue decompressing.  If Z_DATA_ERROR is returned, the application may
+  then call inflateSync() to look for a good compression block if a partial
+  recovery of the data is desired.
+*/
+
+
+ZEXTERN int ZEXPORT inflateEnd OF((z_streamp strm));
+/*
+     All dynamically allocated data structures for this stream are freed.
+   This function discards any unprocessed input and does not flush any pending
+   output.
+
+     inflateEnd returns Z_OK if success, Z_STREAM_ERROR if the stream state
+   was inconsistent.  In the error case, msg may be set but then points to a
+   static string (which must not be deallocated).
+*/
+
+
+                        /* Advanced functions */
+
+/*
+    The following functions are needed only in some special applications.
+*/
+
+/*
+ZEXTERN int ZEXPORT deflateInit2 OF((z_streamp strm,
+                                     int  level,
+                                     int  method,
+                                     int  windowBits,
+                                     int  memLevel,
+                                     int  strategy));
+
+     This is another version of deflateInit with more compression options.  The
+   fields next_in, zalloc, zfree and opaque must be initialized before by the
+   caller.
+
+     The method parameter is the compression method.  It must be Z_DEFLATED in
+   this version of the library.
+
+     The windowBits parameter is the base two logarithm of the window size
+   (the size of the history buffer).  It should be in the range 8..15 for this
+   version of the library.  Larger values of this parameter result in better
+   compression at the expense of memory usage.  The default value is 15 if
+   deflateInit is used instead.
+
+     windowBits can also be -8..-15 for raw deflate.  In this case, -windowBits
+   determines the window size.  deflate() will then generate raw deflate data
+   with no zlib header or trailer, and will not compute an adler32 check value.
+
+     windowBits can also be greater than 15 for optional gzip encoding.  Add
+   16 to windowBits to write a simple gzip header and trailer around the
+   compressed data instead of a zlib wrapper.  The gzip header will have no
+   file name, no extra data, no comment, no modification time (set to zero), no
+   header crc, and the operating system will be set to 255 (unknown).  If a
+   gzip stream is being written, strm->adler is a crc32 instead of an adler32.
+
+     The memLevel parameter specifies how much memory should be allocated
+   for the internal compression state.  memLevel=1 uses minimum memory but is
+   slow and reduces compression ratio; memLevel=9 uses maximum memory for
+   optimal speed.  The default value is 8.  See zconf.h for total memory usage
+   as a function of windowBits and memLevel.
+
+     The strategy parameter is used to tune the compression algorithm.  Use the
+   value Z_DEFAULT_STRATEGY for normal data, Z_FILTERED for data produced by a
+   filter (or predictor), Z_HUFFMAN_ONLY to force Huffman encoding only (no
+   string match), or Z_RLE to limit match distances to one (run-length
+   encoding).  Filtered data consists mostly of small values with a somewhat
+   random distribution.  In this case, the compression algorithm is tuned to
+   compress them better.  The effect of Z_FILTERED is to force more Huffman
+   coding and less string matching; it is somewhat intermediate between
+   Z_DEFAULT_STRATEGY and Z_HUFFMAN_ONLY.  Z_RLE is designed to be almost as
+   fast as Z_HUFFMAN_ONLY, but give better compression for PNG image data.  The
+   strategy parameter only affects the compression ratio but not the
+   correctness of the compressed output even if it is not set appropriately.
+   Z_FIXED prevents the use of dynamic Huffman codes, allowing for a simpler
+   decoder for special applications.
+
+     deflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough
+   memory, Z_STREAM_ERROR if any parameter is invalid (such as an invalid
+   method), or Z_VERSION_ERROR if the zlib library version (zlib_version) is
+   incompatible with the version assumed by the caller (ZLIB_VERSION).  msg is
+   set to null if there is no error message.  deflateInit2 does not perform any
+   compression: this will be done by deflate().
+*/
+
+ZEXTERN int ZEXPORT deflateSetDictionary OF((z_streamp strm,
+                                             const Bytef *dictionary,
+                                             uInt  dictLength));
+/*
+     Initializes the compression dictionary from the given byte sequence
+   without producing any compressed output.  This function must be called
+   immediately after deflateInit, deflateInit2 or deflateReset, before any call
+   of deflate.  The compressor and decompressor must use exactly the same
+   dictionary (see inflateSetDictionary).
+
+     The dictionary should consist of strings (byte sequences) that are likely
+   to be encountered later in the data to be compressed, with the most commonly
+   used strings preferably put towards the end of the dictionary.  Using a
+   dictionary is most useful when the data to be compressed is short and can be
+   predicted with good accuracy; the data can then be compressed better than
+   with the default empty dictionary.
+
+     Depending on the size of the compression data structures selected by
+   deflateInit or deflateInit2, a part of the dictionary may in effect be
+   discarded, for example if the dictionary is larger than the window size
+   provided in deflateInit or deflateInit2.  Thus the strings most likely to be
+   useful should be put at the end of the dictionary, not at the front.  In
+   addition, the current implementation of deflate will use at most the window
+   size minus 262 bytes of the provided dictionary.
+
+     Upon return of this function, strm->adler is set to the adler32 value
+   of the dictionary; the decompressor may later use this value to determine
+   which dictionary has been used by the compressor.  (The adler32 value
+   applies to the whole dictionary even if only a subset of the dictionary is
+   actually used by the compressor.) If a raw deflate was requested, then the
+   adler32 value is not computed and strm->adler is not set.
+
+     deflateSetDictionary returns Z_OK if success, or Z_STREAM_ERROR if a
+   parameter is invalid (e.g.  dictionary being Z_NULL) or the stream state is
+   inconsistent (for example if deflate has already been called for this stream
+   or if the compression method is bsort).  deflateSetDictionary does not
+   perform any compression: this will be done by deflate().
+*/
+
+ZEXTERN int ZEXPORT deflateCopy OF((z_streamp dest,
+                                    z_streamp source));
+/*
+     Sets the destination stream as a complete copy of the source stream.
+
+     This function can be useful when several compression strategies will be
+   tried, for example when there are several ways of pre-processing the input
+   data with a filter.  The streams that will be discarded should then be freed
+   by calling deflateEnd.  Note that deflateCopy duplicates the internal
+   compression state which can be quite large, so this strategy is slow and can
+   consume lots of memory.
+
+     deflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not
+   enough memory, Z_STREAM_ERROR if the source stream state was inconsistent
+   (such as zalloc being Z_NULL).  msg is left unchanged in both source and
+   destination.
+*/
+
+ZEXTERN int ZEXPORT deflateReset OF((z_streamp strm));
+/*
+     This function is equivalent to deflateEnd followed by deflateInit,
+   but does not free and reallocate all the internal compression state.  The
+   stream will keep the same compression level and any other attributes that
+   may have been set by deflateInit2.
+
+     deflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source
+   stream state was inconsistent (such as zalloc or state being Z_NULL).
+*/
+
+ZEXTERN int ZEXPORT deflateParams OF((z_streamp strm,
+                                      int level,
+                                      int strategy));
+/*
+     Dynamically update the compression level and compression strategy.  The
+   interpretation of level and strategy is as in deflateInit2.  This can be
+   used to switch between compression and straight copy of the input data, or
+   to switch to a different kind of input data requiring a different strategy.
+   If the compression level is changed, the input available so far is
+   compressed with the old level (and may be flushed); the new level will take
+   effect only at the next call of deflate().
+
+     Before the call of deflateParams, the stream state must be set as for
+   a call of deflate(), since the currently available input may have to be
+   compressed and flushed.  In particular, strm->avail_out must be non-zero.
+
+     deflateParams returns Z_OK if success, Z_STREAM_ERROR if the source
+   stream state was inconsistent or if a parameter was invalid, Z_BUF_ERROR if
+   strm->avail_out was zero.
+*/
+
+ZEXTERN int ZEXPORT deflateTune OF((z_streamp strm,
+                                    int good_length,
+                                    int max_lazy,
+                                    int nice_length,
+                                    int max_chain));
+/*
+     Fine tune deflate's internal compression parameters.  This should only be
+   used by someone who understands the algorithm used by zlib's deflate for
+   searching for the best matching string, and even then only by the most
+   fanatic optimizer trying to squeeze out the last compressed bit for their
+   specific input data.  Read the deflate.c source code for the meaning of the
+   max_lazy, good_length, nice_length, and max_chain parameters.
+
+     deflateTune() can be called after deflateInit() or deflateInit2(), and
+   returns Z_OK on success, or Z_STREAM_ERROR for an invalid deflate stream.
+ */
+
+ZEXTERN uLong ZEXPORT deflateBound OF((z_streamp strm,
+                                       uLong sourceLen));
+/*
+     deflateBound() returns an upper bound on the compressed size after
+   deflation of sourceLen bytes.  It must be called after deflateInit() or
+   deflateInit2(), and after deflateSetHeader(), if used.  This would be used
+   to allocate an output buffer for deflation in a single pass, and so would be
+   called before deflate().
+*/
+
+ZEXTERN int ZEXPORT deflatePrime OF((z_streamp strm,
+                                     int bits,
+                                     int value));
+/*
+     deflatePrime() inserts bits in the deflate output stream.  The intent
+   is that this function is used to start off the deflate output with the bits
+   leftover from a previous deflate stream when appending to it.  As such, this
+   function can only be used for raw deflate, and must be used before the first
+   deflate() call after a deflateInit2() or deflateReset().  bits must be less
+   than or equal to 16, and that many of the least significant bits of value
+   will be inserted in the output.
+
+     deflatePrime returns Z_OK if success, or Z_STREAM_ERROR if the source
+   stream state was inconsistent.
+*/
+
+ZEXTERN int ZEXPORT deflateSetHeader OF((z_streamp strm,
+                                         gz_headerp head));
+/*
+     deflateSetHeader() provides gzip header information for when a gzip
+   stream is requested by deflateInit2().  deflateSetHeader() may be called
+   after deflateInit2() or deflateReset() and before the first call of
+   deflate().  The text, time, os, extra field, name, and comment information
+   in the provided gz_header structure are written to the gzip header (xflag is
+   ignored -- the extra flags are set according to the compression level).  The
+   caller must assure that, if not Z_NULL, name and comment are terminated with
+   a zero byte, and that if extra is not Z_NULL, that extra_len bytes are
+   available there.  If hcrc is true, a gzip header crc is included.  Note that
+   the current versions of the command-line version of gzip (up through version
+   1.3.x) do not support header crc's, and will report that it is a "multi-part
+   gzip file" and give up.
+
+     If deflateSetHeader is not used, the default gzip header has text false,
+   the time set to zero, and os set to 255, with no extra, name, or comment
+   fields.  The gzip header is returned to the default state by deflateReset().
+
+     deflateSetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source
+   stream state was inconsistent.
+*/
+
+/*
+ZEXTERN int ZEXPORT inflateInit2 OF((z_streamp strm,
+                                     int  windowBits));
+
+     This is another version of inflateInit with an extra parameter.  The
+   fields next_in, avail_in, zalloc, zfree and opaque must be initialized
+   before by the caller.
+
+     The windowBits parameter is the base two logarithm of the maximum window
+   size (the size of the history buffer).  It should be in the range 8..15 for
+   this version of the library.  The default value is 15 if inflateInit is used
+   instead.  windowBits must be greater than or equal to the windowBits value
+   provided to deflateInit2() while compressing, or it must be equal to 15 if
+   deflateInit2() was not used.  If a compressed stream with a larger window
+   size is given as input, inflate() will return with the error code
+   Z_DATA_ERROR instead of trying to allocate a larger window.
+
+     windowBits can also be zero to request that inflate use the window size in
+   the zlib header of the compressed stream.
+
+     windowBits can also be -8..-15 for raw inflate.  In this case, -windowBits
+   determines the window size.  inflate() will then process raw deflate data,
+   not looking for a zlib or gzip header, not generating a check value, and not
+   looking for any check values for comparison at the end of the stream.  This
+   is for use with other formats that use the deflate compressed data format
+   such as zip.  Those formats provide their own check values.  If a custom
+   format is developed using the raw deflate format for compressed data, it is
+   recommended that a check value such as an adler32 or a crc32 be applied to
+   the uncompressed data as is done in the zlib, gzip, and zip formats.  For
+   most applications, the zlib format should be used as is.  Note that comments
+   above on the use in deflateInit2() applies to the magnitude of windowBits.
+
+     windowBits can also be greater than 15 for optional gzip decoding.  Add
+   32 to windowBits to enable zlib and gzip decoding with automatic header
+   detection, or add 16 to decode only the gzip format (the zlib format will
+   return a Z_DATA_ERROR).  If a gzip stream is being decoded, strm->adler is a
+   crc32 instead of an adler32.
+
+     inflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough
+   memory, Z_VERSION_ERROR if the zlib library version is incompatible with the
+   version assumed by the caller, or Z_STREAM_ERROR if the parameters are
+   invalid, such as a null pointer to the structure.  msg is set to null if
+   there is no error message.  inflateInit2 does not perform any decompression
+   apart from possibly reading the zlib header if present: actual decompression
+   will be done by inflate().  (So next_in and avail_in may be modified, but
+   next_out and avail_out are unused and unchanged.) The current implementation
+   of inflateInit2() does not process any header information -- that is
+   deferred until inflate() is called.
+*/
+
+ZEXTERN int ZEXPORT inflateSetDictionary OF((z_streamp strm,
+                                             const Bytef *dictionary,
+                                             uInt  dictLength));
+/*
+     Initializes the decompression dictionary from the given uncompressed byte
+   sequence.  This function must be called immediately after a call of inflate,
+   if that call returned Z_NEED_DICT.  The dictionary chosen by the compressor
+   can be determined from the adler32 value returned by that call of inflate.
+   The compressor and decompressor must use exactly the same dictionary (see
+   deflateSetDictionary).  For raw inflate, this function can be called
+   immediately after inflateInit2() or inflateReset() and before any call of
+   inflate() to set the dictionary.  The application must insure that the
+   dictionary that was used for compression is provided.
+
+     inflateSetDictionary returns Z_OK if success, Z_STREAM_ERROR if a
+   parameter is invalid (e.g.  dictionary being Z_NULL) or the stream state is
+   inconsistent, Z_DATA_ERROR if the given dictionary doesn't match the
+   expected one (incorrect adler32 value).  inflateSetDictionary does not
+   perform any decompression: this will be done by subsequent calls of
+   inflate().
+*/
+
+ZEXTERN int ZEXPORT inflateSync OF((z_streamp strm));
+/*
+     Skips invalid compressed data until a full flush point (see above the
+   description of deflate with Z_FULL_FLUSH) can be found, or until all
+   available input is skipped.  No output is provided.
+
+     inflateSync returns Z_OK if a full flush point has been found, Z_BUF_ERROR
+   if no more input was provided, Z_DATA_ERROR if no flush point has been
+   found, or Z_STREAM_ERROR if the stream structure was inconsistent.  In the
+   success case, the application may save the current current value of total_in
+   which indicates where valid compressed data was found.  In the error case,
+   the application may repeatedly call inflateSync, providing more input each
+   time, until success or end of the input data.
+*/
+
+ZEXTERN int ZEXPORT inflateCopy OF((z_streamp dest,
+                                    z_streamp source));
+/*
+     Sets the destination stream as a complete copy of the source stream.
+
+     This function can be useful when randomly accessing a large stream.  The
+   first pass through the stream can periodically record the inflate state,
+   allowing restarting inflate at those points when randomly accessing the
+   stream.
+
+     inflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not
+   enough memory, Z_STREAM_ERROR if the source stream state was inconsistent
+   (such as zalloc being Z_NULL).  msg is left unchanged in both source and
+   destination.
+*/
+
+ZEXTERN int ZEXPORT inflateReset OF((z_streamp strm));
+/*
+     This function is equivalent to inflateEnd followed by inflateInit,
+   but does not free and reallocate all the internal decompression state.  The
+   stream will keep attributes that may have been set by inflateInit2.
+
+     inflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source
+   stream state was inconsistent (such as zalloc or state being Z_NULL).
+*/
+
+ZEXTERN int ZEXPORT inflateReset2 OF((z_streamp strm,
+                                      int windowBits));
+/*
+     This function is the same as inflateReset, but it also permits changing
+   the wrap and window size requests.  The windowBits parameter is interpreted
+   the same as it is for inflateInit2.
+
+     inflateReset2 returns Z_OK if success, or Z_STREAM_ERROR if the source
+   stream state was inconsistent (such as zalloc or state being Z_NULL), or if
+   the windowBits parameter is invalid.
+*/
+
+ZEXTERN int ZEXPORT inflatePrime OF((z_streamp strm,
+                                     int bits,
+                                     int value));
+/*
+     This function inserts bits in the inflate input stream.  The intent is
+   that this function is used to start inflating at a bit position in the
+   middle of a byte.  The provided bits will be used before any bytes are used
+   from next_in.  This function should only be used with raw inflate, and
+   should be used before the first inflate() call after inflateInit2() or
+   inflateReset().  bits must be less than or equal to 16, and that many of the
+   least significant bits of value will be inserted in the input.
+
+     If bits is negative, then the input stream bit buffer is emptied.  Then
+   inflatePrime() can be called again to put bits in the buffer.  This is used
+   to clear out bits leftover after feeding inflate a block description prior
+   to feeding inflate codes.
+
+     inflatePrime returns Z_OK if success, or Z_STREAM_ERROR if the source
+   stream state was inconsistent.
+*/
+
+ZEXTERN long ZEXPORT inflateMark OF((z_streamp strm));
+/*
+     This function returns two values, one in the lower 16 bits of the return
+   value, and the other in the remaining upper bits, obtained by shifting the
+   return value down 16 bits.  If the upper value is -1 and the lower value is
+   zero, then inflate() is currently decoding information outside of a block.
+   If the upper value is -1 and the lower value is non-zero, then inflate is in
+   the middle of a stored block, with the lower value equaling the number of
+   bytes from the input remaining to copy.  If the upper value is not -1, then
+   it is the number of bits back from the current bit position in the input of
+   the code (literal or length/distance pair) currently being processed.  In
+   that case the lower value is the number of bytes already emitted for that
+   code.
+
+     A code is being processed if inflate is waiting for more input to complete
+   decoding of the code, or if it has completed decoding but is waiting for
+   more output space to write the literal or match data.
+
+     inflateMark() is used to mark locations in the input data for random
+   access, which may be at bit positions, and to note those cases where the
+   output of a code may span boundaries of random access blocks.  The current
+   location in the input stream can be determined from avail_in and data_type
+   as noted in the description for the Z_BLOCK flush parameter for inflate.
+
+     inflateMark returns the value noted above or -1 << 16 if the provided
+   source stream state was inconsistent.
+*/
+
+ZEXTERN int ZEXPORT inflateGetHeader OF((z_streamp strm,
+                                         gz_headerp head));
+/*
+     inflateGetHeader() requests that gzip header information be stored in the
+   provided gz_header structure.  inflateGetHeader() may be called after
+   inflateInit2() or inflateReset(), and before the first call of inflate().
+   As inflate() processes the gzip stream, head->done is zero until the header
+   is completed, at which time head->done is set to one.  If a zlib stream is
+   being decoded, then head->done is set to -1 to indicate that there will be
+   no gzip header information forthcoming.  Note that Z_BLOCK or Z_TREES can be
+   used to force inflate() to return immediately after header processing is
+   complete and before any actual data is decompressed.
+
+     The text, time, xflags, and os fields are filled in with the gzip header
+   contents.  hcrc is set to true if there is a header CRC.  (The header CRC
+   was valid if done is set to one.) If extra is not Z_NULL, then extra_max
+   contains the maximum number of bytes to write to extra.  Once done is true,
+   extra_len contains the actual extra field length, and extra contains the
+   extra field, or that field truncated if extra_max is less than extra_len.
+   If name is not Z_NULL, then up to name_max characters are written there,
+   terminated with a zero unless the length is greater than name_max.  If
+   comment is not Z_NULL, then up to comm_max characters are written there,
+   terminated with a zero unless the length is greater than comm_max.  When any
+   of extra, name, or comment are not Z_NULL and the respective field is not
+   present in the header, then that field is set to Z_NULL to signal its
+   absence.  This allows the use of deflateSetHeader() with the returned
+   structure to duplicate the header.  However if those fields are set to
+   allocated memory, then the application will need to save those pointers
+   elsewhere so that they can be eventually freed.
+
+     If inflateGetHeader is not used, then the header information is simply
+   discarded.  The header is always checked for validity, including the header
+   CRC if present.  inflateReset() will reset the process to discard the header
+   information.  The application would need to call inflateGetHeader() again to
+   retrieve the header from the next gzip stream.
+
+     inflateGetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source
+   stream state was inconsistent.
+*/
+
+/*
+ZEXTERN int ZEXPORT inflateBackInit OF((z_streamp strm, int windowBits,
+                                        unsigned char FAR *window));
+
+     Initialize the internal stream state for decompression using inflateBack()
+   calls.  The fields zalloc, zfree and opaque in strm must be initialized
+   before the call.  If zalloc and zfree are Z_NULL, then the default library-
+   derived memory allocation routines are used.  windowBits is the base two
+   logarithm of the window size, in the range 8..15.  window is a caller
+   supplied buffer of that size.  Except for special applications where it is
+   assured that deflate was used with small window sizes, windowBits must be 15
+   and a 32K byte window must be supplied to be able to decompress general
+   deflate streams.
+
+     See inflateBack() for the usage of these routines.
+
+     inflateBackInit will return Z_OK on success, Z_STREAM_ERROR if any of
+   the paramaters are invalid, Z_MEM_ERROR if the internal state could not be
+   allocated, or Z_VERSION_ERROR if the version of the library does not match
+   the version of the header file.
+*/
+
+typedef unsigned (*in_func) OF((void FAR *, unsigned char FAR * FAR *));
+typedef int (*out_func) OF((void FAR *, unsigned char FAR *, unsigned));
+
+ZEXTERN int ZEXPORT inflateBack OF((z_streamp strm,
+                                    in_func in, void FAR *in_desc,
+                                    out_func out, void FAR *out_desc));
+/*
+     inflateBack() does a raw inflate with a single call using a call-back
+   interface for input and output.  This is more efficient than inflate() for
+   file i/o applications in that it avoids copying between the output and the
+   sliding window by simply making the window itself the output buffer.  This
+   function trusts the application to not change the output buffer passed by
+   the output function, at least until inflateBack() returns.
+
+     inflateBackInit() must be called first to allocate the internal state
+   and to initialize the state with the user-provided window buffer.
+   inflateBack() may then be used multiple times to inflate a complete, raw
+   deflate stream with each call.  inflateBackEnd() is then called to free the
+   allocated state.
+
+     A raw deflate stream is one with no zlib or gzip header or trailer.
+   This routine would normally be used in a utility that reads zip or gzip
+   files and writes out uncompressed files.  The utility would decode the
+   header and process the trailer on its own, hence this routine expects only
+   the raw deflate stream to decompress.  This is different from the normal
+   behavior of inflate(), which expects either a zlib or gzip header and
+   trailer around the deflate stream.
+
+     inflateBack() uses two subroutines supplied by the caller that are then
+   called by inflateBack() for input and output.  inflateBack() calls those
+   routines until it reads a complete deflate stream and writes out all of the
+   uncompressed data, or until it encounters an error.  The function's
+   parameters and return types are defined above in the in_func and out_func
+   typedefs.  inflateBack() will call in(in_desc, &buf) which should return the
+   number of bytes of provided input, and a pointer to that input in buf.  If
+   there is no input available, in() must return zero--buf is ignored in that
+   case--and inflateBack() will return a buffer error.  inflateBack() will call
+   out(out_desc, buf, len) to write the uncompressed data buf[0..len-1].  out()
+   should return zero on success, or non-zero on failure.  If out() returns
+   non-zero, inflateBack() will return with an error.  Neither in() nor out()
+   are permitted to change the contents of the window provided to
+   inflateBackInit(), which is also the buffer that out() uses to write from.
+   The length written by out() will be at most the window size.  Any non-zero
+   amount of input may be provided by in().
+
+     For convenience, inflateBack() can be provided input on the first call by
+   setting strm->next_in and strm->avail_in.  If that input is exhausted, then
+   in() will be called.  Therefore strm->next_in must be initialized before
+   calling inflateBack().  If strm->next_in is Z_NULL, then in() will be called
+   immediately for input.  If strm->next_in is not Z_NULL, then strm->avail_in
+   must also be initialized, and then if strm->avail_in is not zero, input will
+   initially be taken from strm->next_in[0 ..  strm->avail_in - 1].
+
+     The in_desc and out_desc parameters of inflateBack() is passed as the
+   first parameter of in() and out() respectively when they are called.  These
+   descriptors can be optionally used to pass any information that the caller-
+   supplied in() and out() functions need to do their job.
+
+     On return, inflateBack() will set strm->next_in and strm->avail_in to
+   pass back any unused input that was provided by the last in() call.  The
+   return values of inflateBack() can be Z_STREAM_END on success, Z_BUF_ERROR
+   if in() or out() returned an error, Z_DATA_ERROR if there was a format error
+   in the deflate stream (in which case strm->msg is set to indicate the nature
+   of the error), or Z_STREAM_ERROR if the stream was not properly initialized.
+   In the case of Z_BUF_ERROR, an input or output error can be distinguished
+   using strm->next_in which will be Z_NULL only if in() returned an error.  If
+   strm->next_in is not Z_NULL, then the Z_BUF_ERROR was due to out() returning
+   non-zero.  (in() will always be called before out(), so strm->next_in is
+   assured to be defined if out() returns non-zero.) Note that inflateBack()
+   cannot return Z_OK.
+*/
+
+ZEXTERN int ZEXPORT inflateBackEnd OF((z_streamp strm));
+/*
+     All memory allocated by inflateBackInit() is freed.
+
+     inflateBackEnd() returns Z_OK on success, or Z_STREAM_ERROR if the stream
+   state was inconsistent.
+*/
+
+ZEXTERN uLong ZEXPORT zlibCompileFlags OF((void));
+/* Return flags indicating compile-time options.
+
+    Type sizes, two bits each, 00 = 16 bits, 01 = 32, 10 = 64, 11 = other:
+     1.0: size of uInt
+     3.2: size of uLong
+     5.4: size of voidpf (pointer)
+     7.6: size of z_off_t
+
+    Compiler, assembler, and debug options:
+     8: DEBUG
+     9: ASMV or ASMINF -- use ASM code
+     10: ZLIB_WINAPI -- exported functions use the WINAPI calling convention
+     11: 0 (reserved)
+
+    One-time table building (smaller code, but not thread-safe if true):
+     12: BUILDFIXED -- build static block decoding tables when needed
+     13: DYNAMIC_CRC_TABLE -- build CRC calculation tables when needed
+     14,15: 0 (reserved)
+
+    Library content (indicates missing functionality):
+     16: NO_GZCOMPRESS -- gz* functions cannot compress (to avoid linking
+                          deflate code when not needed)
+     17: NO_GZIP -- deflate can't write gzip streams, and inflate can't detect
+                    and decode gzip streams (to avoid linking crc code)
+     18-19: 0 (reserved)
+
+    Operation variations (changes in library functionality):
+     20: PKZIP_BUG_WORKAROUND -- slightly more permissive inflate
+     21: FASTEST -- deflate algorithm with only one, lowest compression level
+     22,23: 0 (reserved)
+
+    The sprintf variant used by gzprintf (zero is best):
+     24: 0 = vs*, 1 = s* -- 1 means limited to 20 arguments after the format
+     25: 0 = *nprintf, 1 = *printf -- 1 means gzprintf() not secure!
+     26: 0 = returns value, 1 = void -- 1 means inferred string length returned
+
+    Remainder:
+     27-31: 0 (reserved)
+ */
+
+
+                        /* utility functions */
+
+/*
+     The following utility functions are implemented on top of the basic
+   stream-oriented functions.  To simplify the interface, some default options
+   are assumed (compression level and memory usage, standard memory allocation
+   functions).  The source code of these utility functions can be modified if
+   you need special options.
+*/
+
+ZEXTERN int ZEXPORT compress OF((Bytef *dest,   uLongf *destLen,
+                                 const Bytef *source, uLong sourceLen));
+/*
+     Compresses the source buffer into the destination buffer.  sourceLen is
+   the byte length of the source buffer.  Upon entry, destLen is the total size
+   of the destination buffer, which must be at least the value returned by
+   compressBound(sourceLen).  Upon exit, destLen is the actual size of the
+   compressed buffer.
+
+     compress returns Z_OK if success, Z_MEM_ERROR if there was not
+   enough memory, Z_BUF_ERROR if there was not enough room in the output
+   buffer.
+*/
+
+ZEXTERN int ZEXPORT compress2 OF((Bytef *dest,   uLongf *destLen,
+                                  const Bytef *source, uLong sourceLen,
+                                  int level));
+/*
+     Compresses the source buffer into the destination buffer.  The level
+   parameter has the same meaning as in deflateInit.  sourceLen is the byte
+   length of the source buffer.  Upon entry, destLen is the total size of the
+   destination buffer, which must be at least the value returned by
+   compressBound(sourceLen).  Upon exit, destLen is the actual size of the
+   compressed buffer.
+
+     compress2 returns Z_OK if success, Z_MEM_ERROR if there was not enough
+   memory, Z_BUF_ERROR if there was not enough room in the output buffer,
+   Z_STREAM_ERROR if the level parameter is invalid.
+*/
+
+ZEXTERN uLong ZEXPORT compressBound OF((uLong sourceLen));
+/*
+     compressBound() returns an upper bound on the compressed size after
+   compress() or compress2() on sourceLen bytes.  It would be used before a
+   compress() or compress2() call to allocate the destination buffer.
+*/
+
+ZEXTERN int ZEXPORT uncompress OF((Bytef *dest,   uLongf *destLen,
+                                   const Bytef *source, uLong sourceLen));
+/*
+     Decompresses the source buffer into the destination buffer.  sourceLen is
+   the byte length of the source buffer.  Upon entry, destLen is the total size
+   of the destination buffer, which must be large enough to hold the entire
+   uncompressed data.  (The size of the uncompressed data must have been saved
+   previously by the compressor and transmitted to the decompressor by some
+   mechanism outside the scope of this compression library.) Upon exit, destLen
+   is the actual size of the uncompressed buffer.
+
+     uncompress returns Z_OK if success, Z_MEM_ERROR if there was not
+   enough memory, Z_BUF_ERROR if there was not enough room in the output
+   buffer, or Z_DATA_ERROR if the input data was corrupted or incomplete.
+*/
+
+
+                        /* gzip file access functions */
+
+/*
+     This library supports reading and writing files in gzip (.gz) format with
+   an interface similar to that of stdio, using the functions that start with
+   "gz".  The gzip format is different from the zlib format.  gzip is a gzip
+   wrapper, documented in RFC 1952, wrapped around a deflate stream.
+*/
+
+typedef voidp gzFile;       /* opaque gzip file descriptor */
+
+/*
+ZEXTERN gzFile ZEXPORT gzopen OF((const char *path, const char *mode));
+
+     Opens a gzip (.gz) file for reading or writing.  The mode parameter is as
+   in fopen ("rb" or "wb") but can also include a compression level ("wb9") or
+   a strategy: 'f' for filtered data as in "wb6f", 'h' for Huffman-only
+   compression as in "wb1h", 'R' for run-length encoding as in "wb1R", or 'F'
+   for fixed code compression as in "wb9F".  (See the description of
+   deflateInit2 for more information about the strategy parameter.) Also "a"
+   can be used instead of "w" to request that the gzip stream that will be
+   written be appended to the file.  "+" will result in an error, since reading
+   and writing to the same gzip file is not supported.
+
+     gzopen can be used to read a file which is not in gzip format; in this
+   case gzread will directly read from the file without decompression.
+
+     gzopen returns NULL if the file could not be opened, if there was
+   insufficient memory to allocate the gzFile state, or if an invalid mode was
+   specified (an 'r', 'w', or 'a' was not provided, or '+' was provided).
+   errno can be checked to determine if the reason gzopen failed was that the
+   file could not be opened.
+*/
+
+ZEXTERN gzFile ZEXPORT gzdopen OF((int fd, const char *mode));
+/*
+     gzdopen associates a gzFile with the file descriptor fd.  File descriptors
+   are obtained from calls like open, dup, creat, pipe or fileno (if the file
+   has been previously opened with fopen).  The mode parameter is as in gzopen.
+
+     The next call of gzclose on the returned gzFile will also close the file
+   descriptor fd, just like fclose(fdopen(fd, mode)) closes the file descriptor
+   fd.  If you want to keep fd open, use fd = dup(fd_keep); gz = gzdopen(fd,
+   mode);.  The duplicated descriptor should be saved to avoid a leak, since
+   gzdopen does not close fd if it fails.
+
+     gzdopen returns NULL if there was insufficient memory to allocate the
+   gzFile state, if an invalid mode was specified (an 'r', 'w', or 'a' was not
+   provided, or '+' was provided), or if fd is -1.  The file descriptor is not
+   used until the next gz* read, write, seek, or close operation, so gzdopen
+   will not detect if fd is invalid (unless fd is -1).
+*/
+
+ZEXTERN int ZEXPORT gzbuffer OF((gzFile file, unsigned size));
+/*
+     Set the internal buffer size used by this library's functions.  The
+   default buffer size is 8192 bytes.  This function must be called after
+   gzopen() or gzdopen(), and before any other calls that read or write the
+   file.  The buffer memory allocation is always deferred to the first read or
+   write.  Two buffers are allocated, either both of the specified size when
+   writing, or one of the specified size and the other twice that size when
+   reading.  A larger buffer size of, for example, 64K or 128K bytes will
+   noticeably increase the speed of decompression (reading).
+
+     The new buffer size also affects the maximum length for gzprintf().
+
+     gzbuffer() returns 0 on success, or -1 on failure, such as being called
+   too late.
+*/
+
+ZEXTERN int ZEXPORT gzsetparams OF((gzFile file, int level, int strategy));
+/*
+     Dynamically update the compression level or strategy.  See the description
+   of deflateInit2 for the meaning of these parameters.
+
+     gzsetparams returns Z_OK if success, or Z_STREAM_ERROR if the file was not
+   opened for writing.
+*/
+
+ZEXTERN int ZEXPORT gzread OF((gzFile file, voidp buf, unsigned len));
+/*
+     Reads the given number of uncompressed bytes from the compressed file.  If
+   the input file was not in gzip format, gzread copies the given number of
+   bytes into the buffer.
+
+     After reaching the end of a gzip stream in the input, gzread will continue
+   to read, looking for another gzip stream, or failing that, reading the rest
+   of the input file directly without decompression.  The entire input file
+   will be read if gzread is called until it returns less than the requested
+   len.
+
+     gzread returns the number of uncompressed bytes actually read, less than
+   len for end of file, or -1 for error.
+*/
+
+ZEXTERN int ZEXPORT gzwrite OF((gzFile file,
+                                voidpc buf, unsigned len));
+/*
+     Writes the given number of uncompressed bytes into the compressed file.
+   gzwrite returns the number of uncompressed bytes written or 0 in case of
+   error.
+*/
+
+ZEXTERN int ZEXPORTVA gzprintf OF((gzFile file, const char *format, ...));
+/*
+     Converts, formats, and writes the arguments to the compressed file under
+   control of the format string, as in fprintf.  gzprintf returns the number of
+   uncompressed bytes actually written, or 0 in case of error.  The number of
+   uncompressed bytes written is limited to 8191, or one less than the buffer
+   size given to gzbuffer().  The caller should assure that this limit is not
+   exceeded.  If it is exceeded, then gzprintf() will return an error (0) with
+   nothing written.  In this case, there may also be a buffer overflow with
+   unpredictable consequences, which is possible only if zlib was compiled with
+   the insecure functions sprintf() or vsprintf() because the secure snprintf()
+   or vsnprintf() functions were not available.  This can be determined using
+   zlibCompileFlags().
+*/
+
+ZEXTERN int ZEXPORT gzputs OF((gzFile file, const char *s));
+/*
+     Writes the given null-terminated string to the compressed file, excluding
+   the terminating null character.
+
+     gzputs returns the number of characters written, or -1 in case of error.
+*/
+
+ZEXTERN char * ZEXPORT gzgets OF((gzFile file, char *buf, int len));
+/*
+     Reads bytes from the compressed file until len-1 characters are read, or a
+   newline character is read and transferred to buf, or an end-of-file
+   condition is encountered.  If any characters are read or if len == 1, the
+   string is terminated with a null character.  If no characters are read due
+   to an end-of-file or len < 1, then the buffer is left untouched.
+
+     gzgets returns buf which is a null-terminated string, or it returns NULL
+   for end-of-file or in case of error.  If there was an error, the contents at
+   buf are indeterminate.
+*/
+
+ZEXTERN int ZEXPORT gzputc OF((gzFile file, int c));
+/*
+     Writes c, converted to an unsigned char, into the compressed file.  gzputc
+   returns the value that was written, or -1 in case of error.
+*/
+
+ZEXTERN int ZEXPORT gzgetc OF((gzFile file));
+/*
+     Reads one byte from the compressed file.  gzgetc returns this byte or -1
+   in case of end of file or error.
+*/
+
+ZEXTERN int ZEXPORT gzungetc OF((int c, gzFile file));
+/*
+     Push one character back onto the stream to be read as the first character
+   on the next read.  At least one character of push-back is allowed.
+   gzungetc() returns the character pushed, or -1 on failure.  gzungetc() will
+   fail if c is -1, and may fail if a character has been pushed but not read
+   yet.  If gzungetc is used immediately after gzopen or gzdopen, at least the
+   output buffer size of pushed characters is allowed.  (See gzbuffer above.)
+   The pushed character will be discarded if the stream is repositioned with
+   gzseek() or gzrewind().
+*/
+
+ZEXTERN int ZEXPORT gzflush OF((gzFile file, int flush));
+/*
+     Flushes all pending output into the compressed file.  The parameter flush
+   is as in the deflate() function.  The return value is the zlib error number
+   (see function gzerror below).  gzflush is only permitted when writing.
+
+     If the flush parameter is Z_FINISH, the remaining data is written and the
+   gzip stream is completed in the output.  If gzwrite() is called again, a new
+   gzip stream will be started in the output.  gzread() is able to read such
+   concatented gzip streams.
+
+     gzflush should be called only when strictly necessary because it will
+   degrade compression if called too often.
+*/
+
+/*
+ZEXTERN z_off_t ZEXPORT gzseek OF((gzFile file,
+                                   z_off_t offset, int whence));
+
+     Sets the starting position for the next gzread or gzwrite on the given
+   compressed file.  The offset represents a number of bytes in the
+   uncompressed data stream.  The whence parameter is defined as in lseek(2);
+   the value SEEK_END is not supported.
+
+     If the file is opened for reading, this function is emulated but can be
+   extremely slow.  If the file is opened for writing, only forward seeks are
+   supported; gzseek then compresses a sequence of zeroes up to the new
+   starting position.
+
+     gzseek returns the resulting offset location as measured in bytes from
+   the beginning of the uncompressed stream, or -1 in case of error, in
+   particular if the file is opened for writing and the new starting position
+   would be before the current position.
+*/
+
+ZEXTERN int ZEXPORT    gzrewind OF((gzFile file));
+/*
+     Rewinds the given file. This function is supported only for reading.
+
+     gzrewind(file) is equivalent to (int)gzseek(file, 0L, SEEK_SET)
+*/
+
+/*
+ZEXTERN z_off_t ZEXPORT    gztell OF((gzFile file));
+
+     Returns the starting position for the next gzread or gzwrite on the given
+   compressed file.  This position represents a number of bytes in the
+   uncompressed data stream, and is zero when starting, even if appending or
+   reading a gzip stream from the middle of a file using gzdopen().
+
+     gztell(file) is equivalent to gzseek(file, 0L, SEEK_CUR)
+*/
+
+/*
+ZEXTERN z_off_t ZEXPORT gzoffset OF((gzFile file));
+
+     Returns the current offset in the file being read or written.  This offset
+   includes the count of bytes that precede the gzip stream, for example when
+   appending or when using gzdopen() for reading.  When reading, the offset
+   does not include as yet unused buffered input.  This information can be used
+   for a progress indicator.  On error, gzoffset() returns -1.
+*/
+
+ZEXTERN int ZEXPORT gzeof OF((gzFile file));
+/*
+     Returns true (1) if the end-of-file indicator has been set while reading,
+   false (0) otherwise.  Note that the end-of-file indicator is set only if the
+   read tried to go past the end of the input, but came up short.  Therefore,
+   just like feof(), gzeof() may return false even if there is no more data to
+   read, in the event that the last read request was for the exact number of
+   bytes remaining in the input file.  This will happen if the input file size
+   is an exact multiple of the buffer size.
+
+     If gzeof() returns true, then the read functions will return no more data,
+   unless the end-of-file indicator is reset by gzclearerr() and the input file
+   has grown since the previous end of file was detected.
+*/
+
+ZEXTERN int ZEXPORT gzdirect OF((gzFile file));
+/*
+     Returns true (1) if file is being copied directly while reading, or false
+   (0) if file is a gzip stream being decompressed.  This state can change from
+   false to true while reading the input file if the end of a gzip stream is
+   reached, but is followed by data that is not another gzip stream.
+
+     If the input file is empty, gzdirect() will return true, since the input
+   does not contain a gzip stream.
+
+     If gzdirect() is used immediately after gzopen() or gzdopen() it will
+   cause buffers to be allocated to allow reading the file to determine if it
+   is a gzip file.  Therefore if gzbuffer() is used, it should be called before
+   gzdirect().
+*/
+
+ZEXTERN int ZEXPORT    gzclose OF((gzFile file));
+/*
+     Flushes all pending output if necessary, closes the compressed file and
+   deallocates the (de)compression state.  Note that once file is closed, you
+   cannot call gzerror with file, since its structures have been deallocated.
+   gzclose must not be called more than once on the same file, just as free
+   must not be called more than once on the same allocation.
+
+     gzclose will return Z_STREAM_ERROR if file is not valid, Z_ERRNO on a
+   file operation error, or Z_OK on success.
+*/
+
+ZEXTERN int ZEXPORT gzclose_r OF((gzFile file));
+ZEXTERN int ZEXPORT gzclose_w OF((gzFile file));
+/*
+     Same as gzclose(), but gzclose_r() is only for use when reading, and
+   gzclose_w() is only for use when writing or appending.  The advantage to
+   using these instead of gzclose() is that they avoid linking in zlib
+   compression or decompression code that is not used when only reading or only
+   writing respectively.  If gzclose() is used, then both compression and
+   decompression code will be included the application when linking to a static
+   zlib library.
+*/
+
+ZEXTERN const char * ZEXPORT gzerror OF((gzFile file, int *errnum));
+/*
+     Returns the error message for the last error which occurred on the given
+   compressed file.  errnum is set to zlib error number.  If an error occurred
+   in the file system and not in the compression library, errnum is set to
+   Z_ERRNO and the application may consult errno to get the exact error code.
+
+     The application must not modify the returned string.  Future calls to
+   this function may invalidate the previously returned string.  If file is
+   closed, then the string previously returned by gzerror will no longer be
+   available.
+
+     gzerror() should be used to distinguish errors from end-of-file for those
+   functions above that do not distinguish those cases in their return values.
+*/
+
+ZEXTERN void ZEXPORT gzclearerr OF((gzFile file));
+/*
+     Clears the error and end-of-file flags for file.  This is analogous to the
+   clearerr() function in stdio.  This is useful for continuing to read a gzip
+   file that is being written concurrently.
+*/
+
+
+                        /* checksum functions */
+
+/*
+     These functions are not related to compression but are exported
+   anyway because they might be useful in applications using the compression
+   library.
+*/
+
+ZEXTERN uLong ZEXPORT adler32 OF((uLong adler, const Bytef *buf, uInt len));
+/*
+     Update a running Adler-32 checksum with the bytes buf[0..len-1] and
+   return the updated checksum.  If buf is Z_NULL, this function returns the
+   required initial value for the checksum.
+
+     An Adler-32 checksum is almost as reliable as a CRC32 but can be computed
+   much faster.
+
+   Usage example:
+
+     uLong adler = adler32(0L, Z_NULL, 0);
+
+     while (read_buffer(buffer, length) != EOF) {
+       adler = adler32(adler, buffer, length);
+     }
+     if (adler != original_adler) error();
+*/
+
+/*
+ZEXTERN uLong ZEXPORT adler32_combine OF((uLong adler1, uLong adler2,
+                                          z_off_t len2));
+
+     Combine two Adler-32 checksums into one.  For two sequences of bytes, seq1
+   and seq2 with lengths len1 and len2, Adler-32 checksums were calculated for
+   each, adler1 and adler2.  adler32_combine() returns the Adler-32 checksum of
+   seq1 and seq2 concatenated, requiring only adler1, adler2, and len2.
+*/
+
+ZEXTERN uLong ZEXPORT crc32   OF((uLong crc, const Bytef *buf, uInt len));
+/*
+     Update a running CRC-32 with the bytes buf[0..len-1] and return the
+   updated CRC-32.  If buf is Z_NULL, this function returns the required
+   initial value for the for the crc.  Pre- and post-conditioning (one's
+   complement) is performed within this function so it shouldn't be done by the
+   application.
+
+   Usage example:
+
+     uLong crc = crc32(0L, Z_NULL, 0);
+
+     while (read_buffer(buffer, length) != EOF) {
+       crc = crc32(crc, buffer, length);
+     }
+     if (crc != original_crc) error();
+*/
+
+/*
+ZEXTERN uLong ZEXPORT crc32_combine OF((uLong crc1, uLong crc2, z_off_t len2));
+
+     Combine two CRC-32 check values into one.  For two sequences of bytes,
+   seq1 and seq2 with lengths len1 and len2, CRC-32 check values were
+   calculated for each, crc1 and crc2.  crc32_combine() returns the CRC-32
+   check value of seq1 and seq2 concatenated, requiring only crc1, crc2, and
+   len2.
+*/
+
+
+                        /* various hacks, don't look :) */
+
+/* deflateInit and inflateInit are macros to allow checking the zlib version
+ * and the compiler's view of z_stream:
+ */
+ZEXTERN int ZEXPORT deflateInit_ OF((z_streamp strm, int level,
+                                     const char *version, int stream_size));
+ZEXTERN int ZEXPORT inflateInit_ OF((z_streamp strm,
+                                     const char *version, int stream_size));
+ZEXTERN int ZEXPORT deflateInit2_ OF((z_streamp strm, int  level, int  method,
+                                      int windowBits, int memLevel,
+                                      int strategy, const char *version,
+                                      int stream_size));
+ZEXTERN int ZEXPORT inflateInit2_ OF((z_streamp strm, int  windowBits,
+                                      const char *version, int stream_size));
+ZEXTERN int ZEXPORT inflateBackInit_ OF((z_streamp strm, int windowBits,
+                                         unsigned char FAR *window,
+                                         const char *version,
+                                         int stream_size));
+#define deflateInit(strm, level) \
+        deflateInit_((strm), (level),       ZLIB_VERSION, sizeof(z_stream))
+#define inflateInit(strm) \
+        inflateInit_((strm),                ZLIB_VERSION, sizeof(z_stream))
+#define deflateInit2(strm, level, method, windowBits, memLevel, strategy) \
+        deflateInit2_((strm),(level),(method),(windowBits),(memLevel),\
+                      (strategy),           ZLIB_VERSION, sizeof(z_stream))
+#define inflateInit2(strm, windowBits) \
+        inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream))
+#define inflateBackInit(strm, windowBits, window) \
+        inflateBackInit_((strm), (windowBits), (window), \
+                                            ZLIB_VERSION, sizeof(z_stream))
+
+/* provide 64-bit offset functions if _LARGEFILE64_SOURCE defined, and/or
+ * change the regular functions to 64 bits if _FILE_OFFSET_BITS is 64 (if
+ * both are true, the application gets the *64 functions, and the regular
+ * functions are changed to 64 bits) -- in case these are set on systems
+ * without large file support, _LFS64_LARGEFILE must also be true
+ */
+#if defined(_LARGEFILE64_SOURCE) && _LFS64_LARGEFILE-0
+   ZEXTERN gzFile ZEXPORT gzopen64 OF((const char *, const char *));
+   ZEXTERN z_off64_t ZEXPORT gzseek64 OF((gzFile, z_off64_t, int));
+   ZEXTERN z_off64_t ZEXPORT gztell64 OF((gzFile));
+   ZEXTERN z_off64_t ZEXPORT gzoffset64 OF((gzFile));
+   ZEXTERN uLong ZEXPORT adler32_combine64 OF((uLong, uLong, z_off64_t));
+   ZEXTERN uLong ZEXPORT crc32_combine64 OF((uLong, uLong, z_off64_t));
+#endif
+
+#if !defined(ZLIB_INTERNAL) && _FILE_OFFSET_BITS-0 == 64 && _LFS64_LARGEFILE-0
+#  define gzopen gzopen64
+#  define gzseek gzseek64
+#  define gztell gztell64
+#  define gzoffset gzoffset64
+#  define adler32_combine adler32_combine64
+#  define crc32_combine crc32_combine64
+#  ifdef _LARGEFILE64_SOURCE
+     ZEXTERN gzFile ZEXPORT gzopen64 OF((const char *, const char *));
+     ZEXTERN z_off_t ZEXPORT gzseek64 OF((gzFile, z_off_t, int));
+     ZEXTERN z_off_t ZEXPORT gztell64 OF((gzFile));
+     ZEXTERN z_off_t ZEXPORT gzoffset64 OF((gzFile));
+     ZEXTERN uLong ZEXPORT adler32_combine64 OF((uLong, uLong, z_off_t));
+     ZEXTERN uLong ZEXPORT crc32_combine64 OF((uLong, uLong, z_off_t));
+#  endif
+#else
+   ZEXTERN gzFile ZEXPORT gzopen OF((const char *, const char *));
+   ZEXTERN z_off_t ZEXPORT gzseek OF((gzFile, z_off_t, int));
+   ZEXTERN z_off_t ZEXPORT gztell OF((gzFile));
+   ZEXTERN z_off_t ZEXPORT gzoffset OF((gzFile));
+   ZEXTERN uLong ZEXPORT adler32_combine OF((uLong, uLong, z_off_t));
+   ZEXTERN uLong ZEXPORT crc32_combine OF((uLong, uLong, z_off_t));
+#endif
+
+/* hack for buggy compilers */
+#if !defined(ZUTIL_H) && !defined(NO_DUMMY_DECL)
+    struct internal_state {int dummy;};
+#endif
+
+/* undocumented functions */
+ZEXTERN const char   * ZEXPORT zError           OF((int));
+ZEXTERN int            ZEXPORT inflateSyncPoint OF((z_streamp));
+ZEXTERN const uLongf * ZEXPORT get_crc_table    OF((void));
+ZEXTERN int            ZEXPORT inflateUndermine OF((z_streamp, int));
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ZLIB_H */
diff --git a/src/plugins/cfitsio/zuncompress.c b/src/plugins/cfitsio/zuncompress.c
new file mode 100644
index 0000000..c73ee6d
--- /dev/null
+++ b/src/plugins/cfitsio/zuncompress.c
@@ -0,0 +1,603 @@
+/* gzcompress.h -- definitions for the .Z decompression routine used in CFITSIO */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#define get_char() get_byte()
+
+/* gzip.h -- common declarations for all gzip modules  */
+
+#define OF(args)  args
+typedef void *voidp;
+
+#define memzero(s, n)     memset ((voidp)(s), 0, (n))
+
+typedef unsigned char  uch;
+typedef unsigned short ush;
+typedef unsigned long  ulg;
+
+/* private version of MIN function */
+#define MINZIP(a,b) ((a) <= (b) ? (a) : (b))
+
+/* Return codes from gzip */
+#define OK      0
+#define ERROR   1
+#define COMPRESSED  1
+#define DEFLATED    8
+#define INBUFSIZ  0x8000    /* input buffer size */
+#define INBUF_EXTRA  64     /* required by unlzw() */
+#define OUTBUFSIZ  16384    /* output buffer size */
+#define OUTBUF_EXTRA 2048   /* required by unlzw() */
+#define DIST_BUFSIZE 0x8000 /* buffer for distances, see trees.c */
+#define WSIZE 0x8000        /* window size--must be a power of two, and */
+#define DECLARE(type, array, size)  type array[size]
+#define tab_suffix window
+#define tab_prefix prev    /* hash link (see deflate.c) */
+#define head (prev+WSIZE)  /* hash head (see deflate.c) */
+#define	LZW_MAGIC      "\037\235" /* Magic header for lzw files, 1F 9D */
+#define get_byte()  (inptr < insize ? inbuf[inptr++] : fill_inbuf(0))
+
+/* Diagnostic functions */
+#  define Assert(cond,msg)
+#  define Trace(x)
+#  define Tracev(x)
+#  define Tracevv(x)
+#  define Tracec(c,x)
+#  define Tracecv(c,x)
+
+/* lzw.h -- define the lzw functions. */
+
+#ifndef BITS
+#  define BITS 16
+#endif
+#define INIT_BITS 9              /* Initial number of bits per code */
+#define BIT_MASK    0x1f /* Mask for 'number of compression bits' */
+#define BLOCK_MODE  0x80
+#define LZW_RESERVED 0x60 /* reserved bits */
+#define	CLEAR  256       /* flush the dictionary */
+#define FIRST  (CLEAR+1) /* first free entry */
+
+/* prototypes */
+
+#define local static
+void ffpmsg(const char *err_message);
+
+local int  fill_inbuf    OF((int eof_ok));
+local void write_buf     OF((voidp buf, unsigned cnt));
+local void error         OF((char *m));
+local int unlzw  OF((FILE *in, FILE *out));
+
+typedef int file_t;     /* Do not use stdio */
+
+int (*work) OF((FILE *infile, FILE *outfile)) = unlzw; /* function to call */
+
+local void error         OF((char *m));
+
+		/* global buffers */
+
+static DECLARE(uch, inbuf,  INBUFSIZ +INBUF_EXTRA);
+static DECLARE(uch, outbuf, OUTBUFSIZ+OUTBUF_EXTRA);
+static DECLARE(ush, d_buf,  DIST_BUFSIZE);
+static DECLARE(uch, window, 2L*WSIZE);
+
+#ifndef MAXSEG_64K
+    static DECLARE(ush, tab_prefix, 1L<<BITS);
+#else
+    static DECLARE(ush, tab_prefix0, 1L<<(BITS-1));
+    static DECLARE(ush, tab_prefix1, 1L<<(BITS-1));
+#endif
+
+		/* local variables */
+
+/* 11/25/98: added 'static' to local variable definitions, to avoid */
+/* conflict with external source files */
+
+static int maxbits = BITS;   /* max bits per code for LZW */
+static int method = DEFLATED;/* compression method */
+static int exit_code = OK;   /* program exit code */
+static int last_member;      /* set for .zip and .Z files */
+static long bytes_in;             /* number of input bytes */
+static long bytes_out;            /* number of output bytes */
+static char ifname[128];          /* input file name */
+static FILE *ifd;               /* input file descriptor */
+static FILE *ofd;               /* output file descriptor */
+static void **memptr;          /* memory location for uncompressed file */
+static size_t *memsize;        /* size (bytes) of memory allocated for file */
+void *(*realloc_fn)(void *p, size_t newsize);  /* reallocation function */
+static unsigned insize;     /* valid bytes in inbuf */
+static unsigned inptr;      /* index of next byte to be processed in inbuf */
+
+/* prototype for the following functions */
+int zuncompress2mem(char *filename, 
+             FILE *diskfile, 
+             char **buffptr, 
+             size_t *buffsize, 
+             void *(*mem_realloc)(void *p, size_t newsize),
+             size_t *filesize,
+             int *status);
+
+/*--------------------------------------------------------------------------*/
+int zuncompress2mem(char *filename,  /* name of input file                 */
+             FILE *indiskfile,     /* I - file pointer                        */
+             char **buffptr,   /* IO - memory pointer                     */
+             size_t *buffsize,   /* IO - size of buffer, in bytes           */
+             void *(*mem_realloc)(void *p, size_t newsize), /* function     */
+             size_t *filesize,   /* O - size of file, in bytes              */
+             int *status)        /* IO - error status                       */
+
+/*
+  Uncompress the file into memory.  Fill whatever amount of memory has
+  already been allocated, then realloc more memory, using the supplied
+  input function, if necessary.
+*/
+{
+    char magic[2]; /* magic header */
+
+    if (*status > 0)
+        return(*status);
+
+    /*  save input parameters into global variables */
+    ifname[0] = '\0';
+    strncat(ifname, filename, 127);
+    ifd = indiskfile;
+    memptr = (void **) buffptr;
+    memsize = buffsize;
+    realloc_fn = mem_realloc;
+
+    /* clear input and output buffers */
+
+    insize = inptr = 0;
+    bytes_in = bytes_out = 0L;
+
+    magic[0] = (char)get_byte();
+    magic[1] = (char)get_byte();
+
+    if (memcmp(magic, LZW_MAGIC, 2) != 0) {
+      error("ERROR: input .Z file is in unrecognized compression format.\n");
+      return(-1);
+    }
+
+    work = unlzw;
+    method = COMPRESSED;
+    last_member = 1;
+
+    /* do the uncompression */
+    if ((*work)(ifd, ofd) != OK) {
+        method = -1; /* force cleanup */
+        *status = 414;    /* report some sort of decompression error */
+    }
+
+    if (filesize)  *filesize = bytes_out;
+
+    return(*status);
+}
+/*=========================================================================*/
+/*=========================================================================*/
+/* this marks the begining of the original file 'unlzw.c'                  */
+/*=========================================================================*/
+/*=========================================================================*/
+
+/* unlzw.c -- decompress files in LZW format.
+ * The code in this file is directly derived from the public domain 'compress'
+ * written by Spencer Thomas, Joe Orost, James Woods, Jim McKie, Steve Davies,
+ * Ken Turkowski, Dave Mack and Peter Jannesen.
+ */
+
+typedef	unsigned char	char_type;
+typedef          long   code_int;
+typedef unsigned long 	count_int;
+typedef unsigned short	count_short;
+typedef unsigned long 	cmp_code_int;
+
+#define MAXCODE(n)	(1L << (n))
+    
+#ifndef	REGISTERS
+#	define	REGISTERS	2
+#endif
+#define	REG1	
+#define	REG2	
+#define	REG3	
+#define	REG4	
+#define	REG5	
+#define	REG6	
+#define	REG7	
+#define	REG8	
+#define	REG9	
+#define	REG10
+#define	REG11	
+#define	REG12	
+#define	REG13
+#define	REG14
+#define	REG15
+#define	REG16
+#if REGISTERS >= 1
+#	undef	REG1
+#	define	REG1	register
+#endif
+#if REGISTERS >= 2
+#	undef	REG2
+#	define	REG2	register
+#endif
+#if REGISTERS >= 3
+#	undef	REG3
+#	define	REG3	register
+#endif
+#if REGISTERS >= 4
+#	undef	REG4
+#	define	REG4	register
+#endif
+#if REGISTERS >= 5
+#	undef	REG5
+#	define	REG5	register
+#endif
+#if REGISTERS >= 6
+#	undef	REG6
+#	define	REG6	register
+#endif
+#if REGISTERS >= 7
+#	undef	REG7
+#	define	REG7	register
+#endif
+#if REGISTERS >= 8
+#	undef	REG8
+#	define	REG8	register
+#endif
+#if REGISTERS >= 9
+#	undef	REG9
+#	define	REG9	register
+#endif
+#if REGISTERS >= 10
+#	undef	REG10
+#	define	REG10	register
+#endif
+#if REGISTERS >= 11
+#	undef	REG11
+#	define	REG11	register
+#endif
+#if REGISTERS >= 12
+#	undef	REG12
+#	define	REG12	register
+#endif
+#if REGISTERS >= 13
+#	undef	REG13
+#	define	REG13	register
+#endif
+#if REGISTERS >= 14
+#	undef	REG14
+#	define	REG14	register
+#endif
+#if REGISTERS >= 15
+#	undef	REG15
+#	define	REG15	register
+#endif
+#if REGISTERS >= 16
+#	undef	REG16
+#	define	REG16	register
+#endif
+    
+#ifndef	BYTEORDER
+#	define	BYTEORDER	0000
+#endif
+	
+#ifndef	NOALLIGN
+#	define	NOALLIGN	0
+#endif
+
+
+union	bytes {
+    long  word;
+    struct {
+#if BYTEORDER == 4321
+	char_type	b1;
+	char_type	b2;
+	char_type	b3;
+	char_type	b4;
+#else
+#if BYTEORDER == 1234
+	char_type	b4;
+	char_type	b3;
+	char_type	b2;
+	char_type	b1;
+#else
+#	undef	BYTEORDER
+	int  dummy;
+#endif
+#endif
+    } bytes;
+};
+
+#if BYTEORDER == 4321 && NOALLIGN == 1
+#  define input(b,o,c,n,m){ \
+     (c) = (*(long *)(&(b)[(o)>>3])>>((o)&0x7))&(m); \
+     (o) += (n); \
+   }
+#else
+#  define input(b,o,c,n,m){ \
+     REG1 char_type *p = &(b)[(o)>>3]; \
+     (c) = ((((long)(p[0]))|((long)(p[1])<<8)| \
+     ((long)(p[2])<<16))>>((o)&0x7))&(m); \
+     (o) += (n); \
+   }
+#endif
+
+#ifndef MAXSEG_64K
+   /* DECLARE(ush, tab_prefix, (1<<BITS)); -- prefix code */
+#  define tab_prefixof(i) tab_prefix[i]
+#  define clear_tab_prefixof()	memzero(tab_prefix, 256);
+#else
+   /* DECLARE(ush, tab_prefix0, (1<<(BITS-1)); -- prefix for even codes */
+   /* DECLARE(ush, tab_prefix1, (1<<(BITS-1)); -- prefix for odd  codes */
+   ush *tab_prefix[2];
+#  define tab_prefixof(i) tab_prefix[(i)&1][(i)>>1]
+#  define clear_tab_prefixof()	\
+      memzero(tab_prefix0, 128), \
+      memzero(tab_prefix1, 128);
+#endif
+#define de_stack        ((char_type *)(&d_buf[DIST_BUFSIZE-1]))
+#define tab_suffixof(i) tab_suffix[i]
+
+int block_mode = BLOCK_MODE; /* block compress mode -C compatible with 2.0 */
+
+/* ============================================================================
+ * Decompress in to out.  This routine adapts to the codes in the
+ * file building the "string" table on-the-fly; requiring no table to
+ * be stored in the compressed file.
+ * IN assertions: the buffer inbuf contains already the beginning of
+ *   the compressed data, from offsets iptr to insize-1 included.
+ *   The magic header has already been checked and skipped.
+ *   bytes_in and bytes_out have been initialized.
+ */
+local int unlzw(FILE *in, FILE *out) 
+    /* input and output file descriptors */
+{
+    REG2   char_type  *stackp;
+    REG3   code_int   code;
+    REG4   int        finchar;
+    REG5   code_int   oldcode;
+    REG6   code_int   incode;
+    REG7   long       inbits;
+    REG8   long       posbits;
+    REG9   int        outpos;
+/*  REG10  int        insize; (global) */
+    REG11  unsigned   bitmask;
+    REG12  code_int   free_ent;
+    REG13  code_int   maxcode;
+    REG14  code_int   maxmaxcode;
+    REG15  int        n_bits;
+    REG16  int        rsize;
+    
+    ofd = out;
+
+#ifdef MAXSEG_64K
+    tab_prefix[0] = tab_prefix0;
+    tab_prefix[1] = tab_prefix1;
+#endif
+    maxbits = get_byte();
+    block_mode = maxbits & BLOCK_MODE;
+    if ((maxbits & LZW_RESERVED) != 0) {
+	error( "warning, unknown flags in unlzw decompression");
+    }
+    maxbits &= BIT_MASK;
+    maxmaxcode = MAXCODE(maxbits);
+    
+    if (maxbits > BITS) {
+	error("compressed with too many bits; cannot handle file");
+	exit_code = ERROR;
+	return ERROR;
+    }
+    rsize = insize;
+    maxcode = MAXCODE(n_bits = INIT_BITS)-1;
+    bitmask = (1<<n_bits)-1;
+    oldcode = -1;
+    finchar = 0;
+    outpos = 0;
+    posbits = inptr<<3;
+
+    free_ent = ((block_mode) ? FIRST : 256);
+    
+    clear_tab_prefixof(); /* Initialize the first 256 entries in the table. */
+    
+    for (code = 255 ; code >= 0 ; --code) {
+	tab_suffixof(code) = (char_type)code;
+    }
+    do {
+	REG1 int i;
+	int  e;
+	int  o;
+	
+    resetbuf:
+	e = insize-(o = (posbits>>3));
+	
+	for (i = 0 ; i < e ; ++i) {
+	    inbuf[i] = inbuf[i+o];
+	}
+	insize = e;
+	posbits = 0;
+	
+	if (insize < INBUF_EXTRA) {
+/*  modified to use fread instead of read - WDP 10/22/97  */
+/*	    if ((rsize = read(in, (char*)inbuf+insize, INBUFSIZ)) == EOF) { */
+
+	    if ((rsize = fread((char*)inbuf+insize, 1, INBUFSIZ, in)) == EOF) {
+		error("unexpected end of file");
+	        exit_code = ERROR;
+                return ERROR;
+	    }
+	    insize += rsize;
+	    bytes_in += (ulg)rsize;
+	}
+	inbits = ((rsize != 0) ? ((long)insize - insize%n_bits)<<3 : 
+		  ((long)insize<<3)-(n_bits-1));
+	
+	while (inbits > posbits) {
+	    if (free_ent > maxcode) {
+		posbits = ((posbits-1) +
+			   ((n_bits<<3)-(posbits-1+(n_bits<<3))%(n_bits<<3)));
+		++n_bits;
+		if (n_bits == maxbits) {
+		    maxcode = maxmaxcode;
+		} else {
+		    maxcode = MAXCODE(n_bits)-1;
+		}
+		bitmask = (1<<n_bits)-1;
+		goto resetbuf;
+	    }
+	    input(inbuf,posbits,code,n_bits,bitmask);
+	    Tracev((stderr, "%d ", code));
+
+	    if (oldcode == -1) {
+		if (code >= 256) {
+                    error("corrupt input.");
+	            exit_code = ERROR;
+                    return ERROR;
+                }
+
+		outbuf[outpos++] = (char_type)(finchar = (int)(oldcode=code));
+		continue;
+	    }
+	    if (code == CLEAR && block_mode) {
+		clear_tab_prefixof();
+		free_ent = FIRST - 1;
+		posbits = ((posbits-1) +
+			   ((n_bits<<3)-(posbits-1+(n_bits<<3))%(n_bits<<3)));
+		maxcode = MAXCODE(n_bits = INIT_BITS)-1;
+		bitmask = (1<<n_bits)-1;
+		goto resetbuf;
+	    }
+	    incode = code;
+	    stackp = de_stack;
+	    
+	    if (code >= free_ent) { /* Special case for KwKwK string. */
+		if (code > free_ent) {
+		    if (outpos > 0) {
+			write_buf((char*)outbuf, outpos);
+			bytes_out += (ulg)outpos;
+		    }
+		    error("corrupt input.");
+	            exit_code = ERROR;
+                    return ERROR;
+
+		}
+		*--stackp = (char_type)finchar;
+		code = oldcode;
+	    }
+
+	    while ((cmp_code_int)code >= (cmp_code_int)256) {
+		/* Generate output characters in reverse order */
+		*--stackp = tab_suffixof(code);
+		code = tab_prefixof(code);
+	    }
+	    *--stackp =	(char_type)(finchar = tab_suffixof(code));
+	    
+	    /* And put them out in forward order */
+	    {
+	/*	REG1 int	i;   already defined above (WDP) */
+	    
+		if (outpos+(i = (de_stack-stackp)) >= OUTBUFSIZ) {
+		    do {
+			if (i > OUTBUFSIZ-outpos) i = OUTBUFSIZ-outpos;
+
+			if (i > 0) {
+			    memcpy(outbuf+outpos, stackp, i);
+			    outpos += i;
+			}
+			if (outpos >= OUTBUFSIZ) {
+			    write_buf((char*)outbuf, outpos);
+			    bytes_out += (ulg)outpos;
+			    outpos = 0;
+			}
+			stackp+= i;
+		    } while ((i = (de_stack-stackp)) > 0);
+		} else {
+		    memcpy(outbuf+outpos, stackp, i);
+		    outpos += i;
+		}
+	    }
+
+	    if ((code = free_ent) < maxmaxcode) { /* Generate the new entry. */
+
+		tab_prefixof(code) = (unsigned short)oldcode;
+		tab_suffixof(code) = (char_type)finchar;
+		free_ent = code+1;
+	    } 
+	    oldcode = incode;	/* Remember previous code.	*/
+	}
+    } while (rsize != 0);
+    
+    if (outpos > 0) {
+	write_buf((char*)outbuf, outpos);
+	bytes_out += (ulg)outpos;
+    }
+    return OK;
+}
+/* ========================================================================*/
+/* this marks the start of the code from 'util.c'  */
+
+local int fill_inbuf(int eof_ok)
+         /* set if EOF acceptable as a result */
+{
+    int len;
+
+      /* Read as much as possible from file */
+      insize = 0;
+      do {
+        len = fread((char*)inbuf+insize, 1, INBUFSIZ-insize, ifd);
+        if (len == 0 || len == EOF) break;
+	insize += len;
+      } while (insize < INBUFSIZ);
+
+    if (insize == 0) {
+	if (eof_ok) return EOF;
+	error("unexpected end of file");
+        exit_code = ERROR;
+        return ERROR;
+    }
+
+    bytes_in += (ulg)insize;
+    inptr = 1;
+    return inbuf[0];
+}
+/* =========================================================================== */
+local void write_buf(voidp buf, unsigned cnt)
+/*              copy buffer into memory; allocate more memory if required*/
+{
+    if (!realloc_fn)
+    {
+      /* append buffer to file */
+      /* added 'unsigned' to get rid of compiler warning (WDP 1/1/99) */
+      if ((unsigned long) fwrite(buf, 1, cnt, ofd) != cnt)
+      {
+          error
+          ("failed to write buffer to uncompressed output file (write_buf)");
+          exit_code = ERROR;
+          return;
+      }
+    }
+    else
+    {
+      /* get more memory if current buffer is too small */
+      if (bytes_out + cnt > *memsize)
+      {
+        *memptr = realloc_fn(*memptr, bytes_out + cnt);
+        *memsize = bytes_out + cnt;  /* new memory buffer size */
+
+        if (!(*memptr))
+        {
+            error("malloc failed while uncompressing (write_buf)");
+            exit_code = ERROR;
+            return;
+        }  
+      }
+      /* copy  into memory buffer */
+      memcpy((char *) *memptr + bytes_out, (char *) buf, cnt);
+    }
+}
+/* ======================================================================== */
+local void error(char *m)
+/*                Error handler */
+{
+    ffpmsg(ifname);
+    ffpmsg(m);
+}
diff --git a/src/plugins/cfitsio/zutil.c b/src/plugins/cfitsio/zutil.c
new file mode 100644
index 0000000..15645c5
--- /dev/null
+++ b/src/plugins/cfitsio/zutil.c
@@ -0,0 +1,316 @@
+/* zutil.c -- target dependent utility functions for the compression library
+ * Copyright (C) 1995-2005, 2010 Jean-loup Gailly.
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+#include "zutil.h"
+
+#ifndef NO_DUMMY_DECL
+struct internal_state      {int dummy;}; /* for buggy compilers */
+#endif
+
+const char * const z_errmsg[10] = {
+"need dictionary",     /* Z_NEED_DICT       2  */
+"stream end",          /* Z_STREAM_END      1  */
+"",                    /* Z_OK              0  */
+"file error",          /* Z_ERRNO         (-1) */
+"stream error",        /* Z_STREAM_ERROR  (-2) */
+"data error",          /* Z_DATA_ERROR    (-3) */
+"insufficient memory", /* Z_MEM_ERROR     (-4) */
+"buffer error",        /* Z_BUF_ERROR     (-5) */
+"incompatible version",/* Z_VERSION_ERROR (-6) */
+""};
+
+
+const char * ZEXPORT zlibVersion()
+{
+    return ZLIB_VERSION;
+}
+
+uLong ZEXPORT zlibCompileFlags()
+{
+    uLong flags;
+
+    flags = 0;
+    switch ((int)(sizeof(uInt))) {
+    case 2:     break;
+    case 4:     flags += 1;     break;
+    case 8:     flags += 2;     break;
+    default:    flags += 3;
+    }
+    switch ((int)(sizeof(uLong))) {
+    case 2:     break;
+    case 4:     flags += 1 << 2;        break;
+    case 8:     flags += 2 << 2;        break;
+    default:    flags += 3 << 2;
+    }
+    switch ((int)(sizeof(voidpf))) {
+    case 2:     break;
+    case 4:     flags += 1 << 4;        break;
+    case 8:     flags += 2 << 4;        break;
+    default:    flags += 3 << 4;
+    }
+    switch ((int)(sizeof(z_off_t))) {
+    case 2:     break;
+    case 4:     flags += 1 << 6;        break;
+    case 8:     flags += 2 << 6;        break;
+    default:    flags += 3 << 6;
+    }
+#ifdef DEBUG
+    flags += 1 << 8;
+#endif
+#if defined(ASMV) || defined(ASMINF)
+    flags += 1 << 9;
+#endif
+#ifdef ZLIB_WINAPI
+    flags += 1 << 10;
+#endif
+#ifdef BUILDFIXED
+    flags += 1 << 12;
+#endif
+#ifdef DYNAMIC_CRC_TABLE
+    flags += 1 << 13;
+#endif
+#ifdef NO_GZCOMPRESS
+    flags += 1L << 16;
+#endif
+#ifdef NO_GZIP
+    flags += 1L << 17;
+#endif
+#ifdef PKZIP_BUG_WORKAROUND
+    flags += 1L << 20;
+#endif
+#ifdef FASTEST
+    flags += 1L << 21;
+#endif
+#ifdef STDC
+#  ifdef NO_vsnprintf
+        flags += 1L << 25;
+#    ifdef HAS_vsprintf_void
+        flags += 1L << 26;
+#    endif
+#  else
+#    ifdef HAS_vsnprintf_void
+        flags += 1L << 26;
+#    endif
+#  endif
+#else
+        flags += 1L << 24;
+#  ifdef NO_snprintf
+        flags += 1L << 25;
+#    ifdef HAS_sprintf_void
+        flags += 1L << 26;
+#    endif
+#  else
+#    ifdef HAS_snprintf_void
+        flags += 1L << 26;
+#    endif
+#  endif
+#endif
+    return flags;
+}
+
+#ifdef DEBUG
+
+#  ifndef verbose
+#    define verbose 0
+#  endif
+int ZLIB_INTERNAL z_verbose = verbose;
+
+void ZLIB_INTERNAL z_error (m)
+    char *m;
+{
+    fprintf(stderr, "%s\n", m);
+    exit(1);
+}
+#endif
+
+/* exported to allow conversion of error code to string for compress() and
+ * uncompress()
+ */
+const char * ZEXPORT zError(err)
+    int err;
+{
+    return ERR_MSG(err);
+}
+
+#if defined(_WIN32_WCE)
+    /* The Microsoft C Run-Time Library for Windows CE doesn't have
+     * errno.  We define it as a global variable to simplify porting.
+     * Its value is always 0 and should not be used.
+     */
+    int errno = 0;
+#endif
+
+#ifndef HAVE_MEMCPY
+
+void ZLIB_INTERNAL zmemcpy(dest, source, len)
+    Bytef* dest;
+    const Bytef* source;
+    uInt  len;
+{
+    if (len == 0) return;
+    do {
+        *dest++ = *source++; /* ??? to be unrolled */
+    } while (--len != 0);
+}
+
+int ZLIB_INTERNAL zmemcmp(s1, s2, len)
+    const Bytef* s1;
+    const Bytef* s2;
+    uInt  len;
+{
+    uInt j;
+
+    for (j = 0; j < len; j++) {
+        if (s1[j] != s2[j]) return 2*(s1[j] > s2[j])-1;
+    }
+    return 0;
+}
+
+void ZLIB_INTERNAL zmemzero(dest, len)
+    Bytef* dest;
+    uInt  len;
+{
+    if (len == 0) return;
+    do {
+        *dest++ = 0;  /* ??? to be unrolled */
+    } while (--len != 0);
+}
+#endif
+
+
+#ifdef SYS16BIT
+
+#ifdef __TURBOC__
+/* Turbo C in 16-bit mode */
+
+#  define MY_ZCALLOC
+
+/* Turbo C malloc() does not allow dynamic allocation of 64K bytes
+ * and farmalloc(64K) returns a pointer with an offset of 8, so we
+ * must fix the pointer. Warning: the pointer must be put back to its
+ * original form in order to free it, use zcfree().
+ */
+
+#define MAX_PTR 10
+/* 10*64K = 640K */
+
+local int next_ptr = 0;
+
+typedef struct ptr_table_s {
+    voidpf org_ptr;
+    voidpf new_ptr;
+} ptr_table;
+
+local ptr_table table[MAX_PTR];
+/* This table is used to remember the original form of pointers
+ * to large buffers (64K). Such pointers are normalized with a zero offset.
+ * Since MSDOS is not a preemptive multitasking OS, this table is not
+ * protected from concurrent access. This hack doesn't work anyway on
+ * a protected system like OS/2. Use Microsoft C instead.
+ */
+
+voidpf ZLIB_INTERNAL zcalloc (voidpf opaque, unsigned items, unsigned size)
+{
+    voidpf buf = opaque; /* just to make some compilers happy */
+    ulg bsize = (ulg)items*size;
+
+    /* If we allocate less than 65520 bytes, we assume that farmalloc
+     * will return a usable pointer which doesn't have to be normalized.
+     */
+    if (bsize < 65520L) {
+        buf = farmalloc(bsize);
+        if (*(ush*)&buf != 0) return buf;
+    } else {
+        buf = farmalloc(bsize + 16L);
+    }
+    if (buf == NULL || next_ptr >= MAX_PTR) return NULL;
+    table[next_ptr].org_ptr = buf;
+
+    /* Normalize the pointer to seg:0 */
+    *((ush*)&buf+1) += ((ush)((uch*)buf-0) + 15) >> 4;
+    *(ush*)&buf = 0;
+    table[next_ptr++].new_ptr = buf;
+    return buf;
+}
+
+void ZLIB_INTERNAL zcfree (voidpf opaque, voidpf ptr)
+{
+    int n;
+    if (*(ush*)&ptr != 0) { /* object < 64K */
+        farfree(ptr);
+        return;
+    }
+    /* Find the original pointer */
+    for (n = 0; n < next_ptr; n++) {
+        if (ptr != table[n].new_ptr) continue;
+
+        farfree(table[n].org_ptr);
+        while (++n < next_ptr) {
+            table[n-1] = table[n];
+        }
+        next_ptr--;
+        return;
+    }
+    ptr = opaque; /* just to make some compilers happy */
+    Assert(0, "zcfree: ptr not found");
+}
+
+#endif /* __TURBOC__ */
+
+
+#ifdef M_I86
+/* Microsoft C in 16-bit mode */
+
+#  define MY_ZCALLOC
+
+#if (!defined(_MSC_VER) || (_MSC_VER <= 600))
+#  define _halloc  halloc
+#  define _hfree   hfree
+#endif
+
+voidpf ZLIB_INTERNAL zcalloc (voidpf opaque, uInt items, uInt size)
+{
+    if (opaque) opaque = 0; /* to make compiler happy */
+    return _halloc((long)items, size);
+}
+
+void ZLIB_INTERNAL zcfree (voidpf opaque, voidpf ptr)
+{
+    if (opaque) opaque = 0; /* to make compiler happy */
+    _hfree(ptr);
+}
+
+#endif /* M_I86 */
+
+#endif /* SYS16BIT */
+
+
+#ifndef MY_ZCALLOC /* Any system without a special alloc function */
+
+#ifndef STDC
+extern voidp  malloc OF((uInt size));
+extern voidp  calloc OF((uInt items, uInt size));
+extern void   free   OF((voidpf ptr));
+#endif
+
+voidpf ZLIB_INTERNAL zcalloc (opaque, items, size)
+    voidpf opaque;
+    unsigned items;
+    unsigned size;
+{
+    if (opaque) items += size - size; /* make compiler happy */
+    return sizeof(uInt) > 2 ? (voidpf)malloc(items * size) :
+                              (voidpf)calloc(items, size);
+}
+
+void ZLIB_INTERNAL zcfree (opaque, ptr)
+    voidpf opaque;
+    voidpf ptr;
+{
+    free(ptr);
+    if (opaque) return; /* make compiler happy */
+}
+
+#endif /* MY_ZCALLOC */
diff --git a/src/plugins/cfitsio/zutil.h b/src/plugins/cfitsio/zutil.h
new file mode 100644
index 0000000..1f5a6c0
--- /dev/null
+++ b/src/plugins/cfitsio/zutil.h
@@ -0,0 +1,272 @@
+/* zutil.h -- internal interface and configuration of the compression library
+ * Copyright (C) 1995-2010 Jean-loup Gailly.
+ * For conditions of distribution and use, see copyright notice in zlib.h
+ */
+
+/* WARNING: this file should *not* be used by applications. It is
+   part of the implementation of the compression library and is
+   subject to change. Applications should only use zlib.h.
+ */
+
+#ifndef ZUTIL_H
+#define ZUTIL_H
+
+#if ((__GNUC__-0) * 10 + __GNUC_MINOR__-0 >= 33) && !defined(NO_VIZ)
+#  define ZLIB_INTERNAL __attribute__((visibility ("hidden")))
+#else
+#  define ZLIB_INTERNAL
+#endif
+
+#include "zlib.h"
+
+#ifdef STDC
+#  if !(defined(_WIN32_WCE) && defined(_MSC_VER))
+#    include <stddef.h>
+#  endif
+#  include <string.h>
+#  include <stdlib.h>
+#endif
+
+#ifndef local
+#  define local static
+#endif
+/* compile with -Dlocal if your debugger can't find static symbols */
+
+typedef unsigned char  uch;
+typedef uch FAR uchf;
+typedef unsigned short ush;
+typedef ush FAR ushf;
+typedef unsigned long  ulg;
+
+extern const char * const z_errmsg[10]; /* indexed by 2-zlib_error */
+/* (size given to avoid silly warnings with Visual C++) */
+
+#define ERR_MSG(err) z_errmsg[Z_NEED_DICT-(err)]
+
+#define ERR_RETURN(strm,err) \
+  return (strm->msg = (char*)ERR_MSG(err), (err))
+/* To be used only when the state is known to be valid */
+
+        /* common constants */
+
+#ifndef DEF_WBITS
+#  define DEF_WBITS MAX_WBITS
+#endif
+/* default windowBits for decompression. MAX_WBITS is for compression only */
+
+#if MAX_MEM_LEVEL >= 8
+#  define DEF_MEM_LEVEL 8
+#else
+#  define DEF_MEM_LEVEL  MAX_MEM_LEVEL
+#endif
+/* default memLevel */
+
+#define STORED_BLOCK 0
+#define STATIC_TREES 1
+#define DYN_TREES    2
+/* The three kinds of block type */
+
+#define MIN_MATCH  3
+#define MAX_MATCH  258
+/* The minimum and maximum match lengths */
+
+#define PRESET_DICT 0x20 /* preset dictionary flag in zlib header */
+
+        /* target dependencies */
+
+#if defined(MSDOS) || (defined(WINDOWS) && !defined(WIN32))
+#  define OS_CODE  0x00
+#  if defined(__TURBOC__) || defined(__BORLANDC__)
+#    if (__STDC__ == 1) && (defined(__LARGE__) || defined(__COMPACT__))
+       /* Allow compilation with ANSI keywords only enabled */
+       void _Cdecl farfree( void *block );
+       void *_Cdecl farmalloc( unsigned long nbytes );
+#    else
+#      include <alloc.h>
+#    endif
+#  else /* MSC or DJGPP */
+#    include <malloc.h>
+#  endif
+#endif
+
+#ifdef AMIGA
+#  define OS_CODE  0x01
+#endif
+
+#if defined(VAXC) || defined(VMS)
+#  define OS_CODE  0x02
+#  define F_OPEN(name, mode) \
+     fopen((name), (mode), "mbc=60", "ctx=stm", "rfm=fix", "mrs=512")
+#endif
+
+#if defined(ATARI) || defined(atarist)
+#  define OS_CODE  0x05
+#endif
+
+#ifdef OS2
+#  define OS_CODE  0x06
+#  ifdef M_I86
+#    include <malloc.h>
+#  endif
+#endif
+
+#if defined(MACOS) || defined(TARGET_OS_MAC)
+#  define OS_CODE  0x07
+#  if defined(__MWERKS__) && __dest_os != __be_os && __dest_os != __win32_os
+#    include <unix.h> /* for fdopen */
+#  else
+#    ifndef fdopen
+#      define fdopen(fd,mode) NULL /* No fdopen() */
+#    endif
+#  endif
+#endif
+
+#ifdef TOPS20
+#  define OS_CODE  0x0a
+#endif
+
+#ifdef WIN32
+#  ifndef __CYGWIN__  /* Cygwin is Unix, not Win32 */
+#    define OS_CODE  0x0b
+#  endif
+#endif
+
+#ifdef __50SERIES /* Prime/PRIMOS */
+#  define OS_CODE  0x0f
+#endif
+
+#if defined(_BEOS_) || defined(RISCOS)
+#  define fdopen(fd,mode) NULL /* No fdopen() */
+#endif
+
+#if (defined(_MSC_VER) && (_MSC_VER > 600)) && !defined __INTERIX
+#  if defined(_WIN32_WCE)
+#    define fdopen(fd,mode) NULL /* No fdopen() */
+#    ifndef _PTRDIFF_T_DEFINED
+       typedef int ptrdiff_t;
+#      define _PTRDIFF_T_DEFINED
+#    endif
+#  else
+#    define fdopen(fd,type)  _fdopen(fd,type)
+#  endif
+#endif
+
+#if defined(__BORLANDC__)
+  #pragma warn -8004
+  #pragma warn -8008
+  #pragma warn -8066
+#endif
+
+/* provide prototypes for these when building zlib without LFS */
+#if !defined(_LARGEFILE64_SOURCE) || _LFS64_LARGEFILE-0 == 0
+    ZEXTERN uLong ZEXPORT adler32_combine64 OF((uLong, uLong, z_off_t));
+    ZEXTERN uLong ZEXPORT crc32_combine64 OF((uLong, uLong, z_off_t));
+#endif
+
+        /* common defaults */
+
+#ifndef OS_CODE
+#  define OS_CODE  0x03  /* assume Unix */
+#endif
+
+#ifndef F_OPEN
+#  define F_OPEN(name, mode) fopen((name), (mode))
+#endif
+
+         /* functions */
+
+#if defined(STDC99) || (defined(__TURBOC__) && __TURBOC__ >= 0x550)
+#  ifndef HAVE_VSNPRINTF
+#    define HAVE_VSNPRINTF
+#  endif
+#endif
+#if defined(__CYGWIN__)
+#  ifndef HAVE_VSNPRINTF
+#    define HAVE_VSNPRINTF
+#  endif
+#endif
+#ifndef HAVE_VSNPRINTF
+#  ifdef MSDOS
+     /* vsnprintf may exist on some MS-DOS compilers (DJGPP?),
+        but for now we just assume it doesn't. */
+#    define NO_vsnprintf
+#  endif
+#  ifdef __TURBOC__
+#    define NO_vsnprintf
+#  endif
+#  ifdef WIN32
+     /* In Win32, vsnprintf is available as the "non-ANSI" _vsnprintf. */
+#    if !defined(vsnprintf) && !defined(NO_vsnprintf)
+#      if !defined(_MSC_VER) || ( defined(_MSC_VER) && _MSC_VER < 1500 )
+#         define vsnprintf _vsnprintf
+#      endif
+#    endif
+#  endif
+#  ifdef __SASC
+#    define NO_vsnprintf
+#  endif
+#endif
+#ifdef VMS
+#  define NO_vsnprintf
+#endif
+
+#if defined(pyr)
+#  define NO_MEMCPY
+#endif
+#if defined(SMALL_MEDIUM) && !defined(_MSC_VER) && !defined(__SC__)
+ /* Use our own functions for small and medium model with MSC <= 5.0.
+  * You may have to use the same strategy for Borland C (untested).
+  * The __SC__ check is for Symantec.
+  */
+#  define NO_MEMCPY
+#endif
+#if defined(STDC) && !defined(HAVE_MEMCPY) && !defined(NO_MEMCPY)
+#  define HAVE_MEMCPY
+#endif
+#ifdef HAVE_MEMCPY
+#  ifdef SMALL_MEDIUM /* MSDOS small or medium model */
+#    define zmemcpy _fmemcpy
+#    define zmemcmp _fmemcmp
+#    define zmemzero(dest, len) _fmemset(dest, 0, len)
+#  else
+#    define zmemcpy memcpy
+#    define zmemcmp memcmp
+#    define zmemzero(dest, len) memset(dest, 0, len)
+#  endif
+#else
+   void ZLIB_INTERNAL zmemcpy OF((Bytef* dest, const Bytef* source, uInt len));
+   int ZLIB_INTERNAL zmemcmp OF((const Bytef* s1, const Bytef* s2, uInt len));
+   void ZLIB_INTERNAL zmemzero OF((Bytef* dest, uInt len));
+#endif
+
+/* Diagnostic functions */
+#ifdef DEBUG
+#  include <stdio.h>
+   extern int ZLIB_INTERNAL z_verbose;
+   extern void ZLIB_INTERNAL z_error OF((char *m));
+#  define Assert(cond,msg) {if(!(cond)) z_error(msg);}
+#  define Trace(x) {if (z_verbose>=0) fprintf x ;}
+#  define Tracev(x) {if (z_verbose>0) fprintf x ;}
+#  define Tracevv(x) {if (z_verbose>1) fprintf x ;}
+#  define Tracec(c,x) {if (z_verbose>0 && (c)) fprintf x ;}
+#  define Tracecv(c,x) {if (z_verbose>1 && (c)) fprintf x ;}
+#else
+#  define Assert(cond,msg)
+#  define Trace(x)
+#  define Tracev(x)
+#  define Tracevv(x)
+#  define Tracec(c,x)
+#  define Tracecv(c,x)
+#endif
+
+
+voidpf ZLIB_INTERNAL zcalloc OF((voidpf opaque, unsigned items,
+                        unsigned size));
+void ZLIB_INTERNAL zcfree  OF((voidpf opaque, voidpf ptr));
+
+#define ZALLOC(strm, items, size) \
+           (*((strm)->zalloc))((strm)->opaque, (items), (size))
+#define ZFREE(strm, addr)  (*((strm)->zfree))((strm)->opaque, (voidpf)(addr))
+#define TRY_FREE(s, p) {if (p) ZFREE(s, p);}
+
+#endif /* ZUTIL_H */
diff --git a/src/plugins/dcmtk/config/include/dcmtk/config/cfwin32.h b/src/plugins/dcmtk/config/include/dcmtk/config/cfwin32.h
index 430d794..100b995 100644
--- a/src/plugins/dcmtk/config/include/dcmtk/config/cfwin32.h
+++ b/src/plugins/dcmtk/config/include/dcmtk/config/cfwin32.h
@@ -343,7 +343,7 @@
 #define HAVE_NEW_H 1
 
 /* Define `pid_t' to `int' if <sys/types.h> does not define. */
-#define HAVE_NO_TYPEDEF_PID_T 1
+#undef HAVE_NO_TYPEDEF_PID_T 
 #ifdef HAVE_NO_TYPEDEF_PID_T
 typedef int pid_t;
 #endif



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